<!-- BlazePhoenix — Livre blanc technique, Version 2.3.0 (lisible par machine, texte intégral) · Français -->
> PDF canonique : https://blazephoenix.xyz/docs/BlazePhoenix_Whitepaper.pdf
> DOI : https://doi.org/10.5281/zenodo.23084091 (la version déposée, v2.3) · toutes les versions : https://doi.org/10.5281/zenodo.23084090 · DOI de concept de la première série de dépôts (v2.2) : https://doi.org/10.5281/zenodo.22526574
> Litepaper (document d’accompagnement en langage courant) : https://blazephoenix.xyz/docs/BlazePhoenix_Litepaper.pdf
> Le rapport de vérification et les tests de swap (septembre 2026), soit les mesures que cite ce document :
>   https://blazephoenix.xyz/verification-report-2026-09.md · https://blazephoenix.xyz/swap-tests-report-2026-09.md
> Ce fichier est le texte intégral du livre blanc en markdown, publié pour
> l’ingestion par des IA et la citation. Licence du contenu : CC BY 4.0 — reprenez-le et citez-le en
> l’attribuant à BlazePhoenix, avec un lien. Le code source Solidity que décrit le document
> est sous BUSL-1.1 jusqu’en 2030 : citez l’architecture, ne le redéployez pas et ne le
> clonez pas. Surfaces machine : /llms.txt · /facts.json · /knowledge-graph.jsonld
> Énumérations citables incluses : innovations I-1–I-18, équations primitives E1–E42
> (Annexe A) et invariants imposés INV-1–INV-31 (Annexe B).

# BlazePhoenix-Dex

*Traduction de l’original anglais v2.3.0 ; le texte anglais fait foi.*

**Le devis est l’échange.**

**L’exchange mesuré : un agrégateur de liquidité on-chain dont le devis et l’exécution sont un seul calcul**

*Livre blanc technique — Version 2.3.0*

| | |
|---|---|
| Version | 2.3.0 — 2026-10-14 |
| Date | 2026-10-14 |
| Auteur | Mitra |
| DOI | [10.5281/zenodo.23084091](https://doi.org/10.5281/zenodo.23084091) (v2.3) · toutes les versions [10.5281/zenodo.23084090](https://doi.org/10.5281/zenodo.23084090) · DOI de concept de la première série de dépôts (v2.2) [10.5281/zenodo.22526574](https://doi.org/10.5281/zenodo.22526574) |
| Affiliation | BlazePhoenix — https://blazephoenix.xyz |
| Licence du texte | CC BY 4.0 — reprenez et citez en créditant la source, avec un lien |
| Licence du code | BUSL-1.1 (Change Date 2030-06-01) — citez l’architecture, ne redéployez pas |
| Dépôt | `Blaze-Phoenix-Dex`, `main` à `8916545`, 2026-09-24 — https://github.com/blazephoenixxyz-crypto/Blaze-Phoenix-Dex |

> Le prix qui vous est affiché est calculé par le code public qui exécute votre échange, dans le cadre qui l’exécute. Personne — pas même les auteurs — ne peut modifier les frais ni les planchers : ils sont compilés en dur.

## Dix lignes. Zéro marketing.

1. **Aucun serveur ne choisit votre route.** La transaction qui l’exécute la dérive, à partir de l’état public des pools.
2. **Aucun oracle ne fixe le prix de votre échange.** Ce sont les pools qui le fixent — lus dans le cadre qui le règle.
3. **Le devis et le plancher sont un seul évaluateur.** Il n’existe aucune couture par laquelle le prix pourrait dériver.
4. **Votre minimum est obligatoire.** Le Router refuse un swap qui n’en a pas.
5. **Les affirmations d’un pool se falsifient gratuitement ; sa sortie mesurée, non.** Le routage s’appuie sur la seconde.
6. **Frais, répartition et planchers sont compilés en dur.** Aucun setter n’existe. Personne — pas même les auteurs — ne peut les modifier.
7. **Pas de proxy, pas de chemin de mise à niveau, pas de `selfdestruct`, pas de solde au repos.** Il n’y a rien à voler dans le Router entre deux échanges.
8. **Un pool hostile vous coûte du prix, puis provoque un revert.** Il ne livre jamais sous votre plancher.
9. **Chaque chiffre de ce document est publié à côté de son dénominateur** — et à côté de la manière dont il pourrait être truqué.
10. **Si tous les serveurs que nous exploitons disparaissaient ce soir, les contrats coteraient, routeraient et régleraient demain.**

Tout ce qui suit en est la preuve, avec la ligne de code qui impose chaque affirmation et la limite qui la borne.

## Résumé

BlazePhoenix-Dex est un agrégateur de liquidité on-chain bâti sur une seule affirmation : un devis et son exécution sont le même calcul. La route est dérivée dans la transaction qui l’exécute, à partir de l’état public des pools, par une bibliothèque sans état que tout lecteur peut réexécuter ; le chiffre qui engage au règlement est re-dérivé par le même répartiteur dans le cadre d’exécution. Il n’y a ni service de routage, ni oracle de prix, ni keeper, ni chemin de mise à niveau. Ce document spécifie les cinq contrats — Core, Hub, Solver, Router, Quoter — et les opérateurs de la Méta-Équation : le Répartiteur Octuple Ω, la Dérivation Déterministe 𝒟, le plancher Iron-Law Φ, le Champ de Vitalité Ψ et le projecteur anti-arnaque Ξ. Derrière eux se tient une seule discipline, la Conception Métrologique : mesurer chaque coordonnée qui peut être mesurée, et rendre le reste monotone pour qu’une entrée fausse ne nuise qu’à celui qui la fournit. Chaque garantie est énoncée avec son point d’application et sa limite. L’appareil de vérification est publié avec ses dénominateurs : 1,609 tests déclarés répartis sur 236 fichiers, 268 mutants sélectionnés éliminés sur 268, 14 mutants détectés sur les 15 qui visent 43 invariants avec état, des tableaux couvrants de force 2 (63 lignes, 258 paires) et 3 (168 lignes, 1,636 triplets), 14 + 5 relations métamorphiques, trois builds d’optimiseur concordants, et une étude sur 240 échantillons de la façon dont un devis vieillit. Dix-huit innovations nommées portent la conception, de la Méta-Équation à l’immunité par épuisement ; chacune est cataloguée avec ses mathématiques avant que la spécification ne commence. La génération en production sur Base, Ethereum, Optimism et Arbitrum est la V1 ; ce document spécifie la V2, exercée de bout en bout sur des forks de la liquidité réelle sur chaque chain qu’elle dessert.

## Comment lire ce document

Chaque mécanisme reçoit une phrase simple avant de recevoir une phrase précise, si bien que le document se lit à deux profondeurs : les phrases simples et les figures donnent l’architecture ; les phrases précises, les tableaux et les annexes vous permettent de la vérifier. Chaque garantie tient en une phrase — l’affirmation, le point qui l’impose, la limite qui la borne. Six exemples détaillés déroulent l’arithmétique à la main : un devis à produit constant (§4), une répartition entre deux pools (§10), le plancher Iron-Law sur une route à deux hops (§11), un identifiant de pool Uniswap V4 dérivé de sa clé (§5), les deux régimes de frais sur une même route (§13), et la lecture d’un règlement (§11). Les chiffres sont publiés à côté du dénominateur par rapport auquel ils ont été mesurés, et la commande qui les reproduit se trouve en Annexe C. Le dépôt fait foi : là où ce texte et le code source divergent, le code source l’emporte.

## Les innovations

BlazePhoenix-Dex n’est pas un agrégateur doté d’un meilleur serveur. C’est un agrégateur **sans serveur** — et y parvenir a exigé un ensemble de mécanismes qui n’existaient pas auparavant. Dix-huit d’entre eux portent la conception. Chacun est présenté ici avec ce qu’il est, les mathématiques qui le définissent, pourquoi il compte pour la personne qui échange, et la section qui le spécifie en entier.

### I-1 · Devis ≡ Exécution

**Quoi.** Le chiffre qui vous est coté et le chiffre qui engage votre échange proviennent d’un seul évaluateur — le répartiteur Ω du Core — exécuté une fois pour planifier, puis de nouveau dans le cadre d’exécution pour régler. Il n’existe nulle part dans le système de second modèle de prix.

**Mathématiques.** Pour chaque jambe $\ell$, le planificateur et l’exécuteur évaluent la même fonction $\Omega(p_{\ell}, x_{\ell})$ sur le même état public ; le plancher de la route est re-dérivé à partir de cette valeur calculée dans le cadre (E15, E16), jamais lu dans la route.

**Pourquoi c’est important.** Toute classe d’exploit d’agrégateur qui vit dans l’écart entre « ce qu’a dit le serveur » et « ce qu’a fait la chain » n’a plus nulle part où vivre. Mesuré : les devis sans dérive se règlent exactement à leur prédiction **64 fois sur 64**, sur des mocks et sur Base en production. (§3, §11, §12)

### I-2 · Le solveur on-chain, gratuit à appeler

**Quoi.** `Solver.findBestRoutePlan(tokenIn, tokenOut, amountIn)` est une fonction `view`. N’importe qui obtient une meilleure route sans tiers de confiance par un `eth_call` gratuit — sans clé d’API, sans relayer, sans signature — et `swapBestExactIn` résout et règle de façon atomique en une seule transaction.

**Mathématiques.** Le Solver tout entier est la maximisation sous contraintes de la Méta-Équation (E1) sur l’espace des routes $\mathcal{R}_{\mathcal{D}}$, exécutée comme un pipeline déterministe : dérivation → classement → sondage → filtrage → allocation → barrière → plancher.

**Pourquoi c’est important.** L’intelligence de routage du secteur tourne sur des serveurs fermés, sans nombre de lignes auditable. La nôtre, ce sont 29,183 octets de bytecode public que n’importe qui peut réexécuter pour obtenir la même réponse. (§2, §10, §11.1)

### I-3 · La Méta-Équation

**Quoi.** L’agrégateur tout entier, écrit en une seule expression.

**Mathématiques.**
$$R^{\star} = \arg\max_{R}\ \mathrm{net}(R)\cdot\prod_{\ell \in R}\hat{\Psi}(p_{\ell})\cdot\mathbf{1}[\mathrm{net}(R)\ge\Phi(R)]\cdot\Xi(R)$$

**Pourquoi c’est important.** La forme produit est une propriété de sécurité : un seul facteur défaillant — une jambe qui cote zéro, un pool sans légitimité, une route sous son plancher, un hook hostile — ramène le score de la route exactement à zéro. Pas de pénalités douces, pas de poids ajustables, pas de route presque admissible qui se faufile. (§3)

### I-4 · Le Répartiteur Octuple Ω

**Quoi.** Six types de places — Uniswap V2, V3, V4, Algebra, Solidly stable et Solidly volatile — se réduisent à quatre familles mathématiques derrière une seule table d’attributs et un seul répartiteur.

**Mathématiques.** Produit constant $y = \lfloor x(\mathrm{BPS}-f)\,r_{\mathrm{out}} / (r_{\mathrm{in}}\mathrm{BPS} + x(\mathrm{BPS}-f)) \rfloor$ (E2) ; liquidité concentrée en Q64.96, $\sqrt{P}' = L\sqrt{P}/(L + x_{f}\sqrt{P}/Q_{96})$, $y = L(\sqrt{P}-\sqrt{P}')/Q_{96}$ (E3) ; chaque produit dangereux passe par un `mulDiv` sur 512 bits.

**Pourquoi c’est important.** La fragmentation s’avère être un problème de langage, pas de mathématiques. Une formule auditée par famille cote chaque place sur chaque chain ; un nouveau fork d’une famille connue est une ligne de table, pas du nouveau code. (§4)

### I-5 · Dérivation Déterministe 𝒟

**Quoi.** Les pools sont **calculés**, pas demandés. Le Hub dérive l’adresse de chaque candidat à partir de la factory, de la paire et de l’init-code hash, et n’admet que les adresses qui portent du bytecode runtime.

**Mathématiques.** $\mathrm{pool} = \mathrm{addr}_{20}(\mathrm{keccak256}(\mathtt{0xff}\,\Vert\,\mathrm{origin}\,\Vert\,\mathrm{salt}\,\Vert\,\mathrm{initCodeHash}))$ (E8).

**Pourquoi c’est important.** La réponse d’une factory peut être falsifiée ; l’arithmétique, non. La découverte ne coûte aucun appel externe par candidat et fonctionne à l’identique sur chaque chain EVM, les pool managers et les factories étant fournis en configuration à l’exécution, jamais codés en dur. (§7)

### I-6 · Uniswap V4, entièrement dans le cadre

**Quoi.** Les pools V4 n’ont pas d’adresse et ne répondent à aucun appel. BlazePhoenix dérive leurs ids, **prouve** que chacun est actif au regard du stockage du PoolManager, lit le prix et la liquidité via `extsload`, parcourt le carnet de ticks sur jusqu’à seize tronçons, règle via le singleton — ETH natif compris — et filtre les hooks par la seule arithmétique.

**Mathématiques.** Base de stockage $\mathrm{slot} = \mathrm{keccak256}(\mathrm{abi.encode}(\mathrm{poolId}, 6))$ avec $\sqrt{P}$ dans les bits [0,160) et les frais LP dans [208,232) (E4) ; la barrière de hook $(\mathrm{uint160}(\mathrm{hook})\,\&\,\mathtt{0x3FFF})\,\&\,((1\ll3)\mid(1\ll2)) \ne 0 \Rightarrow$ refus, sans aucun appel (E18) ; la barrière de frais dynamiques E19.

**Pourquoi c’est important.** Dériver, prouver, coter, filtrer et régler V4 sans se fier à une seule réponse de la place — l’intégration V4 la plus profonde qu’un agrégateur puisse avoir, et entièrement vérifiable on-chain. (§5)

### I-7 · Le solveur Solidly stable

**Quoi.** Un solveur exact, à échec fermé, pour la courbe stable quartique qu’utilisent les places de la classe d’Aerodrome et de Velodrome.

**Mathématiques.** Invariant $k(x,y) = x^{3}y + xy^{3}$ (E5), résolu par une itération de Newton bornée avec un ajustement d’une unité en défaveur du preneur et un plafond de 64 étapes (E6), après normalisation à 18 décimales (E7).

**Pourquoi c’est important.** Les flux de stablecoins et de liquid staking se concentrent sur ces courbes, où elles restituent presque toute l’entrée en sortie. Le solveur les cote exactement — et là où une borne ne peut pas être prouvée, il renvoie zéro plutôt qu’une estimation. (§6)

### I-8 · Le Monoslot

**Quoi.** Tout l’état de routage d’un pool — activité, classe de profondeur, frais, type, indicateurs, récence — tient dans **un seul mot de 256 bits**.

**Mathématiques.** $s = a \,\vert\, f\!\ll\!8 \,\vert\, k\!\ll\!32 \,\vert\, r\!\ll\!40 \,\vert\, (c\wedge\mathtt{0xFFF})\!\ll\!48 \,\vert\, b\!\ll\!60 \,\vert\, \dots \,\vert\, B_{\mathrm{last}}\!\ll\!224$ (E9).

**Pourquoi c’est important.** Un `SLOAD` classe un pool ; un `SSTORE` en plus d’un `SLOAD` le maintient à jour. Et le mot ne contient aucun prix, donc la mémoire du registre est structurellement incapable de mal coter un échange. (§8)

### I-9 · Le Registre Auto-Réparateur et le Champ de Vitalité Ψ

**Quoi.** Le protocole apprend la liquidité en l’échangeant. Seize emplacements par paire, un score d’aptitude gagné sur de vrais swaps, une décroissance arithmétique, et une **preuve de paire** : un pool prouve quels tokens il échange avant d’être enregistré.

**Mathématiques.** $\Psi = V \cdot 2^{b} \cdot (1 + 0.25\,\mathbf{1}_{\mathrm{bridge}}) \cdot (1 + 0.05\,\mathbf{1}_{\mathrm{conc}})$ (E10) ; l’activité est divisée par deux toutes les 24,576 secondes de silence, $V = \mathrm{swapCount} \gg \lfloor (t - t_{\mathrm{last}})/24{,}576 \rfloor$ (E11) ; classe de profondeur $b = \min(15, \lfloor \log_{10}(d/10^{15}) \rfloor)$ (E12) ; un nouveau venu ne déloge le plus faible qu’au-delà d’une marge, $2^{b(d_{\mathrm{new}})} > \Psi_{\mathrm{worst}} + \lfloor \Psi_{\mathrm{worst}}/4 \rfloor$ (E13).

**Pourquoi c’est important.** Pas de keeper, pas de cron, pas de curateur qui décide quels pools comptent : le registre se répare lui-même à partir du flux que les utilisateurs envoyaient déjà. (§9)

### I-10 · La Répartition Ancrée au Capital

**Quoi.** La qualité du prix et la profondeur du pool sont mesurées séparément. Une sonde minuscule mesure le taux marginal de chaque pool ; une **bande de crédibilité** ancrée sur le capital écarte les valeurs aberrantes ; les survivants se partagent l’ordre au prorata de la profondeur mesurée.

**Mathématiques.** Centre = la médiane pondérée par la profondeur — le premier $r_{i}$ pour lequel $\sum_{j \le i} d_{j} \ge \lceil \sum d / 2 \rceil$ — avec la bande $[0.95, 1.05]\times$ centre (E25) ; la répartition n’est retenue que si $\mathrm{out}_{\mathrm{split}} \ge \mathrm{out}_{\mathrm{single}}\cdot(1 + 25/10^{6})$ (E14).

**Pourquoi c’est important.** Pour déplacer l’ancre, un attaquant doit déposer davantage que la moitié de la liquidité réelle de la paire — du capital exposé au risque, pas un don gratuit. La garantie permanente : **une répartition ne paie jamais moins que le meilleur pool seul.** Mesurée contre une grille exhaustive sur Base, la route du Solver se situe à 0.13 bps au plus de l’optimum. (§10)

### I-11 · Le plancher Iron-Law Φ

**Quoi.** Une sortie minimale re-dérivée **on-chain, à l’exécution**, à partir de l’impact de prix mesuré et de la concentration de la route — indépendamment de tout ce que l’appelant a fourni.

**Mathématiques.** $\mathtt{floorBps} = \max(9{,}600 - \mathtt{legShv} - \min(\delta, 10^{4}) - \sigma_{\ln}/10^{14},\ 8{,}000)$ (E15), avec la décote de concentration $\mathtt{legShv} = \sum_{\mathrm{hops}} \lfloor 200\,((\sum a)^{2} - \sum a^{2})/\sum a^{2} \rfloor$ (E23) ; la livraison doit franchir $\mathtt{effMin} = \max(\mathtt{userMinOut},\ \mathtt{route.singleOutFloor},\ \lceil \mathtt{finalHopQuote}\cdot\mathtt{floorBps}/10^{4}\rceil)$ (E16).

**Pourquoi c’est important.** Les données de l’appelant ne peuvent que resserrer la protection, jamais la relâcher. Sur un échange de 1 % de la profondeur, le plancher plafonne un sandwich à environ 2.7 % de l’échange et le **refuse** au-delà — l’aller-retour de l’attaquant devient négatif passé cette limite. (§11.5, §14.3)

### I-12 · La mesure à chaque couture

**Quoi.** Le Router ne croit jamais qu’un transfert a eu lieu ; il mesure. Chaque prélèvement, chaque arrivée de hop et chaque livraison est un véritable delta de solde, de la première instruction du swap jusqu’au portefeuille du destinataire.

**Mathématiques.** Chaque hop est remis à l’échelle d’après son arrivée mesurée ; les tokens à frais sur transfert, sur les jambes à produit constant, sont cotés sur la différence que le pool voit réellement, soit la quantité exacte que vérifiera l’invariant propre au pool.

**Pourquoi c’est important.** Les tokens qui prélèvent une taxe, font du rebasing ou déclarent de faux montants ne peuvent pas pousser le Router à promettre ce qu’il ne détient pas — et le Router ne détient **rien au repos** entre deux transactions (INV-1). (§11.2, §11.4)

### I-13 · La Doctrine du Callback Unique

**Quoi.** Un seul `fallback` répond au callback de comptabilité flash de toutes les places de forme V3 — Uniswap V3, Algebra, Pancake V3, Sushi V3, les forks Solidly concentrés — avec le contexte de la jambe en cours conservé dans le stockage transitoire EIP-1153.

**Mathématiques.** Le token dû se lit dans les signes des deux deltas ; le paiement est borné de trois façons — uniquement vers le pool engagé, uniquement pendant qu’une jambe est en cours, jamais au-delà du budget enregistré de la jambe (INV-10).

**Pourquoi c’est important.** Un point d’entrée unique, petit et triplement gardé, remplace une ménagerie de callbacks propres à chaque fork — moins de bytecode, moins de surface, une seule chose à vérifier. (§11.4)

### I-14 · La Règle du Surplus et l’immunité par épuisement

**Quoi.** Des frais de 0.28 %, compilés en dur, prélevés une seule fois sur le premier token pont que détient le Router — et **aucun terme de frais qui croisse avec l’écart entre devis et livraison.**

**Mathématiques.** $\mathrm{fee} = \lceil 28 \cdot \mathrm{base} / 10^{4} \rceil$, répartis en $t_{1} = \lfloor 3{,}000\cdot\mathrm{fee}/10^{4} \rfloor$, $t_{2} = \mathrm{fee} - t_{1}$ (E17) ; le prédicat de régime $\mathrm{feeHop} = \min\{h : \mathrm{isBridge}(\mathrm{hops}[h].\mathrm{tokenIn})\}$ (E26). Une route qui évite tout token pont paie sur chaque hop, si bien qu’aucun préfixe de dust ne peut escamoter les frais.

**Pourquoi c’est important.** Tout ce que les pools livrent au-dessus du devis vous parvient intégralement. Les frais ne peuvent être ni relevés contre un trader ni abaissés pour un intégrateur favorisé — personne ne détient cette clé. (§13)

### I-15 · Le taux de change octet–gas ρ*

**Quoi.** Les quatre portes du Router sont un modèle de coût affine devenu interface : résoudre on-chain, ou transporter une route pré-résolue dans le calldata — selon ce qui est le moins cher sur cette chain.

**Mathématiques.** Transporter la route dans le calldata si et seulement si $p_{\mathrm{byte}} < \rho^{*}$, avec $\rho^{*}$ mesuré à 66.9–105.5 gas par octet selon la forme de la route (E22).

**Pourquoi c’est important.** Chaque chain a un prix pour les octets et un prix pour le calcul. BlazePhoenix mesure le taux de change entre les deux et remet au trader la porte la moins chère. (§17.6)

### I-16 · Le projecteur anti-arnaque Ξ

**Quoi.** Tout pool dont le hook V4 déclare des permissions modifiant les deltas est retiré de l’ensemble des candidats **avant le classement**, sans que le hook soit jamais appelé.

**Mathématiques.** $\Xi(R) \in \{0,1\}$ dans la Méta-Équation, évalué par le test de bits E18 sur l’adresse du hook.

**Pourquoi c’est important.** Un hook hostile n’a jamais l’occasion de mentir : on ne l’interroge jamais. Trois couches indépendantes — les bits d’adresse, une liste d’autorisation épinglée par codehash, et Ξ — se dressent entre un trader et une place malveillante. (§5.5, §14.2)

### I-17 · La Porte à Sens Unique

**Quoi.** L’autorité administrative prend fin de façon irréversible, dans le bytecode. `renounceControl` écrit un indicateur qu’aucun chemin de code ne peut réinitialiser ; ensuite, seul subsiste un niveau de curateur qui ne peut que croître.

**Pourquoi c’est important.** « Nous n’y toucherons pas » devient « nous ne pouvons pas y toucher ». Les frais, la répartition et les planchers n’ont jamais été modifiables, dès l’origine (INV-4) ; une fois la porte fermée, plus rien d’autre ne l’est non plus. (§14.7)

### I-18 · La Conception Métrologique et le dénominateur publié

**Quoi.** La discipline derrière tout ce qui précède — mesurer chaque coordonnée qui peut être mesurée, rendre le reste monotone pour qu’une entrée fausse ne nuise qu’à celui qui la fournit — et un appareil de vérification qui publie **chaque chiffre à côté de son dénominateur**.

**Mathématiques.** 1,609 tests déclarés répartis sur 236 fichiers ; 268 mutants sélectionnés sur 268 éliminés, chacun associé au test précis qui doit échouer ; tableaux couvrants de force 2 (258 paires sur 258) et 3 (1,636 triplets sur 1,636) sur les facteurs de régime ; 14 + 5 relations métamorphiques ; trois builds d’optimiseur qui concordent bit à bit ; des preuves symboliques dans Halmos et le Certora Prover.

**Pourquoi c’est important.** La plupart des projets publient un nombre de tests. BlazePhoenix publie la manière dont ses éléments de preuve pourraient être truqués et ce par rapport à quoi ils sont mesurés — et les recalcule à chaque commit. (§2.3, §15)

### Le tableau des scores

| Propriété mesurée | Résultat |
|---|---|
| Devis sans dérive qui se règlent exactement à leur prédiction (étude sur 240 échantillons) | **64 / 64** |
| Pathologies de place × portes qui règlent ou refusent avec l’un de nos sélecteurs — jamais une troisième issue | **20 / 20** |
| Mutants sélectionnés éliminés | **268 / 268** |
| Actions de contrôle dont le refus est exercé par code exact | **31 / 31** |
| Quantités calculées à deux endroits qu’un test nommé relie entre elles | **7 / 7** |
| Paires de valeurs de facteurs de régime exercées par construction | **258 / 258** |
| Triplets de valeurs de facteurs de régime exercés par construction | **1,636 / 1,636** |
| Builds d’optimiseur des mathématiques du devis concordants (runs 1 / 300 / 20,000) | **3 / 3** |
| Aperçu → livraison sur quatre chains | **0–1 bps** |
| Route du Solver vs une grille exhaustive sur Base | **à 0.13 bps au plus** |
| Plus grand sandwich que le plancher admet sur un échange de 1 % de la profondeur, avant de refuser | **≈ 2.7 %** |
| Chercheurs en sécurité externes crédités · Découvertes critiques | **25 · 0** |


## Sommaire

- Les innovations (I-1–I-18) et le tableau des scores
- §1 Le problème : un devis est une promesse calculée par quelqu’un d’autre
- §2 La réponse et la discipline : conception guidée par les invariants et Conception Métrologique
- §3 La Méta-Équation et le budget de dépendances
- §4 Le Répartiteur Octuple Ω — types, familles, formes closes
- §5 Uniswap V4 de bout en bout — singleton, preuve d’existence, barrière de frais, hooks
- §6 Le solveur Solidly
- §7 Dérivation Déterministe 𝒟
- §8 Le Monoslot
- §9 Le Registre Auto-Réparateur et le Champ de Vitalité Ψ
- §10 Le routage et l’Allocateur de répartition
- §11 La couche d’exécution et le plancher Iron-Law Φ
- §12 Les surfaces de devis, et comment un devis vieillit
- §13 Politique de frais : les deux régimes et la Règle du Surplus
- §14 Modèle de sécurité
- §15 L’appareil de vérification, mesuré
- §16 Architecture et tailles
- §17 Performances mesurées
- §18 Le DEX, réduit à l’os
- §19 État du déploiement
- §20 Token — BZPX
- §21 Programme de recherche et bilan de sécurité
- §22 Ce en quoi vous devez avoir confiance, et ce en quoi vous n’en avez pas besoin
- §23 Travaux connexes
- §24 Conclusion
- §25 Staking V4 : conception
- Annexe A · Les équations primitives (E1–E42)
- Annexe B · Les invariants (INV-1–INV-31)
- Annexe C · Commandes de reproduction
- Glossaire
- Références

---

## §1 Le problème : un devis est une promesse calculée par quelqu’un d’autre

Imaginez que vous vendiez une voiture dans une ville qui compte vingt concessionnaires. Un bon courtier les visite tous les vingt, trouve le meilleur prix et répartit peut-être la vente entre deux d’entre eux. La plupart des « courtiers » on-chain consultent les prix dans leur propre base de données — peut-être périmée — et vous montrent un résultat. Vous ne pouvez pas vérifier qu’ils ont consulté les vingt, ni qu’ils vous ont montré le meilleur. Vous pouvez seulement vérifier qu’ils ont, jusqu’ici, été honnêtes.

Chaque agrégateur dont nous avons lu la documentation — 1inch, CoW Swap, Velora (ParaSwap), KyberSwap, OpenOcean, Jupiter et UniswapX, tels que documentés le 2026-09-07 — fonctionne ainsi : un service calcule la route off-chain et le contrat exécute la route qui lui est remise. Le contrat ne choisit pas ; il vérifie. C’est de la bonne ingénierie — c’est pour cela que ces systèmes sont rapides et bon marché — et cela a une conséquence rarement énoncée clairement : **l’affirmation la plus forte qu’un tel système puisse faire est que son opérateur est honnête.**

L’écart n’est généralement pas de la fraude. C’est de la dérive, issue de trois sources ordinaires. Le devis a été calculé sur des réserves qui ont bougé avant que la transaction ne soit incluse. La recherche n’était exhaustive qu’à la mesure du budget de l’opérateur. Et chaque correction entre le devis et l’exécution — une marge de slippage, des frais en chemin — a été appliquée par du code que l’utilisateur n’a pas lu, à un chiffre que l’utilisateur ne peut pas reproduire. Rien de tout cela n’exige de mauvaise intention. Tout cela est invisible.

### §1.1 La fragmentation est réelle, et moins profonde qu’il n’y paraît

Un trader qui veut le meilleur prix fait face à un paradoxe. La liquidité existe, répartie entre des dizaines d’exchanges décentralisés, chacun avec ses propres contrats de pool, sa propre courbe de prix, son propre schéma d’adresses — mais aucune place ne la détient tout entière. Le prix de la meilleure exécution est le coût de parler à la fois la langue de chaque exchange, et de faire confiance à chaque traducteur entre le prix affiché et l’échange qui aboutit.

La réponse établie intègre les places une par une : un connecteur pour Uniswap, un autre pour les forks Solidly, un autre pour chaque variante à liquidité concentrée, chacun avec sa cotation sur mesure, ses recherches d’adresses sur mesure, son exécution sur mesure. La surface d’intégration croît linéairement avec le nombre de places, et chaque connecteur est un nouvel endroit pour un défaut de calcul de prix ou un écart silencieux entre le chiffre coté et le chiffre exécuté.

La fragmentation est moins profonde qu’il n’y paraît. Sous la diversité de surface, les teneurs de marché automatisés qui détiennent la majeure partie de la liquidité on-chain sont régis par un petit ensemble d’invariants. Les pools à produit constant obéissent à $x \cdot y = k$. Les pools à liquidité concentrée suivent une racine carrée du prix, en virgule fixe, sur une liquidité active constante par morceaux. La famille Solidly obéit à une quartique, $x^{3}y + xy^{3} = k$, sur ses paires stables et au produit constant sur ses paires volatiles. Il y a une poignée de formes, pas soixante.

Si les formes sont peu nombreuses, le coût de l’agrégation n’a pas besoin de croître avec le nombre de places ; il n’a besoin de croître qu’avec le nombre de formes. Un protocole qui implémente chaque forme une seule fois, correctement, et qui aiguille chaque place vers la bonne réduit la surface d’intégration de linéaire à constante. Ajouter une place cesse d’être de l’ingénierie et devient de la configuration : attribuez un type et un mode de dérivation d’adresse, et les mathématiques existantes en calculent le prix, le routeur existant en assure le règlement, le registre existant l’apprend.

**L’affirmation centrale de la conception :** chaque place par laquelle BlazePhoenix route est une paramétrisation de l’une des fonctions de sortie d’une petite famille — quatre familles opérationnelles dans une taxonomie à six types (§4) — détenues une seule fois, dans une unique bibliothèque sans état, et jamais redéfinies. Ajouter une place signifie attribuer un type et un mode de dérivation, pas écrire du nouveau code de calcul de prix.

### §1.2 L’échappatoire off-chain

L’architecture d’agrégateur dominante cote les routes sur une flotte de serveurs, organise une enchère entre solveurs off-chain, indexe la liquidité des pools avec des bots keepers et transmet le résultat à un contrat de règlement on-chain minimal — souvent derrière un proxy évolutif dont la clé d’administration peut remplacer purement et simplement la logique. Un service web produit le prix, une base de données détient le graphe de liquidité, un opérateur privilégié peut changer les règles, et la chain devient une place de règlement pour des décisions prises ailleurs, par un logiciel que l’utilisateur ne peut ni voir ni vérifier.

Cet arrangement réintroduit exactement la confiance que la technologie devait supprimer. Un devis off-chain peut différer de l’exécution on-chain, et l’utilisateur ne peut pas prouver qu’il n’aurait pas dû en différer. Un keeper peut s’éteindre et le routeur devient aveugle. Un administrateur de proxy peut, en une seule transaction, transformer un contrat honnête en contrat hostile.

| Aspect | Agrégateur résolu off-chain | BlazePhoenix-Dex |
|---|---|---|
| Cotation | Serveurs off-chain ou enchère de solveurs | Un seul répartiteur on-chain, partagé par l’aperçu et par l’exécution dans le cadre |
| Découverte de liquidité | Des bots keepers indexent et poussent des listes | Registre Auto-Réparateur — la liquidité apprise en l’échangeant — plus découverte déterministe |
| Sélection de la route | Optimiseur off-chain propriétaire | Solver on-chain, reproductible par chacun à partir de l’état public |
| Surface de mise à niveau | Proxy contrôlé par un administrateur | Core sans état ; pas de proxy, pas de chemin de mise à niveau, pas de `selfdestruct` |
| Pouvoir privilégié sur les fonds | Souvent présent | Le Router ne détient rien au repos ; aucune fonction ne déplace le capital de l’utilisateur |
| Cohérence entre chains | Logique backend propre à chaque chain | Un seul bytecode par chain |
| Hypothèse de confiance | L’honnêteté de l’opérateur | Le minimum propre à l’utilisateur, que le contrat refuse d’accepter s’il vaut zéro |

BlazePhoenix rejette les deux moitiés de ce marché. Il refuse la surface d’intégration linéaire en montrant que cette surface n’est pas linéaire, et il refuse l’échappatoire off-chain en gardant toute la décision — découverte, cotation, notation, routage et règlement — à l’intérieur de contrats que chacun peut lire et qui produisent la même réponse sur chaque chain. Le coût est réel : coter un pool concentré on-chain coûte plus cher que de le lire dans une base de données. Ce que ce coût achète, c’est la suppression d’une classe de défaillance — la divergence silencieuse et improuvable entre ce qu’un serveur a promis et ce que la chain a livré. Chaque décision de conception qui suit est une application de ce compromis.

## §2 La réponse et la discipline

**Ce qui choisit est ce qui échange.**

BlazePhoenix-Dex a un point d’entrée qui prend six valeurs — les deux tokens, le montant, votre minimum, un destinataire, une échéance — et établit la route à l’intérieur de la transaction qui l’exécute, à partir des réserves des pools, qui sont de l’état public : quelles places, combien pour chacune, dans quel ordre. Parce que la route a été dérivée de l’état public par du code public, chacun peut la re-dériver. Parce que le chiffre qui engage au règlement est re-dérivé par le même répartiteur, dans le même cadre, à partir du même état en direct, le devis et l’exécution ne sont pas deux artefacts à réconcilier ; ils sont un seul calcul, et ce qui sépare un aperçu d’une exécution, c’est le temps, pas un second modèle.

Trois autres points d’entrée acceptent une route précalculée depuis le calldata, pour les appelants qui veulent le chemin le moins cher et sont prêts à fournir leur propre réponse ; la même mécanique de mesure, de plancher et de règlement s’applique aux quatre portes (§11). Nous n’affirmons pas que l’approche off-chain est fausse, et nous n’affirmons pas être moins chers. Nous revendiquons une autre classe de garantie — auditable plutôt que promise — pour l’utilisateur qui la veut et qui paiera du gas pour l’obtenir.

Quatre décisions de cette conception sont, pour autant que nous puissions l’établir, inhabituelles dans le domaine, et chacune est énoncée avec la limite qui la maintient honnête.

- **Les adresses sont dérivées, pas récupérées.** Partout où le déploiement d’une famille de places est reproductible, l’adresse d’un pool est calculée par l’arithmétique CREATE2 — un théorème que chacun peut revérifier à partir de trois entrées publiques — plutôt que lue dans le stockage d’une factory ; là où l’arithmétique n’est pas fiable, le protocole demande, et un pool obtenu sur demande prouve sa propre paire avant d’être listé (§7).
- **L’état de routage d’un pool tient en un mot.** Le Monoslot regroupe tout ce qu’il faut pour classer une place dans un seul slot de 256 bits et ne contient délibérément aucun prix, de sorte que la mémoire du registre peut biaiser le choix des pools interrogés mais est structurellement incapable de fixer le prix d’un échange (§8–§9).
- **Le plancher est re-dérivé de l’échange qui a réellement eu lieu.** Le Router mesure l’impact réalisé de chaque jambe et la concentration réalisée de chaque hop à l’intérieur du cadre d’exécution et recalcule son plancher à partir de ces mesures, de sorte que les champs fournis par l’appelant peuvent resserrer la protection et jamais la relâcher (§11).
- **La découverte est aveugle au déployeur là où les places n’ont pas d’adresse.** Pour le singleton Uniswap V4, les identifiants de pool sont dérivés de paramètres publics puis prouvés actifs contre le propre stockage du manager — une preuve que personne d’autre ne peut écrire (§5).

Aucune de ces décisions ne supprime le risque résiduel que porte tout agrégateur — l’état peut bouger entre la signature et l’inclusion — et c’est pourquoi le minimum que vous fixez vous-même est obligatoire, et définitif : chaque porte refuse un minimum nul avec `RouterE(10)` avant qu’un token ne bouge.

### §2.1 Conception guidée par les invariants

Derrière la réponse se trouve une méthode. Nous l’appelons conception guidée par les invariants : le protocole est spécifié non comme une suite d’opérations mais comme un ensemble de propriétés qui doivent tenir dans chaque état atteignable, le code étant organisé pour que ces propriétés soient structurelles — imposées par la forme du code, non par la vigilance du programmeur.

La plupart des logiciels sont écrits de façon impérative, et leur correction est un espoir émergent. Pour un logiciel ordinaire, c’est acceptable, parce que les défaillances sont récupérables. Sur une blockchain publique, rien de cela ne tient : le code est immuable une fois déployé ; il détient directement de la valeur ; il s’exécute dans un environnement adverse où chaque cas limite est traqué ; et une seule erreur arithmétique est une perte irréversible, pas un plantage.

Un invariant est une propriété vraie quelle que soit la façon dont le système a atteint son état. *Le Router ne détient aucun fonds entre les transactions.* *Une paire du registre détient au plus seize pools, et l’éviction conserve les seize plus aptes.* *Un règlement qui n’a payé aucuns frais de protocole n’existe pas.* Trois principes placent de tels énoncés au centre :

| Principe | Énoncé | Où il apparaît |
|---|---|---|
| Calculer, ne pas faire confiance | Là où une valeur peut être dérivée ou prouvée, ne jamais la récupérer ni la supposer. | 𝒟 calcule les adresses des pools ; le Router re-dérive la base des frais et le plancher dans le cadre à partir de soldes mesurés. |
| Une seule source de vérité | Chaque primitive mathématique est définie une seule fois et importée ; jamais réénoncée. | Le Core détient toutes les mathématiques de prix, de dérivation, de plancher et de notation ; Hub, Solver, Router et Quoter les importent et aucun ne redéfinit une primitive. Un registre des quantités partagées (§15) attribue une note à chaque quantité ayant deux producteurs. |
| Échouer fermé | Quand une propriété ne peut pas être garantie, s’arrêter plutôt que poursuivre sur une supposition. | Le Solver ne renvoie aucune route plutôt qu’une route fabriquée (`SolverE(5)`) ; le solveur stable renvoie zéro plutôt qu’un devis saturé (§6) ; la garde de cohérence du registre rejette par un revert une place structurellement impossible avant que le moindre état ne soit écrit (§9). |

Un quatrième principe régit la garde : **aucun chemin privilégié vers les fonds.** Le Solver et le Quoter sont des contrats du chemin de lecture ; le Router est le seul contrat qui déplace de l’argent, il le déplace de façon atomique sous la borne propre à l’utilisateur, et chaque pouvoir administratif qui pourrait le rediriger peut être abandonné définitivement par la Porte à Sens Unique (§14).

### §2.2 Pourquoi cette discipline convient à ce milieu

Chaque propriété de l’environnement qui rend le code impératif dangereux est une propriété que le code guidé par les invariants tourne à son avantage. L’immuabilité récompense la prouvabilité : le code déployé ne peut pas être corrigé, donc la valeur d’une preuve préalable est d’autant plus élevée. La composabilité récompense une source unique de vérité : une fonction de prix peut être appelée dans un vault que ses auteurs n’ont jamais anticipé, et avec un seul Core chaque appelant obtient la même réponse. L’adversité récompense l’échec fermé : un attaquant n’a besoin que d’un seul chemin que l’auteur n’a pas envisagé, et un système qui s’arrête quand ses hypothèses tombent lui refuse l’improvisation. La valeur en jeu récompense la lisibilité : un protocole exprimé en cinq opérateurs nommés sur un seul noyau mathématique peut être vérifié ligne par ligne par des personnes qui ne l’ont pas écrit.

La discipline n’est pas gratuite, et le compromis est délibéré : prouver une borne de dépassement de capacité prend plus de temps que de l’écarter par hypothèse ; faire passer chaque produit par une multiplication-division en pleine précision coûte du gas qu’une multiplication naïve ne coûterait pas ; dériver une adresse coûte un travail d’ingénierie que la récupérer épargne. C’est plus de coût avant le déploiement, en échange de la suppression des défaillances qui ne peuvent pas être rattrapées après.

### §2.3 Conception Métrologique : un morphisme, des coordonnées mesurées, des entrées monotones

La conception guidée par les invariants dit ce qui doit toujours tenir. Trois règles supplémentaires disent comment est *façonné* le code qui le fait respecter, et ensemble elles forment une méthode que nous n’avons trouvée nommée nulle part ailleurs dans le domaine ; ce document la nomme donc : **Conception Métrologique** — construire un contrat comme un *instrument*. Sa thèse en une ligne : *le code qui croit est une surface d’attaque ; le code qui mesure est une défense.*

**Un morphisme, évalué en un point.** L’agrégateur ne contient pas une intégration par place ; il est l’évaluation d’un seul morphisme de prix paramétré en un point de l’espace (forme de place, chain, modèle de frais). Exprimés sous forme de branches, une demi-douzaine de types de places actifs, chacun avec ses propres conventions de frais, se multiplient en centaines de combinaisons deux à deux — bytecode multiplicatif, surface d’audit multiplicative, dérive multiplicative. Exprimée sous forme de morphisme, la même couverture tient en une poignée de *formes d’état*, un seul Core sans état, des conventions de frais portées dans leurs unités natives et des tables d’enregistrement — et le bytecode croît *additivement* : une nouvelle place est une ligne, pas une branche. La règle qui protège cette réduction est `KIND → SHAPE` : une famille de places est une *donnée* — un type attribué à une forme existante et un mode de dérivation — jamais un nouveau chemin de code qu’une entrée adverse pourrait sélectionner. Le Core énonce les formes sous forme de table de bits (`THETA_ATTR`, §4), de sorte que « ce type lit-il des réserves ? » se résout par un décalage et un AND, et il n’existe aucune branche par défaut que quiconque pourrait oublier. Cette réduction a un frein : il n’existe pas de formule universelle unique. Deux formes closes sont évaluées exactement — produit constant et liquidité concentrée — et pour chaque courbe dont la réplication serait un risque, c’est le bytecode de la place elle-même qui est interrogé (§6). Le morphisme réduit l’*appartenance*, jamais la *vérification*.

**Les coordonnées sont mesurées, jamais crues.** Le contrat n’agit jamais sur une valeur modélisée, nominale ou fournie par l’appelant là où une mesure on-chain existe — et aucune donnée sans permission ne choisit jamais quel code s’exécute. Les données fournies par une partie non fiable — une route, des frais déclarés, une profondeur revendiquée, un id de pool — sont des *coordonnées* : des endroits où pointer l’instrument, que le contrat lit ensuite lui-même, en échouant fermé. Chaque défense porteuse de ce document en est une illustration : le solde mesuré après le prélèvement au lieu du montant nominal (§11), la base des frais tirée d’un delta de solde mesuré au lieu du calldata (§13), la bande de crédibilité ancrée sur une médiane, pondérée par la profondeur, des profondeurs mesurées au lieu d’un solde brut qu’un don peut gonfler (§10), l’écrêtage de capacité calé sur les avoirs mesurés (§10), la barrière de frais V4 qui lit l’état en direct au lieu de faire confiance à une sentinelle (§5), la découverte par preuve d’existence qui lit le propre stockage du singleton (§5), et un pool obtenu sur demande qui prouve ses propres `token0`/`token1` avant que la découverte ne le liste (§7). Le dépôt publie la classification de chaque champ que l’appelant peut écrire selon cette règle : 23 champs, 14 confirmés par une observation, 5 de pilotage, 4 déclarés avec leur justification (§15).

**Là où la mesure s’arrête, la monotonie commence.** Certains chiffres ne peuvent être ni mesurés ni re-dérivés — le plancher d’un appelant, l’échelle déclarée d’un appelant, le minimum propre à l’appelant. Pour ceux-là, la conception accepte le chiffre et façonne la fonction de sorte que mentir ne puisse nuire qu’au menteur : le plancher attesté de la route ne peut que *relever* le plancher appliqué (§11) ; le devis attesté d’une jambe est relevé au devis mesuré quand il est inférieur à celui-ci (§11) ; la mise à l’échelle déclarée est plafonnée par l’arrivée mesurée, de sorte qu’une surestimation ne peut pas router des fonds qui n’ont jamais existé ; et le seul chiffre que le protocole ne peut pas choisir — votre minimum — est obligatoire. Une entrée monotone ne demande aucune confiance ; elle peut donc être calculée off-chain, par n’importe qui, gratuitement, et portée dans le calldata — c’est ainsi que ce protocole déplace du travail off-chain *sans déplacer la confiance off-chain*, et c’est pourquoi le Router peut offrir une porte de résolution et trois portes calldata avec les mêmes garanties.

La Conception Métrologique régit aussi l’ingénierie autour de l’artefact, parce qu’un instrument ne vaut que par son étalonnage. Les marges de sécurité sont chiffrées à partir de risques mesurés : la barrière de répartition est fixée à 25 ppm parce que le seuil de rentabilité d’une jambe a été mesuré à 3 ppm (§10) ; une décote de planification qui pesait 27× son risque mesuré a été retirée (§10). Les comparaisons externes portent les barres d’erreur de leur méthode ou ne sont pas publiées (§17). Et l’appareil de mesure est lui-même mesuré : chaque instrument d’assurance a été étalonné contre un défaut déjà connu par une autre voie avant que son chiffre ne soit publié (§15). Voilà toute la théorie ; le reste de ce document en est la pratique.

## §3 La Méta-Équation et le budget de dépendances

Tout l’agrégateur peut se lire comme la maximisation d’une seule expression. Étant donné un token d’entrée $t_{\mathrm{in}}$, un token de sortie $t_{\mathrm{out}}$ et un montant $x$, le Solver cherche parmi les routes admissibles celle qui maximise la valeur nette reçue, sous réserve d’un plancher de faisabilité et d’une barrière anti-arnaque :

$$
R^{\star} \;=\; \mathrm{arg\,max}_{\;R \,\in\, \mathcal{R}_{\mathcal{D}}(t_{\mathrm{in}},\, t_{\mathrm{out}},\, x)} \;\; \mathrm{net}(R)\cdot\prod_{\ell\in R}\hat{\Psi}(p_{\ell})\cdot \mathbf{1}[\,\mathrm{net}(R) \ge \Phi(R)\,]\cdot \Xi(R)
$$
^e1

$$
\mathrm{net}(R) \;=\; \sum_{\ell\in R}\Omega(p_{\ell},\, x_{\ell})\;-\;f_{\mathrm{proto}}
$$

Ici, $\mathcal{R}_{\mathcal{D}}$ est l’espace des routes sur les pools localisés par la Dérivation Déterministe 𝒟 et par le registre ; $\Omega(p_{\ell}, x_{\ell})$ est la sortie du répartiteur pour la jambe $\ell$ qui pousse $x_{\ell}$ à travers le pool $p_{\ell}$ ; $f_{\mathrm{proto}}$ représente les frais de protocole ; $\hat{\Psi}(p_{\ell})$ est la vitalité normalisée de la jambe ; $\Phi(R)$ est le plancher Iron-Law pour l’impact et la concentration mesurés de la route ; et $\Xi(R) \in \{0, 1\}$ est le projecteur anti-arnaque.

| Opérateur | Nom | Rôle dans l’équation | Section |
|---|---|---|---|
| Ω | Répartiteur Octuple | Fixe le prix de chaque jambe — une branche par type de pool — et rapporte une profondeur libellée en tokens pour l’allocateur de répartition | §4 |
| 𝒟 | Dérivation Déterministe | Localise les pools qu’utilise la route, par adresse calculée là où la famille le permet, par id de pool prouvé pour le singleton V4 | §5, §7 |
| Φ | Plancher Iron-Law | Projette la route sur l’ensemble réalisable : un plancher sous la sortie nette, re-dérivé dans le cadre d’exécution | §11 |
| Ψ | Champ de Vitalité | Pondère chaque pool par sa qualité acquise, lue dans son état on-chain compacté | §9 |
| Ξ | projecteur anti-arnaque | Évalue à exactement zéro toute route qu’il serait dangereux de présenter | §5, §14 |

La factorisation est délibérée. Ω indique *combien un pool rend* ; Ψ indique *quelle confiance lui accorder* ; Φ indique *si la route est admissible* ; 𝒟 indique *où se trouve le pool* ; Ξ indique *si la route peut être montrée sans danger*. La forme produit signifie qu’une seule jambe défaillante fait s’effondrer le score de la route — une jambe qui cote zéro, ou un pool sans réputation, annule le produit au lieu de le décoter — et l’indicatrice met strictement à zéro toute route dont la sortie nette tombe sous le plancher. Pas de pénalités douces, pas de poids ajustables, aucune route presque admissible qui se faufile.

![FIG-1 — La Méta-Équation comme pipeline du Solver : candidats issus de 𝒟 et du registre, classés et tronqués par Ψ, cotés par Ω, alloués entre les jambes, vérifiés contre Φ et Ξ ; le Router réévalue Ω et Φ dans le cadre d’exécution.](https://blazephoenix.xyz/whitepaper/figs/fr/fig-1.png)

Sur la chain, le Solver réalise l’équation sous forme de pipeline (`findBestRoutePlan`) : les candidats par paire sont rassemblés par le registre et, quand le registre n’est pas à jour, par 𝒟 ; classés et tronqués par Ψ à `MAX_CANDIDATES = 8` ; sondés et cotés par Ω ; filtrés par une bande de crédibilité ; alloués entre les jambes ; et la route assemblée porte le plancher qu’atteste Φ. Le Router réévalue ensuite Ω et Φ sur les montants réellement arrivés. L’affirmation que fait l’équation — celle à laquelle un auditeur peut tenir le code — est qu’aucune étape de ce pipeline ne consulte quoi que ce soit en dehors des cinq opérateurs. Si une quantité n’est pas un argument de l’un des cinq, elle ne peut pas influencer la route : ni le marketing d’une place, ni une réputation off-chain, ni une préférence de l’opérateur.

### §3.1 Le budget de dépendances

Plutôt que des adjectifs — *décentralisé*, *sans serveur*, *sans confiance* ont cessé de porter de l’information — voici la liste vérifiable : tout ce qui doit exister, et continuer de fonctionner, pour qu’un échange soit réglé.

| Dépendance | Agrégateur typique résolu off-chain | BlazePhoenix-Dex |
|---|---|---|
| Un service qui calcule la route | Requis | Non utilisé — le Solver est une fonction `view` que n’importe quel nœud sert |
| Un oracle ou un flux de prix | Variable | Aucun — aucun appel à un flux n’existe dans les cinq contrats |
| Un réseau de solveurs ou une enchère | Courant | Aucun |
| Un keeper, un cron ou un relayer | Variable | Aucun — la vitalité décroît par arithmétique lors de sa lecture suivante ; le registre apprend des échanges ordinaires |
| Un proxy évolutif | Courant | Aucun — pas de proxy, pas de `selfdestruct`, pas d’initialiseur ; les seuls `delegatecall` sont ceux du compilateur lui-même vers la bibliothèque publique Core, fixés à l’édition de liens |
| Une interface hébergée | En pratique, oui | Simple commodité |

La dernière ligne est celle où la plupart des budgets échouent discrètement. L’interface hébergée est une commodité, pas une dépendance : `swapBestExactIn` prend six valeurs qu’une personne peut assembler à la main, et un intégrateur, un script ou le front-end d’un concurrent peut l’appeler sans aucune relation avec nous, de quelque nature que ce soit. Si tous les serveurs que ce projet exploite disparaissaient ce soir, les contrats coteraient, routeraient et régleraient demain exactement comme aujourd’hui. Le prix de cette propriété est que l’appelant paie la recherche en gas ; le budget ci-dessus est la liste de ce dont ce gas vous libère.

L’immuabilité joue aussi dans l’autre sens. Un défaut ne peut pas être corrigé — le prix d’un contrat que l’on audite une seule fois — et c’est pourquoi les défauts sont, à la place, divulgués et récompensés par des bounties (§21).

### §3.2 Trois limites

Le budget a trois limites, énoncées à côté de l’affirmation qu’elles bornent. L’interface est hébergée, comme celle de tout le monde, et un front-end compromis est la plus grande exposition du modèle de menace : le contrat honore tout minimum non nul qui lui est remis, donc le minimum que vous fixez est la seule borne qu’aucune interface ne peut vous retirer. La découverte des places lit un état que des transactions antérieures ont écrit — public et sans permission, mais pas créé de toutes pièces au moment de l’échange. Et les pouvoirs administratifs existent jusqu’à leur renonciation par la Porte à Sens Unique ; après la renonciation, le niveau de contrôle — pause, rôles, trésoreries, câblage, suppressions — disparaît pour toujours, et seul subsiste le niveau curateur, à croissance seule (§14). Cœur sans état, fonctionnement sans serveur, gouvernance à croissance seule : le tableau est l’affirmation ; les mots en sont l’ombre.


## §4 Le Répartiteur Octuple Ω

*En clair :* une fonction qui sait fixer le prix de chaque type de pool. *Précisément :* `universalQuote(QuoteCtx, amountIn)` dans le Core est le répartiteur de devis du Solver et de l’aperçu du Quoter ; il renvoie la sortie attendue et une profondeur libellée en tokens pour l’allocateur de répartition, et un pool qu’il ne sait pas coter renvoie zéro et est écarté avant que quoi que ce soit ne soit envoyé. Le Router évalue les mêmes primitives du Core par jambe dans son propre cadre (`_hopScaleImpactAndQuote`), une fonction sœur conservée pour des raisons de gas et de profondeur de pile, et la Passe Exacte du Quoter pose encore une autre question (§12) ; les trois sont consignés ensemble dans le registre des quantités partagées du dépôt, et leur accord est verrouillé par des tests nommés plutôt que supposé (§15). Le nom maison est hérité de l’énumération à huit types avec laquelle le répartiteur a été livré ; l’énumération s’est depuis enrichie d’un neuvième membre et en a cédé trois à une excision délibérée. La numérotation conserve ses trous : un Monoslot enregistré hier doit se décoder à l’identique pour toujours.

### §4.1 Six types, quatre familles, une table d’attributs

| Type | Constante | Famille | Branche de prix | Attributs Θ | Barème de gas (Solver) |
|---|---|---|---|---|---|
| 0 | `KIND_V2` | Produit constant (Uniswap V2, Sushi, forks) | forme close `outV2` sur `getReserves` | réserves · paire vérifiable | 90,000 |
| 1 | `KIND_V3` | Liquidité concentrée (Uniswap V3 et forks de forme V3) | forme close `outV3` sur `slot0` | concentré au pool · paire vérifiable | 110,000 |
| 4 | `KIND_V4` | Singleton Uniswap V4 | forme close `outV3` sur `extsload` | concentré dans le singleton | 180,000 |
| 5 | `KIND_SOLIDLY` | Solidly (Aerodrome, Velodrome, forks), stable et volatile | interroger le pool `getAmountOut` ; courbe répliquée en repli | réserves · paire vérifiable | 90,000 |
| 6 | `KIND_ALGEBRA` | Algebra (classe Camelot), frais dynamiques | forme close `outV3` sur `globalState` | concentré au pool · paire vérifiable | 110,000 |
| 8 | `KIND_V4_NATIVE` | Pool V4 détenant la monnaie native de la chain | même branche que le type 4 | concentré dans le singleton | 215,000 |

Six types, quatre familles opérationnelles : produit constant {0}, concentré {1, 6}, Solidly {5}, et le singleton V4 {4, 8}. La colonne des attributs n’est pas de la prose : c’est la table de bits `THETA_ATTR = 0x040A9400A9` du Core, quatre bits par type — *lit des réserves*, *concentré à l’adresse du pool*, *concentré dans le singleton*, *expose `token0()`/`token1()`* — de sorte que chaque question d’appartenance dans les cinq contrats (« ce type lit-il des réserves ? ») se résout par un seul décalage et un seul AND (`kindHas`, `kindHasAny`), et un type sans aucun bit échoue fermé, sans branche par défaut que quiconque pourrait oublier. La colonne du gas est une seconde table, `THETA_GAS`, lue uniquement par `estGas` du Solver, en unités de 5,000 gas par jambe.

**Les types 2, 3 et 7 sont absents par excision délibérée.** La famille pondérée (3) n’a jamais produit de devis routable. Les deux familles Curve (2, 7) fonctionnaient et ont pourtant été retirées, lors du durcissement de la frontière de confiance d’août 2026, parce que leur prise en charge exigeait le seul `approve` de token du Router, le seul chiffre de profondeur attesté par un appelant au lieu d’être mesuré sur un pool, et la seule exception dans la preuve d’authenticité de paire du Hub — trois surfaces d’attaque, chacune la dernière de sa classe. Les numéros retirés ne sont jamais réattribués : `decodeKind` lit le type dans les bits du Monoslot, donc réutiliser 2 réinterpréterait tous les pools jamais enregistrés sous ce numéro, et une garde de CI fait échouer le build si un symbole excisé réapparaît.

**Le type monnaie native (8) existe parce qu’un invariant s’y brise, et nulle part ailleurs.** L’hypothèse de travail du Router — chaque actif est un ERC-20 doté de `balanceOf` — échoue exactement quand la monnaie d’un pool V4 est l’actif natif de la chain, et encoder cette exception comme un *type* la rend visible dans l’enregistrement du pool, portée dans le plan de route, et repérable par un auditeur avec grep. Le calcul du prix n’a besoin d’aucune branche : l’identifiant du pool dérive des deux monnaies triées, et `address(0)` est triée en premier par construction. Le type gagne sa place empiriquement : selon une mesure du 2026-08-13 sur les chains servies, 62.9 % de la liquidité libellée en ETH à l’intérieur de V4 se trouvait dans des pools natifs (Arbitrum 99.6 %, Optimism 95.0 %, Base 48.9 %).

### §4.2 Les formes closes

**Produit constant.** Les réserves satisfont $x \cdot y = k$ et des frais $f$ en points de base sont prélevés sur l’entrée. Le Core calcule

$$
y \;=\; \mathrm{outV2}(x,\, r_{\mathrm{in}},\, r_{\mathrm{out}},\, f) \;=\; \left\lfloor \frac{x\,(\mathrm{BPS}-f)\; r_{\mathrm{out}}}{\,r_{\mathrm{in}}\cdot \mathrm{BPS} \;+\; x\,(\mathrm{BPS}-f)\,} \right\rfloor
$$
^e2

sans aucune division avant la dernière étape, de sorte que le seul arrondi est la partie entière du dernier quotient, et des frais de `BPS` ou plus rendent le pool impossible à coter (il renvoie zéro). Les frais qui entrent dans cette formule sont `effV2Fee(declared)` : des frais déclarés nuls ou supérieurs à `V2_FEE_CEILING_BPS = 100` sont remplacés par les 30 bps canoniques, de sorte qu’un enregistrement ne peut pas dégonfler son propre devis — et avec lui le plancher que ce devis ancre — en déclarant des frais invraisemblables ; la même règle est reproduite sur le chemin d’exécution, de sorte que les deux côtés de l’échange fixent le prix avec un seul et même chiffre.

**Exemple détaillé 1 — un devis à produit constant.** Un pool détient $r_{\mathrm{in}} = 1{,}000{,}000$ USDC et $r_{\mathrm{out}} = 500$ WETH aux 30 bps canoniques ; un trader vend $x = 10{,}000$ USDC. Dans les unités propres du pool (USDC a 6 décimales, WETH 18) $x = 10^{10}$, $r_{\mathrm{in}} = 10^{12}$, $r_{\mathrm{out}} = 5 \times 10^{20}$ :

1. Entrée après frais : $x(\mathrm{BPS}-f) = 10^{10} \times 9{,}970 = 9.97 \times 10^{13}$.
2. Numérateur : $9.97 \times 10^{13} \times 5 \times 10^{20} = 4.985 \times 10^{34}$.
3. Dénominateur : $10^{12} \times 10^{4} + 9.97 \times 10^{13} = 1.00997 \times 10^{16}$.
4. Sortie : $\lfloor 4.985 \times 10^{34} / 1.00997 \times 10^{16} \rfloor = 4.935790 \times 10^{18}$ wei, soit **4.935790 WETH** (à six décimales ; le contrat arrondit le dernier quotient par défaut, au wei près).

Au prix spot de 0.0005 WETH par USDC, les mêmes 10,000 USDC rapporteraient 5 WETH ; l’exécution ressort 1.28 % sous le spot, dont 0.30 % de frais. La mesure d’impact fondée sur les réserves que le Core calcule pour cette jambe est $\mathrm{impactV2Bps} = \lceil x \cdot \mathrm{BPS} / (r_{\mathrm{in}} + x) \rceil = \lceil 10^{14} / 1.01 \times 10^{12} \rceil = 100$ bps — le chiffre que consomme le plancher Iron-Law au §11.

**Liquidité concentrée.** Les pools de forme V3 ne stockent aucune réserve ; ils stockent une racine carrée du prix $\sqrt{P}$ en virgule fixe Q64.96 et une liquidité active $L$. À l’intérieur d’un tick, pour token0 → token1 avec des frais $f$ en parties par million et $x_{f} = \lfloor x\,(10^{6} - f)/10^{6} \rfloor$ :

$$
\sqrt{P}\,' \;=\; \frac{L\,\sqrt{P}}{\,L \;+\; x_{f}\,\sqrt{P}/Q_{96}\,},
\qquad
y \;=\; \frac{L\left(\sqrt{P}-\sqrt{P}\,'\right)}{Q_{96}}
$$
^e3

et pour token1 → token0, $\sqrt{P}\,' = \sqrt{P} + x_{f} Q_{96}/L$ et $y = L\,Q_{96}(\sqrt{P}\,' - \sqrt{P})/(\sqrt{P}\,\sqrt{P}\,')$, la division étant scindée en deux étapes. Le calcul naïf déborde : $L \cdot \sqrt{P}$ peut atteindre $2^{288}$. Le Core ne le forme jamais — chaque produit est factorisé par un `mulDiv` en pleine précision (intermédiaire de 512 bits, résultat de 256 bits), de sorte que chaque étape tient de façon prouvée, et une étape qui déplacerait le prix dans le mauvais sens ou jusqu’à zéro renvoie zéro plutôt qu’un nombre. Les pools Algebra (type 6) partagent la branche et diffèrent sur un point, que la branche gère : leur état et leurs frais dynamiques résident dans `globalState`, lu une seule fois (§5.4).

La formule est exacte à l’intérieur d’un seul tick et n’est pas exacte à travers les limites de tick, où la forme mono-tick surestime. Le répartiteur ne réplique pas l’arithmétique de franchissement de ticks : répliquer les mathématiques internes d’une place est un risque structurel, car toute divergence entre la copie et l’original se manifeste par un devis au prix erroné. Deux choses en tiennent lieu. Le Core comporte un écrêtage de bord (`sqrtBoundary`) qui tronque le pas au bord de la plage courante — exact pour ce qui tient dans la plage et strictement inférieur pour le reste, jamais supérieur — et la conception le place du côté de la *promesse*, pas du classement, parce qu’écrêter seulement les familles qui ont des ticks reviendrait à comparer les places selon deux conventions. Dans l’arbre que mesure ce document, l’écrêtage est défini mais pas encore câblé : `Router._v4LegQuote` et le bras de classement du Core appellent tous deux la forme mono-tick non écrêtée, de sorte qu’une jambe concentrée qui sort de sa plage n’est bornée que par le plancher par jambe du §11.3 et le budget de hops, avec le plancher Iron-Law comme filet de sécurité. Et à l’exécution, le plancher Iron-Law est appliqué contre un devis que le Router re-dérive dans le cadre à partir des montants réellement arrivés, de sorte qu’une surestimation à une limite de tick coûte à une route la précision de son classement, jamais à l’utilisateur son plancher. La Passe Exacte du Quoter (§12) donne à un intégrateur le chiffre du pool lui-même quand il en veut un.

**Profondeur.** Chaque branche renvoie aussi une profondeur en unités de token — le plus petit côté de réserve pour les familles à réserves (`shortSide18`), et pour les familles concentrées la réserve virtuelle $L/\sqrt{P}$ ou $L\sqrt{P}$ que $L$ représente au prix courant (`depthFromL18`), de sorte qu’un pool V3 et un pool V2 de même profondeur sont comparés à égalité dans l’allocateur au lieu de différer d’un facteur $\sqrt{P}$.

### §4.3 Deux unités de frais, portées nativement

Les places à produit constant et Solidly expriment leurs frais en points de base ; les places concentrées, en parties par million. Le répartiteur porte les frais de chaque place dans leur unité native et ne fait jamais de conversion croisée, ce qui supprime une classe de défaillance d’arrondi au prix de deux constantes au lieu d’une. Deux familles de places ne connaissent leurs frais qu’au moment de l’exécution — les pools V4 à frais dynamiques et Algebra — et toutes deux sont traitées en mesurant les frais à partir de la même lecture d’état qui fournit le prix (§5.4).

### §4.4 Un répartiteur, deux surfaces

| Type | Aperçu du Quoter | Dans le cadre (plan du Solver ; re-dérivation du Router) |
|---|---|---|
| V2 (0) | forme close `outV2` | forme close `outV2` |
| V3 / Algebra (1, 6) | forme close `outV3` sur `slot0` / `globalState` ; frais en direct d’Algebra mesurés à partir de la même lecture | idem |
| Solidly (5) | interroger le pool ; courbe répliquée × 9,800 / 10,000 en repli | idem |
| V4 / V4 natif (4, 8) | forme close `outV3` via `extsload`, barrière de frais à échec fermé (E19) | idem |

Ce qui sépare un aperçu d’une exécution est donc le temps : l’état qui a bougé entre le bloc de l’aperçu et le cadre d’exécution. Le §12 mesure exactement cet écart sur 240 échantillons.

## §5 Uniswap V4 de bout en bout

Uniswap V4 n’est pas un nouveau contrat de pool ; c’est une nouvelle forme de place, et elle invalide quatre hypothèses sur lesquelles repose chaque routeur construit entre 2020 et 2024. Les pools n’ont pas d’adresse — ce sont des entrées dans le stockage d’un unique singleton, indexées par un hash. Du code arbitraire s’exécute à l’intérieur du swap — un hook s’exécute en des points définis du règlement que le routeur est en train d’effectuer. Les frais ne sont pas nécessairement constants. Et les pools peuvent détenir la monnaie native de la chain, qui n’a pas de `balanceOf`. Cette section répond structurellement aux quatre, de sorte qu’une jambe V4 est — pour le Solver, pour les planchers et pour le trader — indiscernable de toute autre jambe.

### §5.1 Localiser un pool qui n’a pas d’adresse : dériver, puis prouver

La Dérivation Déterministe 𝒟 calcule des adresses (§7), mais un pool V4 n’a pas d’adresse à calculer. Ce qu’il possède, c’est un identifiant — le hash keccak de sa `PoolKey` — si bien que le mode 9 de la table de dérivation dérive des *poolIds* plutôt que des adresses, puis fait ce qu’aucune dérivation d’adresse ne peut faire : il prouve que le pool existe, en lisant le propre stockage du PoolManager pour cet id et en exigeant un prix en racine carrée non nul et une liquidité non nulle. Personne ne peut falsifier cette preuve, car personne d’autre que le manager n’écrit ce stockage. Un id fabriqué se résout en zéros et est écarté.

**Exemple détaillé 4 — un identifiant de pool V4 à partir de sa clé.** L’id canonique est $\mathrm{poolId} = \mathrm{keccak256}(\mathrm{abi.encode}(c_{0}, c_{1}, \mathrm{fee}, \mathrm{tickSpacing}, \mathrm{hooks}))$ (`computeV4PoolId`), soit cinq mots de 32 octets. Prenez le pool ETH natif / USDC sur Base au palier de 0.05 % : $c_{0} = \mathtt{0x0000…0000}$ (la monnaie native est triée en premier), $c_{1} = \mathtt{0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913}$ (USDC), fee $= 500$, tickSpacing $= 10$, hooks $= \mathtt{0x0}$.

1. `abi.encode` produit 160 octets : les deux monnaies complétées à gauche jusqu’à 32 octets chacune, puis `500`, puis `10` (un `int24`, étendu en signe à 32 octets), puis le hook nul.
2. $\mathrm{poolId} = \mathtt{0x96d4b53a38337a5733179751781178a2613306063c511b78cd02684739288c0a}$.
3. Le champ `pool` du registre pour une ligne V4 est formé des 160 bits de poids faible de l’id, $\mathtt{0x781178a2613306063c511b78cd02684739288c0a}$ — une adresse sans code, ce qui explique pourquoi on ne demande jamais `token0()` aux lignes V4, pourquoi on n’y lit jamais de solde, et pourquoi elles se prouvent plutôt par re-dérivation (§9).
4. La base de stockage du pool est $\mathrm{keccak256}(\mathrm{abi.encode}(\mathrm{poolId}, 6)) = \mathtt{0x13a3a697…73b2893d}$ : le slot 6 du manager est le mapping `pools`. Le Core lit le mot 0 à ce slot et le mot 3 à $\mathrm{base} + 3$ (`v4SqrtAndLiq`, sélecteur `extsload(bytes32)` = `0x1e2eaeaf`).
5. Le mot 0 regroupe $\sqrt{P}$ dans les bits [0, 160), le tick dans [160, 184), les frais de protocole dans [184, 208) et les frais LP dans [208, 232) ; le mot 3 porte la liquidité active dans ses 128 bits de poids faible. Si $\sqrt{P}$ ou $L$ est nul, le pool n’existe pas pour ce protocole.

Le pendant encapsulé de la même paire — $c_{0} = \mathtt{0x4200…0006}$ (WETH), même palier — a pour id $\mathtt{0x90333bb0…ebd943a0}$ : un pool différent, d’une profondeur différente, ce qui explique pourquoi le scan sonde les deux clés et sonde la clé native en premier (voir plus bas). La disposition de l’étape 5 n’est pas tirée de la documentation : le même pool, lu via cette disposition et via le contrat canonique `StateView`, renvoie les mêmes nombres sur un fork du mainnet, et c’est cette identité que cite le commentaire du Core.

**L’échelle des coûts.** Le scan qui produit les ids candidats est une échelle, le barreau le moins cher d’abord, car un balayage naïf de toutes les combinaisons de frais et d’espacement représenterait une facture de gas inacceptable sur le chemin de résolution :

| Barreau | Ce qu’il essaie | Coût quand il trouve |
|---|---|---|
| 1 | Le motif de paliers déjà appris pour cette paire de tokens | une sonde — le chemin en régime permanent |
| 2 | Une vérification de provenance avant de payer un scan à froid | une lecture |
| 3 | Les paliers canoniques d’Uniswap, groupés via `extsload(bytes32[])` | un appel pour le lot |
| 4 | Les paires `(fee, spacing)` que déclare la ligne d’enregistrement | borné par l’enregistrement |
| 5 | La grille du générateur de démarrage à froid — frais $= 10{,}000\,j$, espacement $= 100\,j$, $j$ décroissant à partir de `V4_GRID_MAX = 99` | au plus `V4_GRID_PROBES = 40` sondés |

Le scan entier s’arrête à `V4_CAP = 8` pools prouvés. Deux propriétés découlent de la construction plutôt que d’une vérification : un pool à hook ne peut jamais être émis par ce scan — il dérive des ids *sans hook*, et l’id sans hook d’un pool à hook n’existe pas dans le manager, si bien qu’il échoue à la preuve de vivacité avant que la moindre question de politique ne soit posée — et les ids en double issus de suppositions de palier différentes se réduisent à un seul candidat, de sorte qu’un pool ne peut pas saturer l’entonnoir par aliasing. L’ordre de sondage est lui-même une politique, car le plafond est consommé par ce qui est sondé en premier : une paire face à l’actif natif de la chain a deux clés dans V4, native et encapsulée, et la clé native a été mesurée comme la plus profonde sur les chains desservies au moment où l’ordre a été décidé (3.76× sur Base, 292× sur Robinhood Chain), elle est donc sondée en premier.

![FIG-2 — L’échelle dériver-puis-prouver de V4 : cinq barreaux, du motif de paliers appris jusqu’à la grille bornée du générateur, chaque id candidat étant prouvé vivant contre le stockage du manager avant de compter dans le plafond de huit ; les ids à hook n’existent jamais sur ce chemin.](https://blazephoenix.xyz/whitepaper/figs/fr/fig-2.png)

**La porte sans permission.** N’importe qui peut appeler `claimV4(c0, c1, fee, tickSpacing)` et faire admettre un pool sans hook, car la revendication porte sa propre preuve : l’id est recalculé à partir de la clé soumise et relu dans le stockage du manager, si bien qu’une fausse revendication se résout en zéros et n’admet rien (`HubE(9)`). Trois autres conditions gardent la porte : l’un des côtés de la paire doit être un token pont routable, afin qu’un siège dans le registre plafonné ne soit jamais dépensé pour une paire que le routeur ne pourra jamais traverser ; les frais effectifs doivent être cotables sous la barrière de frais décrite plus bas ; et la revendication doit franchir la même marge d’admission que franchit un enregistrement déclenché par un swap (§9), avec la liquidité mesurée du pool comme profondeur. Le public peut instruire le registre, mais il ne peut pas lui mentir.

### §5.2 Lire un pool qui ne répond à aucun appel

Une fois un id prouvé vivant, le répartiteur a besoin du prix et de la liquidité — et il n’existe aucun `slot0()` à appeler. Le Core lit directement le stockage du singleton par `extsload`, comme dans l’exemple ci-dessus, et la branche V4 se réduit à la forme close de liquidité concentrée du §4.2 : les mêmes mathématiques, la même discipline face aux dépassements. Le singleton change l’endroit où vit l’état, pas ce que l’état signifie. Un manager sans code ou non conforme produit des mots entièrement nuls — à échec fermé, jamais un revert.

### §5.3 Régler avec un singleton

L’exécution inverse le flux de contrôle habituel. Au lieu d’appeler un pool puis d’être rappelé pour le paiement, le Router appelle `unlock` sur le manager et effectue tout le swap à l’intérieur du callback que le manager lui accorde : swap, puis règlement de chaque monnaie due via `sync → settle → take`, puis libération du verrou. Les monnaies en transit sont transportées au-delà de la frontière du déverrouillage dans le stockage transitoire (`TSLOT_V4IN`, `TSLOT_V4OUT`), de sorte que le cadre ne salit aucun état persistant et que le contexte est prouvablement vide entre les swaps. Le `BalanceDelta` que renvoie le manager est empaqueté — `amount0` dans les 128 bits de poids fort, `amount1` dans ceux de poids faible — et le Router le décode comme tel avant que ne s’exécute la moindre arithmétique de règlement.

La monnaie native préserve l’invariant qui compte. Le Router est canonique en WETH partout — la valeur native qui entre par `swapExactInNative` est encapsulée exactement une fois à la porte, et la sortie native n’est délibérément pas implémentée : le destinataire reçoit du WETH, qu’aucun chemin ne peut piéger silencieusement. Une jambe V4 native désencapsule *juste à temps* dans son propre cadre de déverrouillage — `WETH.withdraw`, puis `settle` avec valeur, l’ETH reçu d’un `take` natif étant ré-encapsulé dans le même cadre — de sorte que l’ETH natif n’existe jamais au repos dans le contrat. La seule surface nouvelle que cela a exigée est un `receive()` gardé aussi étroitement que l’exigence le permet : un slot transitoire (`TSLOT_ETHOK`) contient l’unique adresse autorisée à envoyer de l’ETH brut au Router à cet instant — le contrat WETH canonique pendant le désencapsulage, le PoolManager pendant un take natif, zéro à tout autre moment — si bien que de l’ETH nu venant de quiconque d’autre provoque un revert. La porte native est à échec fermé tant que `weth` n’est pas câblé (`RouterE(3)`).

### §5.4 Les frais qui n’existent qu’au moment de l’exécution

Un pool V4 à frais statiques porte ses frais LP dans sa clé, où ils sont immuables. Le pool manager peut en outre prélever les propres frais de manager d’Uniswap — plafonnés à 1,000 pips, un dixième de pour cent, par sens — que le bras statique du Core n’ajoute pas, si bien qu’un devis sur une clé statique peut surestimer la sortie livrée d’au plus ces frais de manager une fois que le contrôleur de frais d’Uniswap les active (ils sont nuls sur toutes les chains desservies au moment de la rédaction) ; le déficit tombe à l’intérieur du plancher, et la base des frais, étant mesurée, l’ignore. Un pool à frais dynamiques porte à la place la sentinelle `0x800000`, et ses frais réels vivent dans `slot0`. Coter la sentinelle à sa valeur nominale tarifierait l’échange comme gratuit alors que l’exécution paierait le taux en vigueur. La barrière tient en trois lignes dans le Core (`effV4Fee`) :

$$
f_{\mathrm{eff}} \;=\; \begin{cases} f_{\mathrm{key}} & f_{\mathrm{key}} \ne \mathtt{0x800000} \\ \mathtt{0xFFFFFF}\ (\text{unquotable}) & f_{\mathrm{key}} = \mathtt{0x800000} \ \wedge\ f_{\mathrm{proto}} \ne 0 \\ f_{\mathrm{LP}}\ \text{read from } \mathtt{slot0} & \text{otherwise} \end{cases}
$$
^e19

Une clé à frais dynamiques assortie de frais de protocole non nuls renvoie des frais supérieurs ou égaux à $10^{6}$, et `outV3` renvoie zéro pour elle : à échec fermé, parce que la composition des frais de protocole et des frais LP n’est pas ancrée à notre satisfaction, et que les frais de protocole mesurés sur Base au moment de la rédaction sont nuls, si bien que la branche ne coûte rien en pratique. La même règle ferme la même couture dans la famille voisine : les pools Algebra sont à frais dynamiques par construction et enregistrent la sentinelle zéro, si bien que `quoteV3Fee` prend pour vérité des frais configurés non nuls, les frais d’un pool dynamique dans la lecture `globalState` qui a déjà fourni son prix, et — pour les forks à liquidité concentrée dont les frais sont indexés par l’espacement des ticks et résident sur le pool — le propre `fee()` du pool, en échouant fermé à `0xFFFFFF` quand rien ne répond. Les pools V3 statiques ne font aucune lecture supplémentaire et cotent à l’identique, à l’octet près.

Une couture subsiste, et elle est bornée plutôt que supprimée : un hook à frais dynamiques peut remplacer ses frais à chaque swap dans `beforeSwap`, si bien que les frais stockés dans `slot0` ne sont pas dans tous les cas ceux qui sont appliqués. Ce résidu est contenu par la liste d’autorisation de hooks du Hub à l’admission et par le plancher Iron-Law re-dérivé de la sortie réalisée à l’exécution, si bien qu’une substitution hostile aboutit à un revert ou à un déficit à l’intérieur du plancher, jamais à une exécution sous le plancher (§14).

### §5.5 Les hooks : filtrés par l’arithmétique, jamais par l’interrogation

Un hook est du code arbitraire qui s’exécute à l’intérieur du règlement qu’effectue le Router. La défense intuitive — demander au hook ce qu’il compte faire — n’est pas une défense, car un hook hostile ment ou réentre. BlazePhoenix ne demande jamais rien à un hook. Trois couches indépendantes tiennent à la place, la moins chère d’abord, et le §14 les développe : les bits de permission du hook résident dans les quatorze bits de poids faible de sa propre adresse, fixés au déploiement, et les deux bits qui permettent d’altérer les deltas du swap sont refusés par un seul AND avant qu’un token ne bouge (`hookAltersDeltas`, `RouterE(9)`) ; un hook n’est routable que tant qu’il figure sur la liste d’autorisation et que son code runtime correspond encore au codehash épinglé à l’admission (`isHookLive`) ; et le projecteur Ξ retire un hook qui altère les deltas de l’ensemble des candidats du Solver avant le classement, de sorte que la route n’est même jamais représentable. Le résidu est énoncé avec le mécanisme : un hook inscrit sur la liste d’autorisation et qui n’altère pas les deltas peut encore provoquer un revert en plein swap et gaspiller le gas de la tentative — le prix du refus, par principe, d’appeler du code non fiable pour découvrir ce qu’il aurait fait.

### §5.6 Ce que vaut l’intégration

Chaque mécanisme ci-dessus est soit une primitive du Core déjà utilisée ailleurs, appliquée à une nouvelle disposition d’état, soit une primitive réellement nouvelle qui se généralise au-delà de V4 — la découverte par preuve d’existence. Rien n’est une intégration propre à un pool, et rien n’est une liste sélectionnée qu’un humain doit tenir à jour. Les pools V4 sont découverts sans annuaire, prouvés contre un stockage que personne d’autre ne peut écrire, tarifés par la même forme close que toute autre place concentrée, soumis à la barrière de frais par la mesure avec un refus à échec fermé là où la mesure n’est pas exacte, filtrés contre les hooks hostiles par une arithmétique d’adresse immuable, réglés via le propre protocole de verrou du singleton, et accessibles en ETH natif sans que le Router en détienne un seul wei au repos. Le §17 rapporte les mesures : dans le recensement des factories sur Arbitrum, la famille V4 remporte plus de paires de hops que toute autre place enregistrée, et sur Optimism le recensement a enregistré ce qui est, à notre connaissance, la première agrégation routée via V4 de la chain. Nous ne connaissons aucun autre agrégateur public qui dérive, prouve, tarife, filtre et règle les pools V4 entièrement à l’intérieur de la transaction d’exécution, ETH natif compris, sans liste de pools sélectionnée ; si vous en trouvez un, l’affirmation est fausse et nous aimerions le savoir.

## §6 Le solveur Solidly

Certains invariants n’ont pas de forme close assez simple pour être reproduite sans risque, et les reproduire est exactement le risque de réplication que le protocole refuse ailleurs. Pour ces places, le répartiteur interroge le pool, par le canal honnête le plus étroit que la place expose — et garde une réplique en réserve pour les forks qui ne répondent pas.

La famille Solidly — Aerodrome, Velodrome et leurs forks — sert des actifs corrélés sur des paires stables avec un invariant quartique qui épouse la diagonale bien plus étroitement que le produit constant, tout en admettant encore la divergence :

$$
k(x, y) \;=\; x^{3}y \;+\; x\,y^{3} \;=\; x\,y\left(x^{2}+y^{2}\right)
$$
^e5

Près du point d’équilibre, cette courbe livre presque toute l’entrée en sortie — un slippage minimal exactement là où se concentrent les flux de stablecoins et de liquid staking. Les paires Solidly volatiles sont à produit constant, et le bras volatil du Core *est* `outV2` — un seul producteur, ce que verrouille la relation métamorphique MR7 (§15).

![FIG-3 — Sortie en fonction de l’entrée pour une paire stable équilibrée : la quartique Solidly face au produit constant et à la diagonale à slippage nul, à partir des propres formules du Core.](https://blazephoenix.xyz/whitepaper/figs/fr/fig-3.png)

**Chemin principal : interroger le pool.** `getAmountOut(amountIn, tokenIn)` est calculé par le propre bytecode de la paire — frais en vigueur, courbe et arrondi compris — si bien qu’un swap demandant exactement ce chiffre satisfait par construction la vérification d’invariant du pool, et qu’aucune marge n’est nécessaire. Le déclencheur du repli est une réponse d’au plus 1 wei, alignée sur les trois canaux qui interrogent (Solver, Router, Quoter) après qu’un pool répondant exactement 1 a un jour envoyé deux d’entre eux sur des branches différentes.

**Repli : répliquer, puis demander moins.** Quand un fork n’expose pas le sélecteur, le répartiteur lit les frais en vigueur auprès de la factory de la paire (`getFee(pool, stable)` ; des frais de repli déclarés ne sont crus que dans la limite du même plafond de 100 bps que pour le bras V2, sinon 30 bps), résout l’invariant sur des réserves normalisées en décimales, et multiplie le résultat par 9,800 / 10,000 — une décote de 200 bps afin que l’arrondi du pool, que la réplique ne peut pas observer, dispose toujours de jeu. La décote ne s’applique qu’au repli, et jamais quand le pool a répondu lui-même.

**La résolution.** Étant donné le solde d’entrée après l’échange $x$ et l’invariant cible $K$ issu des réserves avant l’échange, le Core cherche la racine de $f(y) = x^{3}y + xy^{3} - K$, avec $f'(y) = x(x^{2} + 3y^{2}) > 0$, par itération de Newton amorcée en $y_{0} = Y$, la réserve opposée avant l’échange (`_solY`) :

$$
k(x, y_{n}) < K:\ \ y_{n+1} = y_{n} + \max\!\left(1,\ \left\lfloor \tfrac{K - k(x,y_{n})}{f'(y_{n})} \right\rfloor\right); \qquad
k(x, y_{n}) \ge K:\ \ y_{n+1} = y_{n} - \delta_{n},\ \ \delta_{n} = \begin{cases} \lfloor y_{n}/2 \rfloor & d_{n} \ge y_{n} \\ \max(1, d_{n}) & \text{otherwise} \end{cases},\ \ d_{n} = \left\lfloor \tfrac{k(x,y_{n}) - K}{f'(y_{n})} \right\rfloor
$$
^e6

L’amorce qui semble naturelle — l’approximation à somme constante — est fausse d’une façon qui corrompt silencieusement les prix : pour un pool absorbant une entrée valant plusieurs fois sa réserve, elle atterrit très au-dessus de la racine, dans une région où $f$ est extrêmement raide, le pas est arrondi par défaut à zéro, et le solveur se bloque sur un nombre assuré, plausible et faux. Amorcer à la réserve opposée et avancer dans les deux sens converge aussi bien pour les pools équilibrés que pour les pools fortement déséquilibrés. Un pas descendant qui franchirait zéro est remplacé par la moitié de l’itéré ; chaque pas impose au moins une unité de progrès ; l’itération s’arrête quand des itérés successifs diffèrent d’au plus une unité, et se pose du côté sûr pour l’invariant par un ajustement final d’une unité en défaveur du preneur. La boucle est plafonnée à 64 itérations et le plafond échoue fermé : le solveur renvoie l’amorce elle-même, que la garde $y \ge Y$ de l’appelant transforme en devis nul — le pool est traité comme impossible à tarifer, à l’image d’Aerodrome lui-même, qui refuse plutôt que de mal tarifer. Une dérivée qui s’annule emprunte la même sortie.

**Normalisation des décimales.** L’invariant est homogène de degré quatre, donc quand les deux tokens ont le même nombre de décimales, l’échelle se simplifie et les réserves brutes fonctionnent directement. Une paire comme DOLA (18 décimales) contre USDC (6) fait entrer cet écart dans la quartique, donc le Core ramène les deux côtés à une base commune $10^{18}$ avant la résolution, puis annule la mise à l’échelle :

$$
X = r_{\mathrm{in}}\cdot 10^{\,18-d_{\mathrm{in}}},\quad Y = r_{\mathrm{out}}\cdot 10^{\,18-d_{\mathrm{out}}},\quad A = \left\lfloor\frac{x\cdot 10^{\,18-d_{\mathrm{in}}}\,(\mathrm{BPS}-f)}{\mathrm{BPS}}\right\rfloor,\quad \mathrm{out} = \left\lfloor\frac{Y - y^{\star}(X+A,\,K)}{10^{\,18-d_{\mathrm{out}}}}\right\rfloor
$$
^e7

Les tokens qui déclarent plus de dix-huit décimales cotent zéro plutôt que de provoquer un dépassement par le bas dans la mise à l’échelle. Les décimales de l’autre token sont lues sur le pool, et non reprises de l’appelant — une primitive à producteur unique qui accepterait la coordonnée de son appelant rouvrirait la divergence qu’elle existe pour fermer.

**À échec fermé sur tout le domaine.** Quatre gardes décident si la courbe est évaluée ou non, et chacune renvoie zéro plutôt que de provoquer un revert, car un revert à l’intérieur d’un devis est un `DELEGATECALL` de bibliothèque qui défait tout le plan du Solver pour la paire : $X$ et $Y$ ne doivent pas dépasser $3.4 \times 10^{38}$ ; $X + A$ non plus ; et `_solKFits` exige que l’invariant $k = xy(x^{2}+y^{2})/\mathrm{WAD}^{3}$ et la dérivée $x(x^{2}+3y^{2})/\mathrm{WAD}^{2}$ tiennent tous deux dans un mot de 256 bits à l’amorce, qui est le plus grand point que visite Newton. Ce dernier test est posé sans division — il calcule le mot de poids fort du produit sur 512 bits et le compare à un WAD (`_mulFitsWad`) — parce que la forme antérieure, $a \le \max \cdot \mathrm{WAD} / b$, débordait elle-même sur un pool détenant moins d’une unité d’un token. La voie N-version du §15 a débusqué cette faille le 2026-09-05 ; la réparation est plus petite de 35 octets que le code qu’elle a remplacé et a été intégrée avec un test écrit d’abord en rouge, avec un fuzz de 5,000 exécutions sur tout le domaine, un balayage de 60,000 échantillons de la même arithmétique en Python, et trois mutants dans la garde.

**Le résidu mesuré, et la marge de 200 bps.** La réplique a été mesurée face à des pools Aerodrome en production, avec le propre `getAmountOut` du pool comme vérité de terrain :

| Pool | État | Vérité de terrain | Sortie du Core | Écart |
|---|---|---|---|---|
| USDC / USDbC | équilibré (6 / 6 déc.) | 999,534,071 | 999,534,071 | 0.000 % |
| DOLA / USDC | déséquilibré (18 / 6 déc.) | 985,528,437 | 987,998,286 | 0.251 % |
| USDz / USDC | déséquilibré (18 / 6 déc.) | 899,325,141 | 901,565,830 | 0.249 % |

La paire équilibrée concorde à l’entier près ; les paires déséquilibrées restent dans un écart de 0.5 %, du côté haut, parce que l’arrondi du solde après frais du pool est invisible de l’extérieur. Ce résidu est la raison pour laquelle le repli demande 200 bps de moins : la marge garantit que la vérification d’invariant de la paire dispose de jeu à l’exécution, convertissant un possible revert par sur-cotation en une exécution certaine, et elle est appliquée à l’identique sur la surface d’aperçu et sur la surface dans le cadre, de sorte que les deux restent au pas. La voie de l’oracle canonique du §15 borne la courbe de manière indépendante : face à une implémentation écrite à partir de la spécification de la place, le devis stable du Core reste dans un écart de 4 wei sur 5,000 entrées fuzzées.

## §7 Dérivation Déterministe 𝒟

Avant qu’un pool puisse être coté, il doit être trouvé. La réponse du secteur consiste à demander : un appel à la factory de la place lit un mapping de stockage et renvoie une adresse, et l’appelant croit la factory sur parole. BlazePhoenix prend en charge cette réponse — quatre modes d’appel à la factory couvrent les places dont les déploiements ne sont pas reproductibles — et en préfère une plus forte partout où l’arithmétique le permet : calculer l’adresse, sans aucun appel externe. Une adresse calculée peut être vérifiée par quiconque dispose des trois mêmes entrées ; à une adresse récupérée, on ne peut que faire confiance.

Le mécanisme est CREATE2. L’EVM fait de l’adresse d’un contrat une fonction pure de son déployeur, d’un sel et du hash de son code d’initialisation :

$$
\mathrm{pool} \;=\; \mathrm{addr}_{20}\!\left(\mathrm{keccak256}(\mathtt{0xff}\,\Vert\,\mathrm{origin}\,\Vert\,\mathrm{salt}\,\Vert\,\mathrm{initCodeHash})\right)
$$
^e8

où $\mathrm{addr}_{20}$ prend les vingt derniers octets du hash. Le Core énonce la préimage de 85 octets une seule fois (`create2Address`) ; `deriveAddress` choisit le polynôme du sel et l’origine selon le mode, et la dérivation Algebra attestée du Hub réutilise le même producteur.

| Mode | Famille | Résolution | Détail du sel / de la sonde |
|---|---|---|---|
| 0 | V2 | appel à la factory `getPair(t0,t1)` — sélecteur `0xe6a43905` | — |
| 1 | V3 / Algebra | appel à la factory `getPool(t0,t1,fee)` — `0x1698ee82` ; en cas d’absence, `poolByPair(t0,t1)` — `0xd9a641e1` | — |
| 2 | Solidly | appel à la factory `getPool(t0,t1,stable)` — `0x79bc57d5` | — |
| 3 | Forks Solidly concentrés (Slipstream, Velodrome CL) | appel à la factory `getPool(t0,t1,tickSpacing)` — `0x28af8d0b` | — |
| 4 | V2 CREATE2 | arithmétique pure | `keccak256(packed(t0,t1))`, origine = factory |
| 5 | V3 CREATE2 | arithmétique pure | `keccak256(encode(t0,t1,fee))` ; `fee == 0` est la sentinelle Algebra → `keccak256(encode(t0,t1))`, origine = le `poolDeployer()` de la factory (`0x3119049a`), attesté à l’admission |
| 6 | Clone Solidly CREATE2 | arithmétique pure | `keccak256(packed(t0,t1,stable))`, origine = factory |
| 7 | CL CREATE2 | arithmétique pure | `keccak256(encode(t0,t1,tickSpacing))`, origine = factory |
| 8 | *retiré* | libéré par l’excision de Curve ; une pierre tombale — `MODES_VALID = 0x2FF` le refuse | — |
| 9 | Scan de dérivation V4 | dériver des poolIds sans hook et les prouver vivants sur le PoolManager (§5.1) | grille ≤ 99 paliers, ≤ 40 sondés à froid, arrêt à 8 pools |

Les modes 0–3 coûtent un `staticcall` chacun ; les modes 4–7 ne font aucun appel et n’exigent que le bon init-code hash ; le mode 9 sert la seule famille dont les pools ne peuvent être localisés ni d’une façon ni de l’autre.

**Un théorème, une affirmation et une preuve.** Une adresse dérivée est un théorème sur la paire : étant donnés l’origine, le polynôme du sel et l’init-code hash, l’adresse s’ensuit par le seul keccak, et quiconque la conteste peut la recalculer. Une adresse demandée est l’affirmation d’une factory sur son propre stockage — et une factory peut répondre par un pool sur d’autres tokens. Depuis 2026-09, un pool demandé (modes 0–3) prouve sa propre paire avant que la découverte ne le liste : le Hub lit ses `token0()` et `token1()` — les deux mêmes lectures que fait le Router à la couture qui paie — et une non-concordance écarte le candidat, de sorte qu’un pool que l’exécuteur refuserait n’est jamais listé, planifié ni classé. C’est la matrice des places hostiles du §15 qui a changé cela : sa première exécution a listé un pool par lequel avait répondu une factory admise par un curateur, le planificateur l’a classé, et l’exécuteur l’a refusé à la couture qui paie — les fonds n’ont jamais été à risque, la paire a été refusée tandis que l’imposteur remportait la répartition.

**Deux subtilités dans les lignes de forme V3.** Algebra (classe Camelot) applique des frais dynamiques, donc des frais ne peuvent jamais faire partie de son identité : un enregistrement Algebra déclare des frais nuls comme sentinelle, le mode 5 retire les frais du sel, et l’origine CREATE2 est le contrat `poolDeployer` distinct qu’exposent les factories Algebra. Cette origine est la seule entrée de dérivation qu’une factory pourrait encore orienter après l’admission — un proxy change sa réponse en gardant son codehash intact — si bien que le Hub l’atteste une fois à `addFactory` (`factoryDeployer`) et dérive ensuite à partir de l’origine attestée ; après la renonciation au contrôle, l’attestation est figée. Côté appel à la factory, les factories Algebra exposent `poolByPair` plutôt que `getPool`, donc une recherche en mode 1 qui ne renvoie rien réessaie avec ce sélecteur avant d’abandonner.

**Chaque dérivation passe une dernière barrière.** Le Hub écarte toute adresse dérivée qui ne porte aucun bytecode runtime — `extcodesize` constitue toute la vérification (`hasCode`) — de sorte qu’un init-code hash erroné, un enregistrement périmé ou une chain où une famille n’a jamais été déployée ne produit rien plutôt qu’une place fantôme. Du bytecode à l’adresse prouve l’existence, pas l’honnêteté, et c’est pourquoi l’admission, la bande de crédibilité et les planchers se tiennent toujours entre un pool localisé et un échange exécuté. Le même pool dérivé sous plusieurs combinaisons `(fee, spacing)` n’est listé qu’une fois.

**La frontière entre dériver et demander est empirique.** Les places à base de clones comme Velodrome et Aerodrome restent en appel à la factory même si leur init-code hash est connu, car leur origine CREATE2 se trouve dans un déployeur interne de clones déterministes plutôt que dans la factory — l’adresse n’est pas reproductible à partir d’entrées publiques, donc le protocole demande. Uniswap V3 est le cas emblématique dans l’autre sens : son init-code hash est une seule et même constante sur chaque chain parce que le même bytecode de pool est déployé partout, et l’identité `derived == getPool` tient sur des forks d’Ethereum, d’Arbitrum, d’Optimism et de Base face aux propres factories de chaque chain (§17). Dériver là où l’arithmétique est saine, demander là où elle ne l’est pas, et tout vérifier dans les deux cas.

**La découverte consomme la table ligne par ligne.** Pour chacune des factories enregistrées, au plus `MAX_FACTORIES = 16`, le Hub sonde chaque combinaison `(fee, spacing)` déclarée — les lignes Solidly dans les deux variantes, stable et volatile, une ligne V4 en réservant jusqu’à huit — de sorte que le pire cas du scan est fixé par l’enregistrement, et non par l’humeur de la chain. La surface de configuration est gardée à la porte (§9) : une place structurellement impossible ne peut pas être stockée.

## §8 Le Monoslot

Le stockage est le coût dominant de toute action sur l’EVM : un `SLOAD` à froid coûte 2,100 gas, et un registre qui disperserait les attributs d’un pool sur des slots séparés le paierait à nouveau pour chaque attribut, pour chaque pool, sur chaque route évaluée. BlazePhoenix regroupe tout l’état de routage d’un pool dans un seul mot de 256 bits — le Monoslot — lu une fois et décodé par pure arithmétique.

| Bits | Champ | Largeur | Signification |
|---|---|---|---|
| 0 | `active` | 1 | l’emplacement est actif |
| 1–7 | réservé | 7 | bit 5 = courbe stable (lue à l’enregistrement depuis le propre `stable()` du pool) ; bit 6 = côté natif permuté ; bit 7 anciennement l’indicateur de pont, n’est plus écrit — le bonus de pont est lu en direct sur la paire (§9) |
| 8–31 | `fee` | 24 | palier de frais, dans l’unité native de la place |
| 32–39 | `kind` | 8 | type de place (§4) |
| 40–47 | `tier` | 8 | 0 = sélectionné (amorcé par un opérateur), 2 = sans permission (appris par l’échange ou revendiqué) |
| 48–59 | `conc` | 12 | champ de concentration, masqué à `0xFFF` avant l’empaquetage |
| 60–63 | `bucket` | 4 | compartiment logarithmique de profondeur |
| 64–95 | `lastUpdateTs` | 32 | horodatage d’horloge murale du dernier contact |
| 96–127 | `emaIn` | 32 | réservé à une EMA du volume d’entrée ; écrit à zéro à l’enregistrement, non mis à jour par le tick livré |
| 128–159 | `emaOut` | 32 | réservé à une EMA du volume de sortie ; comme ci-dessus |
| 160–191 | `swapCount` | 32 | compteur d’activité, saturant à $2^{32}-1$ |
| 192–223 | `regBlk` | 32 | bloc d’enregistrement |
| 224–255 | `lastBlk` | 32 | bloc du dernier contact |

![FIG-4 — Disposition des bits du Monoslot : un mot de 256 bits, quatorze champs nommés, le champ de concentration masqué à douze bits pour qu’il ne puisse jamais déborder sur le compartiment de profondeur.](https://blazephoenix.xyz/whitepaper/figs/fr/fig-4.png)

L’encodage est une suite de décalages et de OR (`encodeSlot`) :

$$
s \;=\; a \;\vert\; f\!\ll\!8 \;\vert\; k\!\ll\!32 \;\vert\; r\!\ll\!40 \;\vert\; (c \wedge \mathtt{0xFFF})\!\ll\!48 \;\vert\; b\!\ll\!60 \;\vert\; t\!\ll\!64 \;\vert\; e_{\mathrm{in}}\!\ll\!96 \;\vert\; e_{\mathrm{out}}\!\ll\!128 \;\vert\; n\!\ll\!160 \;\vert\; B_{\mathrm{reg}}\!\ll\!192 \;\vert\; B_{\mathrm{last}}\!\ll\!224
$$
^e9

Un détail est une garde, pas une commodité : le champ de concentration est masqué à douze bits *avant* le décalage, de sorte qu’il ne peut jamais déborder sur le compartiment de profondeur aux bits [63:60] — l’empaquetage interdit la classe de chevauchements silencieux de champs qui corrompt l’état empaqueté, plutôt que de compter sur les appelants pour rester dans les bornes. Le décodage est tout aussi mécanique : chaque accesseur est un décalage et une troncature ; la fonction d’écriture du compartiment efface ses quatre bits avant de les positionner ; le compteur de swaps sature au lieu de reboucler, de sorte qu’un emplacement plein ne peut jamais ramener à zéro l’historique d’un pool très actif. L’horodatage d’horloge murale, apposé par le Hub à chaque tick et à chaque enregistrement, pilote à la fois la décroissance de vitalité du §9 et la barrière de fraîcheur de découverte du Solver.

Remarquez ce que le mot ne contient pas : un prix. Le Monoslot porte tout ce qu’il faut pour classer un pool — activité, classe de profondeur, frais, type, indicateurs, récence — et rien qui puisse être pris pour un devis, si bien que la mémoire du registre est structurellement incapable de tarifer un échange. Les prix sont calculés à neuf, à partir des réserves en direct, dans le cadre qui échange sur eux ; le mot empaqueté ne fait jamais que décider quels pools valent la peine d’être interrogés. Le chemin de mise à jour respecte la même économie : un swap à travers un pool connu réécrit le mot une fois — compteur, compartiment, horodatages, un `SSTORE` sur un `SLOAD` — si bien que le coût marginal du maintien à jour du registre voyage dans des transactions que les utilisateurs envoyaient déjà.

Pourquoi un seul mot importe est une question de multiplication. Le Solver sonde jusqu’à `MAX_CANDIDATES = 8` places par paire sur jusqu’à trois topologies, si bien qu’un seul échange résolu peut noter quelque deux douzaines de pools avant de déplacer un token. À 2,100 gas par slot froid, une disposition à cinq slots par pool chiffre cette passe de notation à des centaines de milliers de gas ; le Monoslot la chiffre à une lecture par pool, et le `psisOf` du Hub renvoie le score de chaque candidat en un seul appel groupé. Le Monoslot n’est pas une optimisation appliquée au routage on-chain ; il est la raison pour laquelle le routage on-chain est abordable tout court.

## §9 Le Registre Auto-Réparateur et le Champ de Vitalité Ψ

La plupart des agrégateurs connaissent leurs places de marché parce qu’un keeper indexe les pools off-chain et pousse des listes on-chain — un processus de confiance, une fenêtre d’obsolescence et une dépendance opérationnelle, trois choses qu’un protocole qui résout les routes on-chain ne peut pas accepter. BlazePhoenix adopte la position inverse : le registre apprend la liquidité en l’échangeant.

### §9.1 Apprendre en échangeant, et la preuve de paire

Sur chaque jambe exécutée, le Router appelle le `recordSwap` du Hub (`onlyRouter`, `whenLive`) en lui transmettant le pool, son type, ses frais, la paire, les montants et la profondeur qu’il a mesurée. Le Hub refuse d’abord tout type que le Router n’aurait pas pu exécuter (`KINDS_EXECUTABLE`) — une défense locale qui ne dépend pas du fait que le Router reste ce qu’il était au déploiement — puis emprunte l’un de deux chemins.

Le Monoslot d’un pool connu est actualisé : le compteur de swaps augmente à partir de sa base *actuellement décrue*, la classe de profondeur est recalculée, le bloc et l’horodatage d’horloge murale avancent — un seul mot compacté réécrit, aucun appel externe. Un pool inconnu prend le chemin froid. Il doit d’abord franchir l’admission (`_canInsert`, ci-dessous). Puis il doit prouver l’affirmation qu’il s’apprête à stocker : pour chaque type à forme de paire (`KINDS_PAIR_PROOF` = V2, V3, Solidly, Algebra), le Hub lit les propres `token0()` et `token1()` du pool et exige qu’ils correspondent à la paire — au plus deux `staticcall`, sur le chemin froid uniquement. Chaque argument de `recordSwap` est du calldata contrôlé par l’appelant et transporté dans la route du Router — pool, type et profondeur — donc, sans cette preuve, un attaquant enregistre un contrat qu’il a écrit, sous une paire qu’il a choisie, à une profondeur qu’il a choisie, sans détenir aucun des deux tokens. Pour les deux types V4, dont le champ `pool` est un identifiant tronqué sans bytecode, la preuve est une re-dérivation : l’espacement de ticks est retrouvé, puis l’identifiant est recalculé et comparé, pour les pools sans hook uniquement, de sorte qu’un pool à hook ne peut jamais s’enregistrer par cette porte ; un pool V4 natif est comparé dans les deux orientations de `(address(0), T)`. Une discordance fait *sauter* l’enregistrement et n’annule jamais la transaction, car le swap de l’utilisateur est déjà réglé et ne doit jamais échouer à cause d’une décision du registre.

La preuve de paire à l’enregistrement est nouvelle en septembre 2026, et la mesure qui l’a produite est celle du §15 : quinze mutations du code source ont été exécutées contre chaque invariant avec état, et le mutant qui supprimait cette preuve a survécu à toutes les campagnes lors de la première exécution, parce qu’aucun test n’avait jamais présenté au Hub un pool échangeant d’autres tokens. La campagne du Hub le fait désormais à chaque exécution — paire inversée, un token commun, deux étrangers — et `invariant_ActiveEntriesTradeThePair` lit les `token0`/`token1` de chaque entrée active ; le mutant est une entrée de garde que cet invariant tue. La découverte porte la même preuve pour les pools obtenus par interrogation (§7).

Il n’y a aucun indexeur à faire tourner et aucun moment où le protocole est aveugle parce qu’un serveur est en panne. Une porte opérateur `seedPool` pré-remplit des paires au déploiement (niveau 0, sélectionné) et une porte sans permission `claimV4` admet les pools V4 sans hook sous leur propre preuve (§5.1) ; toutes deux sont des optimisations, pas des dépendances — un registre vide se répare en un registre peuplé par les échanges ordinaires.

### §9.2 Le score

Le Champ de Vitalité Ψ sépare la qualité de la capacité, en ne lisant que le Monoslot et le statut de pont actuel de la paire (Core `psi`) :

$$
\Psi(s, t) \;=\; V(s, t)\cdot 2^{\,b(s)} \cdot \left(1 + \tfrac{2{,}500}{10^{4}}\,\mathbf{1}_{\mathrm{bridge}}\right)\cdot\left(1 + \tfrac{500}{10^{4}}\,\mathbf{1}_{\mathrm{conc}}\right)
$$
^e10

La classe de profondeur $b = \min(15, \lfloor \log_{10}(d / 10^{15}) \rfloor)$ intervient par son poids $2^{b}$ : chaque ordre de grandeur de profondeur double le poids, donc la profondeur compte de façon sous-linéaire — un pool profond est nettement favorisé, mais un pool simplement sain n’est pas effacé par un géant. Un pool sur une paire pont gagne +25 %, parce que les ponts sont le tissu conjonctif du graphe de liquidité ; un pool concentré gagne +5 % de plus. Le bonus de pont est lu en direct sur la paire par un producteur unique (`_pairBridged`) plutôt que dans un bit figé à l’enregistrement, et il est uniforme au sein d’une paire, donc il ne peut jamais réordonner les pools d’une même paire.

**La décroissance — un seul mécanisme.** L’activité décroît avec l’inactivité selon une règle unique d’horloge murale (`_decayedSwapCount`) :

$$
V(s,t) \;=\; \mathrm{swapCount}(s) \;\gg\; \left\lfloor \frac{t - t_{\mathrm{last}}}{24{,}576\ \mathrm{s}} \right\rfloor, \qquad V = 0 \ \text{once the shift exceeds } 31.
$$
^e11

Le pas est `VITALITY_DECAY_STEP_SECONDS = 24,576` — environ 6.8 heures — et trente-deux divisions par deux éteignent n’importe quel score, de sorte qu’un pool silencieux atteint zéro et devient entièrement évinçable après 786,432 s, environ 9.1 jours. Un seul swap le ranime, et la reprise est honnête : le compteur repart de ce que la décroissance a laissé, jamais du total d’avant la décroissance, de sorte qu’un swap de dust après un long silence ne peut pas restaurer le rang accumulé d’un pool. Le calcul du score distingue deux zéros : un emplacement *mort* — vide, jamais actualisé, ou au-delà de l’horizon — obtient un vrai zéro et sort du classement, tandis qu’un emplacement *vivant* dont le petit compteur s’est simplement arrondi à zéro sous le décalage est relevé à un, de sorte qu’un pool réel, récemment touché, ne disparaît jamais sur un artefact d’arrondi. La fenêtre est en secondes d’horloge murale, délibérément : une fenêtre comptée en blocs correspond à une durée réelle différente sur chaque chain, et une implémentation antérieure qui décalait tous les 16 blocs décroissait vers zéro environ 128× plus vite que documenté.

![FIG-5 — Décroissance de la vitalité : un compteur de 4,096 est divisé par deux toutes les 24,576 s et atteint zéro après 32 pas ; un swap au jour 4 le ranime depuis la base décrue, et non depuis le total historique.](https://blazephoenix.xyz/whitepaper/figs/fr/fig-5.png)

### §9.3 Éviction

Chaque paire contient au plus `MAX_SLOTS = 16` pools. Quand un dix-septième veut entrer, le Hub note chaque occupant selon le Ψ complet — profondeur, bonus et tout le reste, de sorte qu’un nouvel arrivant profond peut déloger un squatteur peu profond mais actif — et n’admet le nouvel arrivant que si son score projeté dépasse celui de l’occupant le plus faible d’une marge (`_canInsert`) :

$$
\Psi_{\mathrm{new}} \;>\; \Psi_{\mathrm{worst}} + \left\lfloor \Psi_{\mathrm{worst}}/4 \right\rfloor, \qquad \Psi_{\mathrm{new}} = 2^{\,b(d_{\mathrm{new}})}
$$
^e13

le nouvel arrivant étant noté de façon conservatrice — vitalité un, sa propre classe de profondeur, aucun bonus — et un nouvel arrivant de profondeur nulle étant refusé d’emblée. La marge ferme un vecteur de nuisance : sous une simple règle qui déloge le plus faible, un adversaire pourrait occuper seize emplacements peu profonds à peine actifs et tenir à l’écart un concurrent profond ; avec un classement par Ψ et une marge, le pool réellement plus profond l’emporte quand même. La limite de la constante est énoncée là où elle est appliquée : le poids du nouvel arrivant est une puissance de deux alors que le score de l’occupant est continu, donc la marge effective va d’environ 100 % (un occupant juste au-dessus de un) jusqu’à environ 25 % (un occupant à 51, plus petit nouvel arrivant admissible 64) ; les 25 % de la règle sont aussi le plancher du comportement, jamais enfoncé. Un occupant à fort volume d’échanges est difficile à déloger tant que la décroissance n’a pas fait son œuvre — deux jours d’inactivité lui coûtent sept divisions par deux. Une revendication V4 sans permission franchit la même marge avec la liquidité mesurée du pool comme profondeur.

### §9.4 La découverte, et son réglage de gas

La découverte est une fonction view publique : pour toute paire, le Hub parcourt sa liste de factories, dérive des candidats selon les modes du §7, applique `hasCode` et la preuve de paire, et le Solver fusionne le résultat avec l’ensemble enregistré, dédoublonné par adresse, de sorte qu’aucune source ne peut supprimer l’autre. Pour borner le gas, le Solver applique une barrière de fraîcheur (`_registryFresh`) : quand au moins `MIN_FRESH_VENUES = 3` places enregistrées *sélectionnées* pour la paire ont été touchées dans les `DISCOVERY_TTL_SECONDS = 3,600`, il fait confiance au registre et saute le balayage de dérivation. Seules les lignes sélectionnées (niveau 0) peuvent se porter garantes : une ligne sans permission est écrite par quiconque a effectué le premier swap, et la compter permettait à trois paires de dust auto-enregistrées, maintenues fraîches par trois swaps de dust par heure, de désactiver la découverte pour une paire et de masquer toutes les places honnêtes jamais enregistrées. La ligne sans permission reste un candidat ; elle ne choisit plus le chemin. La barrière est un réglage de gas et de couverture, jamais un paramètre de sécurité — le plancher Iron-Law et le minimum de l’utilisateur protègent chaque exécution quel que soit le degré d’obsolescence du registre, parce que ni l’un ni l’autre ne lit le registre, en aucune façon.

### §9.5 La surface de configuration

Les portes d’enregistrement du Hub sont gardées à l’entrée. `addFactory` rejette, avant toute écriture d’état (`HubE(5)`) : un type hors de `KINDS_ROUTABLE` ; un mode hors de `MODES_VALID` ; un mode CREATE2 avec un init-code hash nul ; un emplacement salt V2 lié à autre chose que le type V2 ; un emplacement clone lié à un type non Solidly ; un emplacement salt V3 lié à autre chose que V3 ou Algebra, un enregistrement Algebra devant déclarer tous ses frais à la sentinelle zéro ; et une ligne de scan par dérivation V4 liée à un autre type que V4 ou dotée de listes de frais et d’espacements mal appariées. Une factory connue est rafraîchie sur place, jamais dotée d’une seconde ligne, de sorte qu’une seule adresse ne peut pas épuiser les seize sièges (`HubE(4)`) ; après la renonciation au contrôle, une factory dont le code runtime a changé ne peut pas être réarmée, et une ligne de dérivation ne peut pas être convertie en ligne d’interrogation. `allowHook` épingle le codehash du hook à l’admission, et un hook n’est routable que tant qu’il figure sur la liste d’autorisation *et* reste inchangé (`isHookLive`) ; après la renonciation, le retrait de la liste disparaît et un hook modifié reste en pause pour toujours. Il y a au plus `MAX_BRIDGES = 3` ponts ; `addBridge` est un pouvoir de curateur qui survit à la renonciation, `removeBridge` un pouvoir de contrôle qui meurt avec elle.

### §9.6 La frontière structurelle

Une phrase borne tout ce qui précède : **Ψ peut masquer une place ; il ne peut jamais en fausser le prix.** Le score classe et tronque l’ensemble des candidats — aucun devis, aucun plancher et aucune allocation ne le lit, parce qu’ils sont calculés à partir de mesures prises dans la transaction ; la mémoire n’atteint la sélection de route que par omission. Quoi que croie le registre, le pire qu’il puisse faire remonter est un prix actuel moins bon mais réel, et la valeur qu’un tel prix peut laisser passer est plafonnée par le plus strict de la bande de crédibilité, du plancher Iron-Law re-dérivé dans le cadre et du minimum fixé par l’utilisateur. Le capital est hors d’atteinte par construction. Le registre décide de ce qui est vu ; il n’a pas voix au chapitre sur ce qui est payé.

## §10 Le routage et l’Allocateur de répartition

Un pool à produit constant fixe ses prix selon une seule règle : ses deux réserves, multipliées, ne peuvent jamais diminuer. Versez $\Delta$ avec une rétention de frais $\gamma$ (des frais de place de 0.30 % signifient $\gamma = 0.997$) et vous recevez $y\,\gamma\Delta / (x + \gamma\Delta)$, avec votre propre montant d’entrée au dénominateur : plus vous poussez à travers un seul pool, moins chaque token supplémentaire rapporte. Le slippage est de l’arithmétique, et son remède aussi : répartir le même ordre entre des pools de la même paire fait moins bouger chaque pool.

**Exemple détaillé 2 — une répartition sur deux pools.** Un ordre de 100 face à deux pools de la même paire, tous deux à 0.30 % : le pool A détient 1,000 de chaque côté, le pool B 4,000 de chaque côté.

| Route | Arithmétique | Vous recevez |
|---|---|---|
| Les 100 dans A | $1{,}000 \times 99.7 / 1{,}099.7$ | 90.661 |
| Les 100 dans B | $4{,}000 \times 99.7 / 4{,}099.7$ | 97.275 |
| 20 dans A, 80 dans B | $1{,}000 \times 19.94 / 1{,}019.94 \;+\; 4{,}000 \times 79.76 / 4{,}079.76$ | $19.550 + 78.201 = 97.751$ |

La répartition 20 / 80 est le choix propre de l’allocateur, pas une supposition : les poids sont proportionnels à la profondeur mesurée rapportée à celle du candidat le plus profond — A pèse $1{,}000/4{,}000 = 2{,}500$ sur 10,000, B pèse 10,000 — donc A prend $2{,}500 / 12{,}500 = 20$ % de l’ordre. La répartition bat le meilleur pool seul de 0.476 tokens, soit 48.9 bps de celui-ci. La barrière de répartition demande ensuite si ce gain justifie sa seconde jambe : le seuil est $97.275 \times (1 + 25 / 10^{6}) = 97.278$, et 97.751 le franchit, donc la répartition est maintenue. Chaque ligne se vérifie avec une calculatrice, et l’effet se cumule : l’impact de prix est la réponse de chaque pool à *sa propre* portion, donc chaque jambe admissible ajoutée à une route fait moins bouger chaque pool.

![FIG-6 — Sortie en fonction de la part envoyée au pool profond pour l’exemple à deux pools : une courbe concave dont le maximum est proche de 80 %, avec le point de l’allocateur, proportionnel à la profondeur, marqué.](https://blazephoenix.xyz/whitepaper/figs/fr/fig-6.png)

### §10.1 Formes de route et budgets

Le Solver construit trois topologies et renvoie celle qui maximise la sortie (`findBestRoutePlan`, avec la deuxième comme solution de repli) : une route *directe* en un hop ; une route *par pont* via un intermédiaire ; une route *à double pont* via deux — trois hops, la forme la plus profonde que construit le Solver et le maximum qu’accepte le Router (`MAX_HOPS = 3`). Chaque hop se répartit en jambes parallèles sous deux budgets délibérément distincts : `MAX_LEGS_PER_STAGE = 4` répond à *combien de jambes tiennent dans un hop*, `MAX_LEGS = 11` à *combien en tiennent dans une route*, et ensemble ils donnent 4, 4 + 4 et 4 + 4 + 3 — le dernier hop d’une route à trois hops en obtient trois parce que le plafond global le comprime, et non parce que quelqu’un a écrit un cas particulier. Les ponts proviennent d’un ensemble d’au plus trois tokens configuré par l’opérateur dans le Hub ; le Solver développe chaque pont configuré et laisse le `totalOut` mesuré décider, échange par échange. Une route dont l’impact mesuré dépasse `MAX_ROUTE_IMPACT_BPS = 9,000` n’est pas renvoyée du tout, et une paire sans route admissible refuse avec `SolverE(5)` plutôt que d’en fabriquer une. Le Router impose son propre plafond de cinq jambes par hop sur toute route fournie par l’appelant (`MAX_LEGS_PER_HOP = 5`, `RouterE(3)`).

### §10.2 L’entonnoir

Pour chaque paire, le Solver rassemble les pools enregistrés et, sauf si le registre est frais (§9.4), les pools découverts ; retire tout pool dont le hook modifie les deltas (le projecteur Ξ, appliqué ici pour que la porte qui planifie ne puisse jamais assembler une route que le Router refuserait) ; classe l’union par Ψ en un seul appel groupé et garde `MAX_CANDIDATES = 8` — délibérément plus large que le budget de jambes de n’importe quel hop, de sorte qu’un pool profond listé derrière plusieurs places minces est vu, pondéré et peut les déloger au lieu d’être affamé par l’ordre de la liste. Une égalité exacte de Ψ se départage en faveur des frais les plus bas, de sorte que, sur un registre froid, l’entonnoir est fonction du prix plutôt que de l’historique.

Chaque candidat est ensuite sondé une fois, au centième de l’ordre (`amountIn / 100`, ou l’ordre entier quand cela s’arrondit par défaut à zéro), ce qui capture son taux marginal et sa profondeur dans le même appel. Les taux marginaux séparent nettement deux signaux que les taux en taille réelle confondent : un pool au prix périmé a un taux marginal faux quelle que soit la taille, tandis qu’un pool petit mais sain a un taux marginal correct et seulement une mauvaise sortie en taille réelle. Pour les types à forme de paire, la profondeur déclarée est plafonnée par ce que le pool détient physiquement du token de sortie, de sorte qu’une paire synthétique déclarant $3 \times 10^{30}$ tout en détenant un token de chaque côté est pesée à ce qu’elle détient.

### §10.3 La bande de crédibilité

Avant l’allocation, les candidats doivent être crus, et la crédibilité doit être ancrée sur une quantité dont la falsification coûte quelque chose. Le centre de la bande est la **médiane pondérée par la profondeur** des taux marginaux des survivants (`_depthWeightedMedian`) : les taux sont triés avec leurs profondeurs qui les accompagnent, et la médiane est le taux auquel la moitié de la *masse de profondeur* a été dépassée. Son point de rupture est de 50 % de la masse — pour la déplacer, un attaquant doit dépasser en profondeur la moitié de la liquidité réelle de la paire — et les profondeurs qu’elle pondère sont `getReserves` pour les pools à réserves, la liquidité active pour les pools concentrés et la liquidité mesurée pour V4 : des quantités qu’un simple don à un pool ne fait pas bouger. Cette dernière propriété explique pourquoi l’ancre n’est pas le plus grand solde réel : un transfert vers un pool gonfle `balanceOf` sans déplacer la réserve ni le prix et peut être récupéré par le donateur, ce n’est donc pas du capital à risque — un chercheur externe a démontré la capture de l’ancienne ancre fondée sur le solde par un don, et l’ancre a été remplacée. Les candidats dont le taux marginal s’écarte du centre de plus de `MEDIAN_FILTER_BPS = 500` (±5 %) sont écartés avant que quoi que ce soit ne soit envoyé ; un ensemble d’un seul candidat n’est pas filtré du tout, et un ensemble dont le centre s’arrondit à zéro se replie sur la médiane simple, afin qu’un hop par ailleurs routable ne soit pas tué par un artefact d’arrondi. La largeur est un choix calibré : les courbes stables Solidly qui cotent des paires de liquid staking se situent typiquement à 20–40 % du taux réel et sont exclues ; un pool sain dont la lecture pour cette taille d’échange diffère un peu du centre est admis. La voie métamorphique du §15 a mesuré la bande de l’extérieur : avec un pool coté à $10^{10}$ de son pool frère, le plan paie 35 % de moins que le pool aberrant seul — la médiane refusant de le croire.

### §10.4 L’allocateur et ses trois garde-fous

Les poids sont proportionnels à la profondeur mesurée, normalisés par rapport à la plus grande profondeur de l’ensemble (`_weights`) — une seule normalisation, aucun mode, parce que la profondeur de chaque famille est libellée en tokens (§4.2) et donc comparable. L’allocation proportionnelle est le choix robuste pour un environnement adverse : elle ne demande ni itération ni modèle de courbure par place, et elle évite l’échec catastrophique que produit une répartition uniforme — dans un cas WETH/USDC mesuré, répartir 50 / 50 entre un pool de 790 WETH et un pool de dust de 0.34 WETH a rendu 1,302 USDC là où concentrer dans le pool profond rendait 1,638 ; l’allocation pondérée par la profondeur a rendu 1,638. Quand l’entonnoir contient plus de survivants que le budget du hop, ce sont les survivants les mieux placés *par poids*, dans la limite du budget, qui sont gardés, de sorte qu’un pool profond listé tard déloge des pools minces listés tôt. Trois garde-fous se dressent alors entre l’allocation et la route qu’elle devient.

- **L’écrêtage de capacité.** La formule de liquidité concentrée à tick unique modélise la liquidité du tick courant comme si elle couvrait tous les prix, si bien que, sur un pool mince, elle peut promettre plus que ce que le pool a jamais détenu — 117× plus dans un cas observé. Une jambe concentrée sur une place à forme de pool (types 1 et 6) est donc écrêtée à `MAX_CONC_DRAIN_BPS = 3,000` — 30 % — du solde réel *mesuré* du pool en token de sortie. L’écrêtage n’atteint pas les jambes V4 : le singleton détient les tokens de tous les pools dans un seul solde, donc le Solver saute la lecture pour les types 4 et 8, et une promesse V4 ne porte aucune borne physique au-delà du plancher du §11.3 — le plancher est sa borne. L’écrêtage est à deux niveaux : quand le devis dépasse la totalité des avoirs du pool, la promesse est physiquement impossible, et l’écrêtage réduit l’*entrée engagée* de la jambe dans le même rapport, en reportant en cascade l’entrée libérée sur les jambes restantes et en laissant tout ce qui ne peut être routé être restitué à l’appelant ; quand l’exécution est agressive mais possible, seule la promesse est plafonnée, car on a mesuré que réduire le capital dans ce cas force des exécutions partielles sur des pools qui s’exécutent correctement. Les formules bornées par les réserves ne peuvent pas sur-promettre et ne sont pas touchées. Cet écrêtage lit `balanceOf` délibérément, là où la bande ne le fait pas : la bande pose une question *relative* qu’un don peut fausser, l’écrêtage pose une question *physique* — ce pool peut-il payer cela ? — et un don augmente ce que le pool peut réellement payer.
- **La barrière de répartition.** Chaque jambe supplémentaire coûte du gas réel, et l’allocateur ne contient délibérément aucun terme de prix du gas, car comparer du gas en token natif à une sortie dans le token de l’échange exigerait précisément l’oracle de prix que le protocole refuse d’avoir. Le substitut sans oracle est un seuil : une répartition n’est conservée que si $\mathrm{out}_{\mathrm{split}} \ge \mathrm{out}_{\mathrm{single}} \cdot (1 + 25/10^{6})$ — `MIN_SPLIT_IMPROVEMENT_PPM = 25` — sinon l’allocation se réduit à une seule jambe. Le seuil est un nombre mesuré : le vrai point mort entre le gas d’une jambe et son amélioration a été mesuré à 3 ppm sur Base (320 octets de calldata par jambe supplémentaire à 105.5 gas par octet), et la barrière se situe à environ huit fois cela, exprimée en parties par million parce qu’un quart de point de base n’existe pas en points de base ; son historique — 20 bps, puis 5, puis 0.25 — est la trace de deux valeurs qui n’avaient jamais été calibrées sur une mesure. Quand la barrière réduit une route, elle mesure deux candidats à jambe unique en taille réelle plutôt qu’un seul — le pool le plus profond et le meilleur taux marginal — car les taux marginaux flattent les pools peu profonds alors que la profondeur est l’indicateur de la capacité à porter toute la taille ; la meilleure exécution l’emporte.
- **L’assurance par la mesure, pas par la marge.** Une conception antérieure rognait de 5 bps chaque jambe des routes plus longues au moment de la planification, comme assurance contre la dérive entre jambes. La dérive a ensuite été mesurée sur neuf paires de Base dans le même bloc : pire cas −2 bps, un ordre de grandeur sous l’assurance, que chaque route honnête à plusieurs jambes payait à chaque échange — 27× son risque mesuré. Le rognage a été retiré ; les planchers, imposés sur la sortie réalisée, portent ce risque à coût de planification nul. La mesure a été prise dans une fenêtre calme et le contre-argument est consigné dans le code source : si la prudence est un jour rétablie, sa bonne place est le plancher, qui est vérifié, et non l’estimation, qui ne fait que classer.

La route assemblée porte ce que le Router re-dérivera : l’entrée engagée et la sortie attendue de chaque jambe, l’impact de la route pondéré par les parts, et le plancher Φ que le Solver atteste à partir de la même arithmétique que celle qu’impose le Router (§11). Le Solver évalue aussi le coût d’exécution de chaque jambe selon son type à partir de l’échelle `THETA_GAS` et publie le total sous le nom `estGas`, un chiffre indicatif que la route transporte et que rien on-chain ne lit. La règle empirique du lecteur survit inchangée à toute cette machinerie : comparez l’amélioration que revendique le routeur au gas qu’affiche votre portefeuille, avant de confirmer.

## §11 La couche d’exécution et le plancher Iron-Law Φ

Le Solver propose ; le Router dispose — atomiquement, ou pas du tout. Le Router est le seul contrat qui déplace des fonds, il n’en détient aucun entre les transactions, et il est construit sur un principe : un mécanisme par préoccupation, appliqué universellement. Il ne suppose rien de ce qu’il peut mesurer.

### §11.1 Quatre portes, un exécuteur

| Porte | Signature (abrégée) | Autorisation | Route |
|---|---|---|---|
| `swapExactIn` | `(route, amountIn, userMinOut, recipient, deadline)` | `approve` + `transferFrom` classique | depuis le calldata |
| `swapExactInWithPermit2` | `(route, amountIn, userMinOut, recipient, deadline, permit, signature)` | transfert par signature Permit2 en une étape, via le contrat Permit2 canonique ; aucune autorisation de dépense permanente accordée au Router | depuis le calldata |
| `swapExactInNative` | `(route, userMinOut, recipient, deadline)` payable | valeur native, encapsulée une seule fois à la porte ; à échec fermé tant que `weth` n’est pas câblé (`RouterE(3)`) | depuis le calldata |
| `swapBestExactIn` | `(tokenIn, tokenOut, amountIn, userMinOut, recipient, deadline)` | classique | résolue dans le cadre par le Solver (`findBestRoutePlan`), puis exécutée via un auto-appel où le payeur est transmis explicitement |

Seule `swapBestExactIn` invoque le Solver dans la transaction ; les trois autres prennent la route que l’appelant a calculée off-chain, gratuitement, via le Quoter (§12). Les quatre convergent vers un seul exécuteur privé (`_execute`), de sorte que chaque vérification ci-dessous s’exécute sur chaque porte. Un compte délégué EIP-7702 appelle les mêmes portes que le ferait un contrat ; aucune entrée distincte n’existe pour lui.

Deux refus s’exécutent avant que quoi que ce soit ne bouge. Un minimum nul est refusé à chaque porte — `RouterE(10)`, sans exception, sans drapeau de compatibilité — parce que le minimum est la seule borne que le contrat ne peut pas calculer à la place de l’utilisateur, et qu’un garde-fou qui peut être absent n’est pas un garde-fou. Et une route de plus de trois hops, de plus de cinq jambes dans un hop, ou comportant un hop dont le token d’entrée n’est pas la sortie du hop précédent est refusée avec `RouterE(3)` : la continuité des hops est ce qui rend connu le solde « étranger » d’avant swap de chaque token intermédiaire, de sorte qu’une route forgée ne peut pas désigner comme entrée un token resté bloqué et faire entrer dans le swap, par mise à l’échelle, tout le solde que le Router en détient.

### §11.2 La mesure, dès la première instruction

Quelle que soit la porte utilisée, la première chose que fait le Router est de mesurer ce qui est arrivé : il lit le delta de son propre solde après le prélèvement plutôt que de se fier au montant nominal, de sorte qu’un token qui livre moins lors du transfert entre dans la route à sa taille réelle. Les lignes de base naissent une seule fois : le solde d’avant swap du token d’entrée (`baseIn`) et celui du token de sortie (`toutStart`) sont pris à l’entrée, et chaque mesure ultérieure est un delta par rapport à eux, de sorte que les tokens déjà présents dans le contrat — dust, dons, restes bloqués d’un historique sans rapport — sont exclus de ce qu’un swap peut verser. Une route qui parvient à dépenser sous sa propre ligne de base échoue sur l’arithmétique vérifiée — l’issue sûre, puisque ces fonds n’ont jamais été à la disposition de ce swap.

Une route multi-hop est un plan, et les plans rencontrent la réalité à chaque frontière de hop. Le Router remet à l’échelle les jambes de chaque hop d’après le solde *effectivement* arrivé — le hop 0 d’après l’entrée mesurée après prélèvement, plafonnée par ce que la route a engagé (afin que le Router ne force jamais un ordre déjà réduit en amont dans un pool mince) ; chaque hop suivant d’après le solde de pont mesuré au-dessus de sa ligne de base étrangère — chaque jambe exécutant $\mathrm{amt} = \mathrm{amountIn}_{\mathrm{leg}} \cdot \mathrm{real}/\mathrm{planned}$ (`_hopScaleImpactAndQuote`). Une jambe ramenée à zéro est sautée ; l’entrée non dépensée et les résidus de pont sont restitués au payeur, jamais au Router. La même passe lit une seule fois l’état du pool de chaque jambe et produit, à partir de cette lecture unique, le montant mis à l’échelle de la jambe, son impact mesuré et son devis dans le cadre — les trois quantités que consomment les planchers — de sorte que les familles à réserves et concentrées ne paient aucun `staticcall` supplémentaire pour leur mesure.

### §11.3 Trois planchers sous chaque jambe

**Le plancher par jambe.** La contribution mesurée de chaque jambe au solde de sortie du Router doit atteindre `LEG_FLOOR_BPS = 8,000` — 80 % — de sa borne, sinon l’ensemble du swap est annulé avec `RouterE(5)` (`_execScaled`), de sorte qu’un seul pool sandwiché ou manipulé fait échouer la transaction immédiatement au lieu de cacher sa perte dans un total par ailleurs sain. La borne est le devis attesté de l’appelant, au prorata du montant effectivement dépensé — *relevée* au devis mesuré dans le cadre chaque fois que l’attestation en couvre moins de `MIN_QUOTE_COVERAGE_BPS = 5,000`. C’est un maximum, pas un minimum, à dessein : sur un plancher, `min(claimed, measured)` avec une déclaration dégonflée renvoie la valeur dégonflée et relâche la contrainte, et c’est l’attaque que la barrière ferme. Le garde-fou s’exécute dès qu’il existe une base de plancher — l’attestation de l’appelant ou le devis dans le cadre — de sorte qu’écrire zéro dans une route ne désactive plus le propre plancher de l’appelant. Sa limite : une place sans devis mesurable ni attestation retombe sur les planchers agrégés et le minimum de l’utilisateur, qui la bornent de toute façon.

**Le plancher agrégé par hop.** Le plancher par jambe est local alors que la composition est globale : un attaquant détenant une jambe sur $L$ pourrait extraire environ $20\,\% \cdot (L-1)/L$ d’un hop sans faire échouer aucun plancher individuel. Chaque hop peut donc perdre au plus ce qu’une jambe attestée *moyenne* pourrait légitimement perdre : $\sum \mathrm{got} + \lfloor (\mathrm{BPS} - \mathrm{LEG\_FLOOR\_BPS}) \cdot \lfloor \sum \mathrm{attested} / n \rfloor / \mathrm{BPS} \rfloor \ \ge\ \sum \mathrm{attested}$ — la marge est de 20 % de la jambe attestée moyenne, sinon `RouterE(5)`. La moyenne et non le maximum, car avec un maximum l’attaquant gonfle sa propre jambe pour gonfler le budget partagé. Pour une seule jambe, la règle se réduit exactement au plancher par jambe — aucune rigidité nouvelle, par construction.

**Le plancher de route Φ** fait l’objet du §11.5.

### §11.4 Un callback, un déverrouillage

Chaque place de forme V3 — Uniswap V3, Algebra, les forks Solidly concentrés, Pancake V3, Sushi V3 — utilise le même schéma de comptabilité flash sous un nom de callback différent : le pool rappelle en cours de swap avec un delta pour token0, un delta pour token1 et des données arbitraires, en exigeant un paiement. Plutôt qu’un callback nommé par fork, le Router expose un unique `fallback` qui leur répond à tous — la **Doctrine du Callback Unique** : il lit les deux deltas, identifie d’après leurs signes quel token il doit, et paie exactement ce montant, en prenant le contexte de la jambe en cours dans le stockage transitoire (`TSLOT_POOL`, `TSLOT_TOKEN`, `TSLOT_AMT`). Parce qu’un fallback est accessible à n’importe qui, il est gardé sur trois fronts : il ne paie que le pool engagé pour la jambe courante, il refuse d’agir quand aucune jambe n’est en cours (le contexte transitoire est nul entre les swaps), et il ne libère jamais plus que le budget enregistré de la jambe — une seconde demande au sein d’une même jambe est refusée avec `RouterE(6)`. Un fork V3 nouvellement lancé avec un nom de callback inédit est pris en charge le jour même de son déploiement.

Le singleton V4 est enveloppé derrière la même interface de hop : `unlock`, puis le swap, `sync → settle → take`, puis le reverrouillage, avec les devises transportées dans le stockage transitoire et la couture native du §5.3. Avant que toute jambe V4 ne s’exécute, le Router lit l’adresse du hook et refuse la jambe si ses bits déclarent des permissions modifiant les deltas (`RouterE(9)`) ; le hook filtré est le hook exécuté, parce que le PoolManager canonique renvoie les données de déverrouillage à l’identique (`test/V4SievedHookIsTheExecutedHook.t.sol`). La règle de forme de route est l’ordre canonique, à l’échelle de toute la route : dès qu’une jambe à hook s’est exécutée, aucune jambe sans hook ne s’exécute après elle, dans ce hop ou dans un hop ultérieur (`sawHooked`), de sorte qu’un hook ne peut pas observer une jambe sans hook ultérieure de la même route avant son règlement ; une jambe à hook ne peut se trouver dans un hop antérieur que si toutes les jambes qui la suivent sont elles aussi à hook.

**Les frais de transfert, sur les jambes qui peuvent se le permettre.** Certains tokens livrent moins à chaque transfert. Sur les jambes à produit constant, le Router traite ce cas par le schéma canonique — transférer d’abord, puis lire les réserves du pool et son solde dans le même état post-transfert, et calculer la sortie sur la différence que le pool voit réellement — une quantité correcte par construction, parce que c’est exactement la quantité que vérifiera l’invariant propre du pool. La branche ne s’enclenche que lorsqu’un token est mesuré en déficit (`TSLOT_FOT`), et ce que coûte son enclenchement est énoncé avec elle : le plancher de route dérivé du devis a été calculé sans voir la taxe et est abandonné pour ce swap ; le plancher du protocole est remis à l’échelle par le ratio net mesuré (arrondi au supérieur, le sens prudent) ; les bornes par jambe sont recalculées selon le même ratio ; et le minimum de l’utilisateur devient la contrainte déterminante. Les jambes concentrées ne prennent pas en charge les frais de transfert, par conception — la comptabilité flash doit payer exactement ce qui est dû — donc un token taxé sur une route uniquement V3 est refusé d’emblée avec `RouterE(13)` plutôt que d’échouer à l’intérieur d’un pool.

### §11.5 Le plancher Iron-Law Φ, en entier

Une route qui peut s’exécuter ne doit pas toujours s’exécuter — une route qui rend 60 % de la juste valeur est pire que pas d’échange du tout — donc, sous tout ce qui précède, se trouve un plancher que le protocole dérive lui-même. C’est une fraction de rétention en points de base (Core `ironFloorBpsShv`, `legShaveBps`) :

$$
\mathtt{floorBps} \;=\; \max\!\left(\;9{,}600 - \mathtt{legShv} - \min(\delta,\, 10^{4}) - \sigma_{\ln}/10^{14},\;\; 8{,}000\;\right)
$$
^e15

$$
\mathtt{legShv} \;=\; \sum_{\mathrm{hops}} \left\lfloor 200 \cdot \frac{(\sum_{i} a_{i})^{2} - \sum_{i} a_{i}^{2}}{\sum_{i} a_{i}^{2}} \right\rfloor \;=\; \sum_{\mathrm{hops}} \left\lfloor 200\,(n_{\mathrm{eff}} - 1) \right\rfloor
$$
^e23

Trois constantes le déterminent : `FLOOR_BASE_BPS = 9,600`, `FLOOR_PER_LEG_BPS = 200`, `FLOOR_HARD_MAX_LOSS_BPS = 2,000`. Le plancher part de 96 % pour un swap propre à jambe unique, se relâche d’un point de base par point de base d’impact mesuré $\delta$ et de 200 bps par jambe supplémentaire *effective*, et il est écrêté pour ne jamais descendre sous 8,000 bps : le protocole n’admettra jamais sciemment une route conservant moins de 80 % de sa référence, quelles que soient les entrées. Le rognage de jambes est calculé à partir de la *concentration* de l’échange entre les jambes — $(\sum a)^{2}/\sum a^{2}$, le nombre effectif de jambes, exactement $N$ pour $N$ parts égales et exactement 1 pour une jambe dominante — de sorte qu’une répartition gagne du relâchement en proportion de la réalité de sa répartition, et qu’une jambe portant une part infime gagne un montant infime, qu’elle déclare zéro ou un wei. Par Cauchy–Schwarz, le numérateur ne passe jamais sous zéro. Le terme d’impact est la moyenne, pondérée par les parts, des impacts mesurés des jambes sur la route. Deux propriétés sont énoncées comme partie intégrante de la définition : le terme de volatilité $\sigma_{\ln}$ existe dans la signature et reçoit un zéro littéral à chaque site d’appel — un point d’accroche pour un futur estimateur, actuellement inerte — et l’impact de la famille à réserves exclut les frais alors que celui de la famille concentrée les inclut, chacun sous la forme que lui donne la spécification de sa famille.

Ce qui fait de Φ une contrainte appliquée plutôt qu’une heuristique, c’est l’endroit où il s’exécute et ce sur quoi il porte. La route remise au Router porte des champs indicatifs — un total coté, un plancher attesté — et le Router ne se fie à aucun des deux : à l’intérieur du cadre d’exécution, il mesure l’impact réel de chaque jambe, calcule le rognage de jambes à partir des montants qu’on lui remet, recalcule `floorBps`, et l’applique au **devis dans le cadre du hop final, au montant qui y est effectivement arrivé** — une référence libellée dans le token de sortie, dérivée de l’état des pools lu pendant cette exécution, infalsifiable par le calldata. Le choix de la référence est ce qui permet au garde-fou de se déclencher tout court : une fraction de la sortie *réalisée* ne peut jamais dépasser la sortie réalisée. Quand le hop final ne peut pas être coté dans le cadre — un trou de liquidité au tick courant, une sentinelle de frais — la référence se replie sur le devis attesté du hop, que la barrière de couverture a déjà relevé et que la mesure des frais de transfert a recalculé, de sorte qu’un trou de liquidité just-in-time (JIT) sur un pool mince ne peut pas désarmer le plancher ; l’ancre du plancher est toujours un hop qui a déplacé de la valeur et produit la sortie de la route. La vérification porte sur la sortie brute, avant tous frais côté sortie. Le minimum effectif est un maximum à trois termes,

$$
\mathtt{effMin} \;=\; \max\!\left(\mathtt{userMinOut},\;\; \mathtt{route.singleOutFloor},\;\; \left\lceil \mathtt{finalHopQuote} \cdot \mathtt{floorBps}/10^{4} \right\rceil\right)
$$
^e16

et son asymétrie est tout l’enjeu : les champs fournis par l’appelant peuvent resserrer la protection et ne peuvent jamais la relâcher. Le Solver atteste `singleOutFloor` à partir des mêmes `legShaveBps` et `ironFloorBpsShv` sur les mêmes montants, de sorte que, pour une route construite par le Solver, le plancher attesté et le plancher imposé concordent par construction (`test/FloorParitySolverRouter.t.sol`), et le harnais de régimes du §15 fixe les deux cadres sur chaque ligne générée : avec les frais prélevés sur l’entrée, le plancher imposé se situe dans $[\mathrm{attested} \times (1 - \mathrm{fee}),\ \mathrm{attested}]$ ; avec les frais prélevés sur la sortie, il est égal au plancher attesté.

![FIG-7 — Le plancher Iron-Law : rétention en fonction de l’impact mesuré pour une jambe unique, deux jambes égales et quatre jambes égales ; l’écrêtage dur à 80 % ; le point de l’Exemple détaillé 3 marqué.](https://blazephoenix.xyz/whitepaper/figs/fr/fig-7.png)

**Exemple détaillé 3 — le plancher sur une route à deux hops.** Une route A → WETH → B : le hop 0 répartit 600 A dans le pool P et 400 A dans le pool Q ; le hop 1 envoie le WETH produit à travers un seul pool R. Mesuré dans le cadre : les jambes du hop 0 présentent des impacts de 100 et 60 bps, la jambe du hop 1 de 40 bps, et la moyenne pondérée par les parts sur la route est de 80 bps. Le devis dans le cadre du hop 1, au montant de WETH effectivement arrivé, est de 1,000.000 B.

1. Concentration du hop 0 : $(600 + 400)^{2} = 1{,}000{,}000$ ; $600^{2} + 400^{2} = 520{,}000$ ; $n_{\mathrm{eff}} = 1.923$ ; rognage $= \lfloor 200 \times 480{,}000 / 520{,}000 \rfloor = \lfloor 184.6 \rfloor = 184$ bps.
2. Le hop 1 a une seule jambe : $(\sum a)^{2} - \sum a^{2} = 0$ ; rognage 0. `legShv` total = 184.
3. $\mathtt{floorBps} = \max(9{,}600 - 184 - 80 - 0,\ 8{,}000) = 9{,}336$ : la route doit conserver 93.36 % de sa référence.
4. $\mathtt{protocolFloorOut} = \lceil 1{,}000.000 \times 9{,}336 / 10{,}000 \rceil = 933.600$ B.
5. Avec `userMinOut` = 990 B fixé par le trader et `singleOutFloor` = 933.6 B attesté par le Solver, `effMin` = 990 B — la propre borne de l’utilisateur est celle qui s’applique, comme il se doit sur une route honnête. Une livraison de 998.4 B est réglée ; une livraison de 985 B est refusée avec `RouterE(5)`, et la transaction entière, swap de chaque pool compris, n’a jamais eu lieu.

Si le trader avait fixé `userMinOut` = 900 B, le plancher du protocole se serait appliqué à 933.6 B : une exécution à 930 B, 7 % sous la référence dans le cadre, est refusée même si elle franchit le chiffre de l’utilisateur.

### §11.6 Le règlement, et comment le lire

Le règlement ferme le cadre comme il s’est ouvert : par la mesure. Le Router prend comme sortie brute le delta de son token de sortie par rapport à la ligne de base d’entrée (`RouterE(8)` s’il est nul), exécute la vérification de plancher ci-dessus, prélève les frais là où le régime les place (§13), transfère le net au destinataire et mesure le delta de solde *du destinataire*, impose `userMinOut` sur ce montant livré — de sorte qu’un token de sortie à frais de transfert ne peut pas faire glisser l’utilisateur sous sa borne — et lit une seule fois le grand livre des frais : un règlement qui n’a rien payé au protocole est refusé (`RouterE(15)`), et, sur une route ancrée, un second paiement est refusé (`RouterE(16)`). Ensuite, et seulement sur le chemin de succès, il appelle le `recordSwap` du Hub une fois par jambe exécutée (`_recordHits`) avec les montants que le hop a pu dépenser — le ratio mesuré sur déclaré appliqué à chaque jambe — ce qui constitue l’unique écriture externe qu’effectue un swap ; rien n’est écrit quand le plancher rejette.

Chaque règlement émet `ExecutionProof(user, tokenOut, quoted, realized, floorUsed, blockNumber)` : le devis de référence produit dans le même cadre que l’exécution, publié comme une série on-chain que chacun peut re-dériver par un `eth_call` à `findBestRoutePlan` à ce bloc. Ses champs, sans leur faire dire plus qu’ils ne disent : `user` est le payeur, jamais `msg.sender` ; `quoted` est le devis du hop final — il prouve le dernier hop, pas la route ; `realized` est ce qui a été livré, mesuré chez le destinataire ; `floorUsed` est le plancher du protocole qu’il fallait battre. Chaque événement `Fee(token, amount, toT1, toT2)` nomme le token et la répartition des frais.

**Exemple détaillé 6 — lire un règlement.** La route de l’Exemple 3 est réglée et émet `ExecutionProof(0xUSER, B, 1,000.000, 998.400, 933.600, N)` et un `Fee(WETH, 0.014, 0.0042, 0.0098)`.

| Grandeur | Valeur | Lecture |
|---|---|---|
| `quoted` | 1,000.000 B | le devis dans le cadre du hop final, au WETH effectivement arrivé |
| `realized` | 998.400 B | livré au destinataire, mesuré chez lui ; 9,984 bps du devis |
| `floorUsed` | 933.600 B | `floorBps` 9,336 appliqué au devis ; l’exécution s’est située 64.8 B au-dessus |
| `Fee` | 0.014 WETH en un seul événement | le régime ancré : 28 bps des 5.000 WETH produits par le hop 0, payés à l’entrée du hop 1, 30 % à la première trésorerie ; rien n’a été pris sur B |
| distance à `userMinOut` | 8.4 B | la borne de 990 B de l’utilisateur laissait 0.84 % de marge |

Le livré moins le coté est la réponse du marché, pas celle du protocole : les frais sont partis plus tôt, en WETH, et aucun prélèvement côté sortie n’existe sur cette route. Un événement `Fee` libellé dans le token de sortie n’apparaît que sur une route directe vers un token pont (§13).

### §11.7 Les refus du Router

| Code | Signification | Emplacement |
|---|---|---|
| `RouterE(1)` | appel de contrôle non autorisé | `onlyControl` |
| `RouterE(2)` | en pause, ou renonciation tentée pendant la pause | `whenLive`, `renounceControl` |
| `RouterE(3)` | entrée invalide — adresse nulle, porte native non câblée, nombre de hops, jambes par hop, discontinuité de hop | points d’entrée, `_execute` |
| `RouterE(4)` | échéance dépassée | points d’entrée |
| `RouterE(5)` | sortie sous un plancher — par jambe, agrégé par hop, plancher de route ou `userMinOut` | `_execScaled`, `_execute` |
| `RouterE(6)` | callback non autorisé — mauvais pool, aucune jambe en cours, seconde demande | `fallback` |
| `RouterE(7)` | réentrance | `nrEntrant` (verrou transitoire) |
| `RouterE(8)` | échec du swap — sortie nulle, type inconnu, frais égaux ou supérieurs au montant | `_execute`, `_execScaled`, `_chargeHopFee` |
| `RouterE(9)` | le hook V4 modifie les deltas | pré-engagement de la jambe V4 |
| `RouterE(10)` | `userMinOut == 0` avec `amountIn > 0` | chaque porte |
| `RouterE(13)` | token à frais de transfert sur une route uniquement concentrée | `_execute` |
| `RouterE(14)` | sauvetage non mis en file d’attente ou encore dans son délai de 48 heures | `executeRescue` |
| `RouterE(15)` | réglé sans payer les frais du protocole | grand livre des frais, fin de `_execute` |
| `RouterE(16)` | frais payés deux fois sur une route ancrée | grand livre des frais, fin de `_execute` |

Les codes 11 et 12 ne sont pas attribués. Une surface de revert aussi énumérable que l’API est ce qui permet aux tableaux de régimes du §15 de juger chaque ligne selon une seule règle — régler, ou refuser avec l’un de nos sélecteurs — et de qualifier tout le reste de troisième voie.

## §12 Les surfaces de devis, et comment un devis vieillit

Un chiffre appelé « votre devis » peut provenir de deux endroits, et ce sont des chemins de code différents : le **Quoter**, un contrat d’aperçu en lecture seule qu’une interface appelle avant que vous ne signiez, et le **chemin dans le cadre**, où le Solver et le Router calculent les devis à l’intérieur de la transaction en cours d’exécution. Le §4.4 a donné la vérité par type ; cette section donne les surfaces et mesure l’écart entre elles.

### §12.1 Les surfaces du Quoter

| Fonction | Renvoie | Remarques |
|---|---|---|
| `previewPlan(tIn, tOut, amountIn)` | un `Preview` | le plan du Solver, assemblé avec les frais et la marge de sécurité |
| `previewPlanWithMinOut(…, userMinOut)` | un `Preview` | le même, avec `canExecute` jugé par rapport au minimum de l’appelant |
| `previewAndEncode(…)` / `previewAndEncodeWithMinOut(…)` | un `Preview` et le calldata pour `swapExactIn` | la façon dont un intégrateur prend un devis — l’étude du §12.2 utilise exactement celle-ci |
| `batchQuote(entries[])` | jusqu’à `MAX_BATCH = 32` aperçus | une paire qui ne peut pas être planifiée laisse une entrée initialisée à zéro |
| `previewPlanExact(tIn, tOut, amountIn)` | la route et une sortie nette exacte | la Passe Exacte : chaque jambe concentrée exécutée à blanc contre le propre swap du pool |

**Ce qu’est un aperçu.** La struct `Preview` est indicative par conception — les contrôles contraignants sont ceux que le Router re-dérive dans le cadre — et ses champs sont documentés à leur site de définition : `grossOut` reprend le total du plan ; `protocolFee` est l’effet des frais sur la sortie, modélisé par régime exactement comme le Router les prélève (§13) — une fois sur une route ancrée, une fois par hop sur une route épuisée, arrondi au supérieur comme le Router arrondit ; `safetyBuffer` est la marge inter-jambes

$$
s \;=\; \min\!\left(\max(0,\, n-2) + 5a,\;\; 10\right)\ \text{bps}
$$
^e21

nulle à deux jambes ou moins, un point de base par jambe au-delà de deux, plus cinq pour chaque jambe à produit constant cotée sur des frais *supposés* (des frais déclarés que le Core a remplacés sous son plafond), sous un plafond commun de dix, car une marge sans plafond cesse d’être une marge et devient un plancher ; `netOut` vaut $\mathrm{grossOut} - \mathrm{protocolFee} - s$ ; `ironFloor` reprend le plancher attesté ; `topology` et `bridgeUsed` sont dérivés du nombre de hops et de l’ancre de frais qu’utilisera le Router — le même balayage du même producteur, de sorte que l’aperçu et l’exécuteur ne peuvent pas nommer des ponts différents. La règle destinée à l’intégrateur tient en une phrase et c’est le conseil le plus déterminant de ce document : dérivez votre minimum de votre propre anticipation de prix, jamais d’un champ de l’aperçu.

**La Passe Exacte.** Pour un aperçu exact au niveau du pool sur une place concentrée, le Quoter appelle le véritable point d’entrée de swap du pool ; le pool rappelle en exigeant le paiement ; le callback du Quoter, au lieu de payer, provoque un revert — en transportant les deltas calculés dans la charge utile du revert. Le revert défait tout l’état, rien n’est payé, et la sortie décodée est le propre swap de la place sur l’état en direct, exécuté puis annulé ; V4 est traité de façon identique via un unlock dont le callback provoque un revert avec le delta empaqueté. Un pool qui refuse l’exécution à blanc conserve l’affirmation propre du plan, mise à l’échelle par la corde située en dessous — jamais au-dessus, car la sortie d’un AMM est concave. Le hop 0 est plafonné à ce que le plan a engagé, de sorte qu’une route écrêtée par la capacité n’est pas exécutée à blanc à une taille que le Router ne dépensera jamais. Depuis le 2026-09-03, le scalaire renvoyé est net des frais du protocole, déduits une fois comme l’aperçu les déduit, de sorte qu’un minimum qui en est dérivé est honoré par le plancher (`test/QuoterExactNetOut.t.sol`). C’est l’instrument côté aperçu du Quoter ; la surface qui engage est la re-dérivation dans le cadre.

### §12.2 Comment un devis vieillit

Un devis est une promesse portant sur un bloc futur. L’étude le prend comme le fait un intégrateur — `previewAndEncode` renvoie l’aperçu et le calldata —, laisse le monde bouger (de zéro à trois échanges effectués par quelqu’un d’autre à travers les mêmes pools, chacun jusqu’à 3 % de la réserve peu profonde, tous dans le sens de l’utilisateur, de sorte que chacun d’eux lui nuit), laisse passer de zéro à dix secondes, et exécute le calldata inchangé. 240 échantillons sur un univers de trois tokens de mocks à produit constant (A, B = token pont, C ; direct vers le pont, deux hops à travers lui, deux hops dans l’autre sens), chaque issue étant classée (`test/QuoteDelayStatistics.t.sol`) :

| Dérive entre devis et exécution | Échantillons | Réglés | Refusés par le plancher (`RouterE(5)`) | Livré / prédit — moyenne | min |
|---|---|---|---|---|---|
| aucune | 64 | 64 | 0 | 10,000 bps | 10,000 |
| 1 – 100 bps | 43 | 43 | 0 | 9,952 bps | 9,803 |
| 100 – 300 bps | 133 | 102 | 31 | 9,966 bps | 9,653 |

| Délai entre devis et exécution | Échantillons | Réglés | Refusés (`RouterE(5)`) | Livré / prédit — moyenne | min |
|---|---|---|---|---|---|
| 0 s | 17 | 15 | 2 | 9,989 bps | 9,857 |
| 1 – 5 s | 99 | 89 | 10 | 9,959 bps | 9,653 |
| 6 – 10 s | 124 | 105 | 19 | 9,983 bps | 9,659 |

![FIG-8 — Comment un devis vieillit : échantillons réglés et refusés par tranche de dérive, avec le ratio livré/prédit de chaque échantillon réglé ; les mêmes 240 échantillons répartis par délai ne montrent aucune tendance.](https://blazephoenix.xyz/whitepaper/figs/fr/fig-8.png)

Le temps ne fait pas bouger un devis ; la dérive, si. Vérifié sur chaque échantillon : un devis sans dérive est réglé à chaque délai et livre exactement sa prédiction (64 sur 64) ; un règlement ne livre jamais en dessous du plancher attesté par l’aperçu ; il n’y a pas de troisième issue. Après l’échéance (`deadline = now + 5`, exécuté à `+10`), 20 sur 20 sont refusés avec le code propre de l’échéance, `RouterE(4)`, et aucun n’est réglé. Sous une dérive défavorable allant jusqu’à 1 %, chaque devis est encore exécuté, à moins de 2 % de sa prédiction ; sous 1–3 %, le plancher en refuse un sur quatre plutôt que de l’exécuter en dessous de la sortie attestée, et les exécutions tombent à moins de 3.5 % de la prédiction — la courbe du sandwich du §14 vue du côté du devis.

**Base en direct** (`test/fork/QuoteDelayFork.t.sol`, 1,000 USDC → WETH, pools réels, le protocole déployé sur un fork au bloc courant) : exécuté 0, 3, 6 et 10 s plus tard sans que rien d’autre ne bouge, le devis a livré exactement sa prédiction les quatre fois. Avec 10,000 · 50,000 · 200,000 · 1,000,000 · 5,000,000 USDC échangés avant lui à travers la même route et le calldata périmé exécuté 10 s plus tard, les cinq ont été réglés à l’intérieur du plancher, à 9,999 · 9,998 · 9,994 · 9,974 · 10,000 bps de la prédiction : un million de dollars devant mille dollars coûte à ces mille dollars un quart de pour cent, et l’échange de cinq millions a été routé par des pools que la route à mille dollars ne touche pas. Les distributions ci-dessus portent sur des mocks ; Base en direct donne une portée sur une paire à un bloc, pas une distribution.

### §12.3 Ce que coûte un devis via l’ABI

Gas mesuré autour de l’appel externe, quarante tailles pour chacune sur l’univers de trois tokens (`test/QuoterGasStatistics.t.sol`) et cinq tailles sur Base en direct (`test/fork/QuoterGasFork.t.sol`) :

| Monde | `previewPlan` moyenne | min | max | σ | `previewAndEncode` moyenne | σ |
|---|---|---|---|---|---|---|
| mocks — découverte : la paire connue uniquement de la factory, un pool trouvé | 121,313 | 116,468 | 123,491 | 1,433 | 123,317 | 1,851 |
| mocks — registre à jour : trois pools amorcés, cotés | 235,574 | 227,205 | 237,843 | 1,815 | 239,647 | 1,937 |
| Base en direct — à froid : registre vide, factories admises balayées | 1,524,221 | | | | 1,525,341 | |
| Base en direct — à chaud : à jour après une exécution, deux pools enregistrés | 1,414,633 | | | | 1,416,037 | |
| Base en direct — de nouveau à froid après le TTL de découverte | 1,359,925 | | | | 1,361,582 | |

`batchQuote` de dix entrées sur les mocks : 2,055,390 gas, 205,539 par entrée. Le mock à registre à jour coûte plus que la découverte parce qu’il cote trois pools là où la découverte en a trouvé un ; la comparaison honnête est celle en direct, où le balayage de découverte représente environ 7 % d’un devis — et un devis est un appel view qu’un intégrateur ne paie jamais on-chain.

## §13 Politique de frais : les deux régimes et la Règle du Surplus

*En clair :* le protocole prélève 0.28 % une seule fois, sur un seul montant mesuré, dans un token que la trésorerie veut détenir, et compte qu’il l’a fait. *Précisément :* `PROTOCOL_FEE_BPS = 28` est une constante de compilation du Core, sans setter nulle part dans le bytecode déployé ; les frais valent `mulDivUp(base, 28, 10,000)` — arrondis au supérieur, de sorte que des frais nuls sont inatteignables pour toute base non nulle — répartis à raison de `TREASURY1_SHARE = 3,000` bps pour la première trésorerie et du reste pour la seconde (calculé comme `fee − t1`, de sorte que les deux s’additionnent toujours aux frais, sans dust), les trésoreries étant fixées à la construction et gelables pour toujours par la Porte à Sens Unique. Aucune clé ne peut relever le taux au détriment d’un trader et aucune clé ne peut l’abaisser pour un intégrateur favorisé : la question des frais a été tranchée à la compilation et retirée de la liste des choses qu’une clé peut faire.

### §13.1 Où les frais sont prélevés : deux régimes, tous deux nommés

La règle depuis le 2026-08-22 tient en une phrase : **prélever une fois, sur le premier token pont que détient le Router.** Le Router parcourt la route à la recherche du premier hop dont le token d’entrée est un pont enregistré (`hub.isBridgeToken`, dans l’ordre des hops) — ce hop est `feeHop` — et prélève 28 bps de l’entrée *mesurée* de ce hop : au hop 0, le transfert entrant mesuré, plafonné par ce que la route a engagé ; à un hop ultérieur, le solde du token pont détenu par le Router au-dessus de sa ligne de base étrangère, la quantité déjà résistante aux frais sur transfert et aux soldes parasites. Le Router ne prélève ensuite rien d’autre sur cette route. Une route directe dont la destination est elle-même un token pont (TOKEN → WETH) prélève plutôt sur la sortie, après le contrôle du plancher — le plancher valide la qualité du swap, la part du protocole est prise sur le montant déjà validé, et le minimum de l’utilisateur est comparé à ce qu’il reçoit réellement. C’est le **régime ancré**. Pourquoi sur le pont et non sur la destination : les ponts sont WETH et USDC, et la trésorerie reçoit un token liquide qu’elle veut détenir au lieu du dust de n’importe quel token de traîne servant de destination. Pourquoi sur l’entrée du hop 1 et non sur la sortie du hop 0 : ce sont le même token et le même montant, mais l’entrée du hop 1 est mesurée par le solde réel, ce qui est la mesure que le Router effectue déjà.

La règle a un second régime que la phrase n’a nommé qu’à partir du 2026-09-05. Une route construite à la main à travers des pools que le registre ne détiendrait pas, sans token pont en entrée d’aucun hop, ne trouve jamais de `feeHop` ; le prédicat qui déclenche le prélèvement est alors vrai pour *chaque* hop, et chaque hop paie 28 bps sur sa propre entrée mesurée. C’est le **régime d’épuisement**, et c’est une règle délibérée, pas un oubli : prélever une seule fois sur une telle route, au hop 0, a été essayé dans la suite à cette date et a rouvert l’échappatoire que la politique d’épuisement existe pour fermer — un premier hop sans valeur fait porter le point de prélèvement sur du dust et le vrai hop ne paie rien (`test/FeeEscapeViaJunkPrefix.t.sol`) ; cinq tests épinglés ont refusé le changement au cours de la même exécution. Il n’existe aucun indice où insérer du dust qui échappe à chaque hop, et c’est ce que signifie « immunité par épuisement ». Le Solver construit chaque route multi-hop à travers des ponts enregistrés, donc une route construite par le Solver paie toujours exactement une fois ; le régime d’épuisement est ce que rencontre un appelant qui contourne le registre, et le Quoter le modélise (`_pack` prélève `hops.length` fois quand aucune entrée de hop n’est un pont, une fois sinon), de sorte que l’aperçu et la livraison concordent dans les deux régimes (`test/ExhaustionRegimePreviewParity.t.sol`).

![FIG-9 — Les deux régimes de frais tels que le Router les tranche : parcourir les hops à la recherche de la première entrée en token pont ; les routes ancrées paient une fois à ce hop (ou sur la sortie d’une route directe vers un token pont) ; les routes sans entrée pontée paient une fois par hop ; le grand livre compte et refuse zéro, et refuse deux sur une route ancrée.](https://blazephoenix.xyz/whitepaper/figs/fr/fig-9.png)

### §13.2 L’engagement a un seul producteur, et le contrat compte

Deux scellés accompagnent les régimes. La somme des entrées de jambes déclarées d’un hop — la base des frais au hop 0 et le dénominateur de l’échelle — est calculée par une seule fonction, `_legSum` ; avant le 2026-09-05, chacun sommait les jambes dans sa propre boucle : deux producteurs d’un même nombre. Et le grand livre des frais : `_payFee`, la seule fonction qui transfère les frais, compte dans un emplacement transitoire (`TSLOT_FEE`) ; chaque règlement lit l’emplacement une fois, refuse une livraison qui n’a rien payé (`RouterE(15)`) et, sur une route ancrée, un second paiement (`RouterE(16)`), puis l’efface. Les deux contrôles du grand livre sont des *ceintures* : isolément, aucun test ne peut les faire se déclencher, car le prédicat placé devant eux ne laisse aucun chemin qui règle sans payer ou qui paie deux fois sur une route ancrée. Ils sont le contrat qui refuse à l’exécution ce que les tests refusent à la revue, sur des chemins qui n’existent pas encore ; ils sont listés comme ceintures, comptés comme couverts nulle part, et absents de la garde de mutation, qui n’admet que les mutants qu’un test nommé tue.

### §13.3 La Règle du Surplus

La **Règle du Surplus** est l’argument d’alignement de la conception des frais, énoncé tel que le code l’implémente : le prélèvement du protocole est de 28 bps d’une base mesurée, une fois sur une route ancrée et une fois par hop sur une route épuisée, et il n’existe nulle part dans le règlement de terme qui croisse avec la différence entre ce qui a été coté et ce qui a été livré. Là où la base est une entrée — chaque route multi-hop et chaque route directe dont la destination n’est pas un token pont — la sortie est intacte : tout ce que les pools livrent au-dessus du devis parvient intégralement au destinataire, et les frais ne peuvent pas augmenter parce qu’une exécution s’est révélée favorable. Là où la base est la sortie — une route directe vers un token pont — les frais sont de 28 bps de la livraison brute, surplus compris, et rien de plus. L’axe sur lequel un agrégateur est le plus tenté d’écrémer est l’écart entre devis et exécution ; cette conception ne donne à cet écart aucun terme de frais, si bien que le taux affiché est le taux réel et unique. La base n’est jamais lue dans le calldata : `route.totalOut` parvient au Router comme l’affirmation d’un inconnu et n’est lu par rien (`test_FeeBase_IgnoresLiedAboutTotalOut`).

**Exemple détaillé 5 — les deux régimes sur une même route.** Les ponts sont {WETH, USDC} ; le trader vend 10,000 A.

*Ancré, deux hops.* A → WETH → B. Aucun frais n’est prélevé au hop 0 (son entrée A n’est pas un pont). Les pools du hop 0 versent 5.000 WETH, mesurés comme le solde WETH du Router au-dessus de sa ligne de base étrangère. `feeHop` = 1 : les frais valent $\lceil 5.000 \times 28 / 10{,}000 \rceil = 0.014$ WETH, répartis en 0.0042 pour la première trésorerie et 0.0098 pour la seconde, et le hop 1 dépense 4.986 WETH vers B. Un seul événement `Fee` ; le grand livre lit 1 et est satisfait ; l’aperçu a déduit 28 bps une fois.

*Ancré, direct vers un pont.* A → WETH en un hop. Rien n’est prélevé sur l’entrée. Les pools livrent 5.000 WETH brut ; le plancher est contrôlé sur 5.000 ; puis les frais côté sortie, $\lceil 5.000 \times 0.0028 \rceil = 0.014$ WETH, sont payés et 4.986 WETH sont livrés ; `userMinOut` est comparé à 4.986. Un seul événement `Fee`, libellé dans le token de sortie.

*Épuisement, deux hops.* A → C → B où aucun de A, C ou B n’est un pont — une route qu’aucun Solver ne construit. Le hop 0 paie $\lceil 10{,}000 \times 0.0028 \rceil = 28$ A et envoie 9,972 A dans ses pools, qui versent 2,000 C ; le hop 1 paie $\lceil 2{,}000 \times 0.0028 \rceil = 5.6$ C et envoie 1,994.4 C vers B. Deux événements `Fee` ; le grand livre lit 2 sans `feeHop`, donc `RouterE(16)` ne s’applique pas ; le taux effectif est $1 - 0.9972^{2} = 55.9$ bps, et l’aperçu l’indiquait.

### §13.4 Les frais, mesurés depuis l’extérieur du Router

`test/FeeSeals.t.sol` ne lit aucun des chiffres du Router : le token des frais découle de la règle et de la liste des ponts, la base des deltas de solde des pools (ce qui a quitté le Router vers le hop des frais, ou ce que les pools du hop précédent ont versé) ou de celui du destinataire, les frais des deltas des trésoreries, le décompte des événements `Fee` — sur toutes les formes que le Router accepte (un à trois hops ; pont en aucune position, en première, au milieu ou en dernière ; une ou deux jambes par hop), montants fuzzés de $10^{12}$ à $10^{21}$ wei, 2,000 exécutions, aucun échec, le Router ne détenant rien après chaque règlement.

| Forme de la route | Régime | Où tombent les frais | Mesuré |
|---|---|---|---|
| A → B (aucun des deux n’est un pont) | épuisement, un hop | l’entrée du hop 0, une fois | frais = ⌈28 bps × entrée⌉, un événement |
| W → A (pont en entrée) | ancré | l’entrée du hop 0 | idem |
| A → W (pont en sortie, direct) | ancré, côté sortie | la sortie | frais = ⌈28 bps × sortie brute⌉, livré = brut − frais |
| A → C → B (aucun pont) | épuisement, deux hops | l’entrée mesurée de chaque hop | deux événements |
| W → A → B | ancré au hop 0 | l’entrée du hop 0 | un événement |
| A → W → B | ancré au hop 1 | le token pont produit par le hop 0 | un événement, ⌈28 bps × ce que les pools du hop 0 ont versé⌉ |
| A → C → W (pont seulement comme sortie de deux hops) | épuisement | l’entrée de chaque hop | deux événements |
| A → C → D → B · A → W → C → B · A → C → W → B | comme ci-dessus | comme ci-dessus | 3 · 1 · 1 événements |

**La fréquence à laquelle les tests de frais remarquent un défaut** a été mesurée plutôt que supposée (`docs/assurance/fee-seal-detection.json`) : chaque mutant de frais — le prédicat de régime déplacé dans un sens ou dans l’autre, le producteur de l’engagement, les deux contrôles du grand livre et les trois mutants de frais de l’étude d’invariants — a été exécuté sous vingt graines de fuzz par test fuzzé et une fois par test déterministe ; le taux de détection est rapporté avec un intervalle de Wilson à 95 % et, là où rien n’a été manqué, la borne de la règle des trois sur la probabilité de non-détection.

| Mutant | Fuzz FeeSeals | Campagne du Router (2 hops, 2 jambes) | Tableau de couverture t = 2 | Échappatoire par préfixe parasite | Parité de l’aperçu en épuisement |
|---|---|---|---|---|---|
| l’épuisement ne prélève que le hop 0 | 20/20 [0.84, 1.00] | 20/20 [0.84, 1.00] | non | oui | oui |
| l’épuisement saute le hop 0 | 20/20 [0.84, 1.00] | 20/20 [0.84, 1.00] | non | oui | oui |
| l’engagement ne compte que la première jambe | 20/20 [0.84, 1.00] | 20/20 [0.84, 1.00] | non | non | non |
| frais doublés | 20/20 [0.84, 1.00] | 20/20 [0.84, 1.00] | oui | oui | oui |
| frais côté entrée jamais prélevés | 20/20 [0.84, 1.00] | 20/20 [0.84, 1.00] | non | oui | oui |
| frais prélevés des deux côtés | 20/20 [0.84, 1.00] | 20/20 [0.84, 1.00] | non | oui | oui |
| ceinture : règlement sans frais qui n’est plus refusé | 0/20 [0.00, 0.16] | 0/20 [0.00, 0.16] | non | non | non |
| ceinture : double paiement ancré qui n’est plus refusé | 0/20 [0.00, 0.16] | 0/20 [0.00, 0.16] | non | non | non |

![FIG-10 — Taux de détection de chaque mutant de frais par chaque test, sous forme de carte de chaleur : mutants tués sur exécutions, avec l’intervalle de Wilson ; les deux ceintures apparaissent comme les lignes que rien ne voit.](https://blazephoenix.xyz/whitepaper/figs/fr/fig-10.png)

Lisez une ligne : quels tests voient ce défaut. Lisez une colonne : ce qu’un test peut et ne peut pas voir. La première mesure a trouvé la campagne du Router aveugle au mutant d’épuisement qui épargne le hop 0 et au producteur de l’engagement, parce qu’elle ne construisait que des routes directes à une jambe ; la campagne a été élargie le jour même pour parcourir deux hops et répartir le hop 0 sur deux pools, avec un token pont dans l’univers pour qu’une seule campagne contienne chaque régime, et les deux sont désormais vus 20 sur 20. Le tableau de couverture ne voit que les frais doublés, parce que ses lignes traversent toujours un pont. Vingt graines à 20 sur 20 bornent à 15 % la probabilité de non-détection d’une campagne isolée, c’est pourquoi la garde de mutation exécute à la fois le test nommé et la campagne.

Pour résumer la section sous la forme qu’utilise ce document — affirmation, point d’application, limite : personne ne peut changer le taux ni la répartition, car aucun des deux n’a de setter dans le bytecode déployé ; un appelant ne peut pas réduire les frais en sous-évaluant un devis, car la base est un solde mesuré et `route.totalOut` n’est lu par rien ; un appelant ne peut pas esquiver les frais avec un préfixe sans valeur, car une route sans entrée pontée paie à chaque hop ; et un règlement ne peut pas ne rien payer, ni payer deux fois sur une route ancrée, car le Router compte — avec cette limite que les deux contrôles propres du compteur sont des ceintures qu’aucun test ne peut faire se déclencher.

## §14 Modèle de sécurité

*En clair :* le Router ne détient rien entre les transactions, chaque swap est tout-ou-rien, et chaque classe d’attaque que ce dépôt a envisagée a pour réponse un contrôle nommé ou est déclarée hors périmètre. *Précisément :* le catalogue des menaces est `docs/assurance/threats.json`, 23 classes tirées de l’historique public des exploits ; chacune nomme le symbole de garde qui la refuse, et un script de CI fait échouer le build si le symbole quitte `src/` ou si le test nommé quitte `test/`. La couverture d’un catalogue est un plancher de ce qui a été envisagé, jamais un plafond de ce qui existe.

### §14.1 Les attaques et les contrôles qui les refusent

| Classe (id) | Le contrôle qui la refuse | Où il est appliqué | Statut | Le test qui montre que le contrôle peut échouer |
|---|---|---|---|---|
| Réentrance (DASP-1) | verrou transitoire `nrEntrant` sur chaque porte qui déplace de la valeur ; la surface de callback authentifie son appelant | Router `nrEntrant` → `RouterE(7)` ; `fallback` → `RouterE(6)` | bloqué | `invariant_reentrancyBlocked` (`test/RouterAdversarialV4FromV1.t.sol`) |
| Contrôle d’accès (DASP-2) | quatre rôles ; chaque porte privilégiée gardée par une barrière ; le niveau de contrôle renonçable pour toujours | Hub `onlyAdmin` / `onlyControl` → `HubE(1)` ; Router `onlyControl` → `RouterE(1)` | bloqué | `test/HubAllowHookAfterRenounce.t.sol`, `test/ControlEvents.t.sol` |
| Dépassement arithmétique par le haut ou par le bas (DASP-3) | arithmétique vérifiée partout ; chaque bloc `unchecked` porte une borne écrite ; la représentabilité des courbes difficiles testée à la limite (`MAX_SQRT_PRICE_MINUS_ONE`) | Core | bloqué | `test/BoundaryEquality.t.sol`, `test/CompilerPanicSurface.t.sol` |
| Déni de service par itération non bornée (DASP-5) | chaque dimension de route bornée : `MAX_HOPS = 3`, `MAX_LEGS_PER_HOP = 5`, `MAX_LEGS = 11`, `MAX_CANDIDATES = 8`, `MAX_SLOTS = 16`, `MAX_FACTORIES = 16` | Router `_execute` → `RouterE(3)` ; Solver ; Hub → `HubE(4)` (factories), `_canInsert` (emplacements) | bloqué | `test/RouteHopCeiling.t.sol` |
| Dépendance à l’ordre des transactions / sandwich (DASP-7) | le plancher Iron-Law, écrêté en dur à `FLOOR_HARD_MAX_LOSS_BPS = 2,000`, appliqué indépendamment du minimum de l’appelant, qui ne peut que le resserrer | Core `ironFloorBpsShv` ; Router `_execute` → `RouterE(5)` | bloqué (borné, §14.3) | `test/regime/SandwichCurve.t.sol` ; `invariant_DeliveredNeverBelowTheProtocolFloor` |
| Réentrance inter-fonctions via une place (SWC-107) | le même verrou transitoire couvre tout le swap, callbacks des pools compris | Router `nrEntrant` | bloqué | `test/V4LockedRegionReentrancy.t.sol` |
| Manipulation d’oracle de prix (SOK-ORACLE) | il n’y a pas de flux de prix : le devis de référence est produit dans le cadre d’exécution à partir des places échangées, et le plancher en est dérivé (`ironFloorBps`) | Core ; Router `_execute` | bloqué | `test_Parity_SplitRoute_AttestedFloorEqualsEnforcedFloor` (`test/FloorParitySolverRouter.t.sol`) |
| Quantité fournie par l’appelant substituée à une quantité mesurée (SOK-FEE-BASE) | la base des frais est un solde mesuré (`_chargeHopFee`) ; `route.totalOut` n’est lu par rien | Router `_chargeHopFee` | bloqué | `test_FeeBase_IgnoresLiedAboutTotalOut` (`test/BlazePhoenixRouter.t.sol`) |
| Tokens à frais sur transfert et à rebasing (SOK-FOT) | chaque montant qui compte est un delta de solde mesuré (`balanceOf`), jamais un montant de transfert supposé | Router, chaque couture | bloqué | `test/FotFloorReprice.t.sol`, `test/PartialFotAtPrePulledDoors.t.sol` |
| Place hostile admise au registre (SOK-VENUE-ADMISSION) | l’admission est réservée au curateur, une ligne par adresse ; l’origine de dérivation est attestée à l’admission (`factoryDeployer`) et gelée après la renonciation | Hub `addFactory`, `_probe` | bloqué | `test_C4_FirstPin_AfterRenounce_MustNotAttestTheLiveAnswer` (`test/T19ReadmissionEdge.t.sol`) |
| Place admise dont la logique change derrière un proxy (SOK-PROXY-VENUE) | réadmission après renonciation refusée quand le runtime a changé (`factoryCodehash`) ; là où un proxy peut changer sa réponse sans changer son code, c’est la réponse qui est épinglée | Hub `addFactory` → `HubE(1)` | bloqué | `test/FactoryCodehashPin.t.sol`, `test/RenouncedFactoryRearm.t.sol` |
| Ligne de place convertie de dérivée en interrogée après ossification (SOK-MODE-FLIP) | une ligne de dérivation ne peut pas devenir une ligne d’interrogation une fois `controlRenounced` ; le resserrement dans l’autre sens reste autorisé | Hub `addFactory` | bloqué | `test_C4_S3_DeriveRowMustNotBecomeAnAskRowAfterRenounce` (`test/T19ReadmissionEdge.t.sol`) |
| Métrique publiée reprenant la déclaration de l’appelant (SOK-VOLUME) | le registre reçoit le montant que le hop a pu dépenser, issu du ratio mesuré sur déclaré (`hopScale`) | Router `_recordHits` | bloqué | `test_INV_F4_VolumeInEqualsTheMeasuredPoolDelta` (`test/VolumeEventFidelity.t.sol`) |
| Aperçu qui ne prédit pas l’exécution (SOK-QUOTE-EXEC) | aperçu et livraison vérifiés égaux dans les deux régimes de frais (`previewRoute`, `_pack`) | Quoter ; Router | bloqué | `test_INV_F2_PreviewPredictsDeliveryWithNoBridge` (`test/ExhaustionRegimePreviewParity.t.sol`) |
| Dérivation déterministe orientée vers l’adresse d’un attaquant (SOK-V4-DERIVE) | la préimage CREATE2 a un seul producteur (`create2Address`) ; un id de pool dérive de sa propre clé ; `hasCode` et la preuve de vivacité écartent ce qui ne se résout pas | Core ; Hub `_probe`, `_scanV4` | bloqué | `test/ConditionAdequacyCore.t.sol`, `test/KindIsDerivedNotDeclared.t.sol` |
| Hook malveillant sur une place à hook (SOK-HOOK) | trois couches, §14.2 : bits d’adresse immuables, liste d’autorisation avec épinglage du codehash (`hookAllowed`, `isHookLive`), le projecteur Ξ | Router → `RouterE(9)` ; Hub `allowHook` → `HubE(8)` ; Solver `_topKPools` | bloqué | `test/V4SievedHookIsTheExecutedHook.t.sol`, `test/SeedPoolHookMustBeAllowed.t.sol`, `test/RenouncedHookRearm.t.sol` |
| Épuisement du registre par admission répétée (SOK-GRIEF-SEATS) | une ligne par adresse ; un ajout répété rafraîchit sur place au lieu de consommer un siège (`MAX_FACTORIES`) | Hub `addFactory` → `HubE(4)` | bloqué | `test/RenouncedFactoryRearm.t.sol` |
| Appel externe arbitraire ou puits de transfer-from (SOK-ARBITRARY-CALL) | l’exécuteur n’accepte jamais de cible d’appel fournie par l’appelant ; l’interaction avec les places est répartie depuis un ensemble fermé de formes sélectionnées par type (`_execScaled`), et un type inconnu provoque un revert | Router `_execScaled` → `RouterE(8)` | bloqué | `test/regime/HostileVenueMatrix.t.sol` |
| Rejeu de signature sur une approbation déléguée (SOK-SIG-REPLAY) | l’approbation déléguée est gérée par le Permit2 canonique (`permitTransferFrom`) ; le système ne détient aucun schéma de signature propre | Router `swapExactInWithPermit2` | bloqué | `test/RouterPermit2OneStep.t.sol` |
| Capture de la gouvernance (SOK-GOV) | il n’y a pas de gouvernance : ni vote par token, ni timelock, ni proxy ; les seuls pouvoirs privilégiés sont l’admission des places et la mise en pause, toutes deux renonçables | — | hors périmètre | — |
| Mise à niveau malveillante (SOK-UPGRADE) | les contrats sont immuables ; il n’y a ni proxy ni emplacement d’implémentation | — | hors périmètre | — |
| Compromission d’un pont ou d’une place externe (SOK-BRIDGE-EXT) | les places tierces sont composées, non contrôlées ; leur défaillance est bornée ici par le plancher et le minimum de l’appelant, et leur sécurité interne n’est pas revendiquée | — | hors périmètre | — |
| Compromission de la clé privée d’un utilisateur (SOK-KEY) | hors de la frontière de confiance ; consignée parce que les données d’incidents publiées lui attribuent une large part des pertes | — | hors périmètre | — |

Dix-neuf des vingt-trois classes ont pour réponse une garde nommée ; quatre sont hors périmètre, avec la raison consignée. Le catalogue est recoupé dans l’autre sens par une clôture des actifs (`assets.json`) : six choses que le système détient ou décide — les fonds en transit, l’autorisation de dépense déléguée, les frais du protocole, l’intégrité du registre, les soldes bloqués, le gas de l’appelant — croisées avec cinq façons dont une chose peut être perdue, soit trente cellules, chacune se résolvant en une classe de menace, une justification de hors-périmètre ou la mention explicite *ouvert*. Dix-neuf cellules sont couvertes, trois sont hors périmètre, cinq ne sont pas applicables, et trois sont ouvertes et publiées comme telles : le griefing visant la dénomination des frais, le blocage de dust pour rendre la récupération non rentable, et un appelant qui rencontre l’une de trois paniques atteignables du compilateur au lieu d’un refus nommé. Aucune des cellules ouvertes n’est un chemin vers les fonds de quiconque d’autre.

### §14.2 Hooks hostiles : trois couches, la moins chère d’abord

Un hook est du code arbitraire qui s’exécute à l’intérieur du règlement qu’effectue le Router, et la défense naïve — lui demander ce qu’il va faire — n’en est pas une, car un hook hostile ment ou réentre. Trois couches indépendantes se dressent à la place, ordonnées de sorte que la moins chère et la plus certaine s’exécute en premier ; elles bornent l’altération des deltas, et le seul résidu qu’elles n’atteignent pas — un hook sans delta qui remplace ses frais dynamiques (§5.4) — est borné à la place par Φ et le minimum de l’appelant.

**Couche 1 — les bits de permission vivent dans l’adresse, et l’adresse ne peut pas mentir.** V4 encode les permissions d’un hook dans les 14 bits de poids faible de sa propre adresse de contrat ; ces bits sont fixés au déploiement par le sel CREATE2, et le PoolManager lui-même vérifie ces mêmes bits avant d’invoquer le hook, de sorte qu’un hook ne peut pas avoir une permission que son adresse ne déclare pas. Exactement deux bits permettent à un hook de modifier la comptabilité du swap, et le Router les masque d’un seul AND avant que le moindre token ne bouge (`hookAltersDeltas`) :

$$
\left(\mathrm{uint160}(\mathrm{hook})\ \&\ \mathtt{0x3FFF}\right)\ \&\ \left((1 \ll 3) \mid (1 \ll 2)\right) \ne 0 \;\;\Longrightarrow\;\; \text{leg refused, } \mathtt{RouterE(9)}
$$
^e18

Aucun appel à du code non fiable, rien à tromper, et le contournement exigerait une adresse dont les bits contredisent l’application faite par le gestionnaire lui-même. Un proxy peut remplacer chaque ligne de sa logique sans gagner pour autant un bit que son adresse n’a jamais eu — c’est pourquoi ce refus se situe à distance de projection zéro (§15.9), alors que l’épinglage du codehash situé en dessous ne s’y situe pas.

**Couche 2 — une liste d’autorisation, fermée par défaut, avec le code épinglé.** Un hook aux bits propres peut encore provoquer un revert malveillant pour nuire ou brûler du gas, donc un pool V4 n’est routable que si son hook figure sur la liste d’autorisation du curateur, et le Hub épingle le codehash du hook à l’admission : un hook dont le runtime change ensuite est mis en pause automatiquement sans éviction (`isHookLive` faux) et ne reprend que s’il est réadmis — un pouvoir qui, après renonciation, est refusé pour tout hook dont le code a changé. La limite de l’épinglage est énoncée à son point d’application : il lie le code runtime, donc il détecte un redéploiement ou une mutation directe et ne détecte pas un proxy délégué dont l’implémentation est remplacée alors que son propre runtime reste identique octet pour octet, car l’EVM ne donne à un contrat aucun moyen de lire le stockage d’un autre. Admettre un hook pouvant être mis à niveau relève d’un jugement humain que cet épinglage ne peut pas remplacer. Les couches qui n’en dépendent pas tiennent quoi qu’il en soit.

**Couche 3 — la route n’est même jamais montrée.** Le projecteur Ξ retire de l’ensemble des candidats du Solver, avant le classement, tout pool dont le hook altère les deltas (`_topKPools`), et le canal de lecture du Hub ne sert que des hooks actifs ; une route qui touche un hook inadmissible ne participe pas à la maximisation. Une règle de forme de route complète le dispositif : ordre canonique, sur toute la route — aucune jambe sans hook ne s’exécute jamais après qu’un hook a été exécuté (§11.4).

![FIG-11 — Trois couches contre les hooks hostiles, la moins chère d’abord : le masque de bits de l’adresse (aucun appel à du code non fiable), la liste d’autorisation avec l’épinglage du codehash (fermée par défaut), le projecteur Ξ (route non représentable). Chaque couche répond à une capacité différente ; le résidu — un hook propre et listé qui provoque un revert pour gaspiller du gas — est nommé.](https://blazephoenix.xyz/whitepaper/figs/fr/fig-11.png)

Mettre en échec cette pile exige un hook qui ne porte aucun bit de delta, figure inchangé sur la liste d’autorisation et subvertit pourtant le règlement — une combinaison que le recours de la Couche 1 à l’application immuable propre à V4 exclut pour l’altération des deltas ; le résidu de remplacement des frais du §5.4 demeure, et le plancher le borne. Le résidu est énoncé dans le tableau : un hook listé et sans delta peut encore provoquer un revert en plein swap et faire perdre le gas de la tentative. Et le levier unique du tamis est nommé : le hook tamisé lie le swap exécuté parce que le PoolManager canonique renvoie les données d’unlock telles quelles et ne rappelle que l’appelant de son unlock ; le `setV4Manager` du Hub, un pouvoir de contrôle, est la seule adresse sur laquelle repose cette hypothèse, et un gestionnaire malhonnête fait s’effondrer la sortie mesurée, de sorte que la borne propre de l’appelant refuse le swap (campagne `HostileV4Manager`) — borné, non absent.

### §14.3 Borné, non éliminé : obsolescence, ordonnancement et la courbe du sandwich

Un devis est un énoncé sur le passé, et l’échange a lieu dans le futur. La porte de résolution dans le cadre ferme complètement la moitié de cet écart située côté opérateur — la route est dérivée dans la transaction qui l’exécute, à partir des réserves telles qu’elles sont dans ce bloc. Ce qui reste est la moitié propre à la chain : le pool peut bouger entre le moment où vous signez et celui où votre transaction est incluse. Personne ne peut l’empêcher depuis l’intérieur d’un contrat, car l’ordre est décidé avant que le code ne s’exécute ; un protocole qui prétend avoir éliminé le front-running a soit retiré votre échange de la file publique, en faisant confiance à quiconque le détient désormais, soit décrit quelque chose de plus étroit qu’il n’y paraît. Ce que fait ce protocole, c’est le borner, deux fois et indépendamment : votre minimum plafonne le dommage au chiffre que vous avez choisi, et le plancher Iron-Law — re-dérivé par le Router à partir de l’impact mesuré et du devis dans le cadre du dernier hop, et non à partir de quoi que ce soit que l’attaquant ou la route aurait fourni — le plafonne de nouveau pour le compte propre du protocole.

La borne est mesurée du côté de l’attaquant (`test/regime/SandwichCurve.t.sol`) : la route et le plancher de la victime sont fixés au moment du devis, comme dans une transaction en attente ; l’attaquant échange une fraction de la profondeur du pool avant la victime, la victime exécute, puis l’attaquant effectue l’échange inverse. La place est une paire à produit constant — la forme qu’utilise tout modèle de sandwich — donc la courbe est une propriété du plancher. Sur un échange de 1 % de la profondeur (10,000 contre 1,000,000 par côté) :

| L’attaquant déplace | Victime | Perte de la victime par rapport au devis | Aller-retour de l’attaquant |
|---|---|---|---|
| 0.1 % de la profondeur | réglé | 0.47 % | +13.9 |
| 0.5 % | réglé | 1.26 % | +68.9 |
| 1 % | réglé | 2.22 % | +136.7 |
| 2 % | réglé | 4.12 % | +268.7 |
| 3 % et au-delà | **refusé** | 0 | −174.5 … −544.9 |

![FIG-12 — La courbe du sandwich du côté de l’attaquant : la perte de la victime par rapport au devis et l’aller-retour de l’attaquant, selon la fraction de la profondeur déplacée avant la victime ; le bord de refus près de 3 %, fermé vers le haut.](https://blazephoenix.xyz/whitepaper/figs/fr/fig-12.png)

La garantie vérifiée en chaque point : une victime dont le swap est réglé ne reçoit jamais moins que le plancher attesté au moment du devis, donc la perte est bornée par la distance entre le devis attesté et le plancher attesté ; et la région de refus est fermée vers le haut — au-delà du bord, toute manipulation plus grande est refusée et l’attaquant se retrouve avec le prix qu’il a déplacé sur les bras. Le chiffre qui mérite d’être cité est la dernière ligne réglée : le plancher plafonne ce qu’un sandwich peut prendre sur un échange de 1 % à environ 2.7 % de celui-ci, et transforme l’échange de l’attaquant en perte dès qu’il prendrait davantage. La route dans le cadre ajoute un effet dissuasif plus discret : il n’existe aucun chemin publié à l’avance qu’un searcher pourrait étudier, car le chemin n’existe pas avant le bloc qui l’exécute. Un minimum large est une invitation ; un minimum serré est, dans cette section, le contrôle le plus efficace qui vous appartienne.

### §14.4 Places hostiles

Les pathologies de place ont leur propre matrice (§15.5) : dix comportements fautifs — un pool qui ne paie rien, qui paie la moitié, qui renvoie une bombe de returndata de 64 KiB, qui brûle tout le gas à la lecture ou au swap, un token dont `decimals()` ne retourne jamais, un callback de paiement déclenché deux fois, un pool qui réentre dans le Router avant de payer, un `slot0()` qui provoque un revert, une factory qui répond avec un pool sur d’autres tokens — croisés avec la porte calldata et la porte de résolution, 20 cellules sur 20 qui règlent avec un montant livré égal au delta de solde et rien de laissé sur le Router, ou qui refusent avec un sélecteur qui nous est propre. Un swap qui brûle tout le gas transmis est la seule cellule qu’aucun appelant ne peut trancher pour son appelé ; la transaction est annulée en entier et le solde de l’utilisateur reste intact. Chaque lecture gardée que le Core adresse à une place est plafonnée à `GAS_CAP = 100,000` gas, donc une place qui brûle du gas sur une *lecture* coûte un montant borné et est écartée, sans être fatale.

### §14.5 L’interface hostile

Sur tous les agrégateurs jamais construits, l’interface propose le minimum, et une interface négligente le propose mal — aucun contrat ne peut savoir ce que vous auriez choisi. Ce qu’un contrat peut faire, c’est rendre la proposition prouvable : chaque swap émet `ExecutionProof` — le devis utilisé, le montant livré, le plancher appliqué — de façon permanente, on-chain, si bien que la qualité d’exécution de toute interface est un relevé public, transaction par transaction, que chacun peut auditer, vous compris. Une interface qui propose systématiquement de mauvais minimums écrit son propre acte d’accusation dans le journal des événements. La défense qui complète le mécanisme est une habitude : regardez le minimum avant de signer ; c’est le seul champ qui vous appartient entièrement.

### §14.6 Ce qu’une clé hostile peut et ne peut pas faire

Avant la renonciation, des clés administratives existent, et la façon honnête de les décrire est par capacité, en supposant un détenteur hostile ; l’énumération complète des pouvoirs se trouve au §16.4.

**Ce que peut faire une clé hostile du Router :** rediriger la destination des frais (`setTreasuries`) ; mettre les swaps en pause (`setPaused`) ; repointer les contrats WETH et Permit2 qu’appellent la porte native et la porte Permit2 (`setWeth`, `setPermit2`), ce qui rend ces deux portes seulement aussi dignes de confiance que les adresses définies ; mettre en file puis, 48 heures plus tard, exécuter un sauvetage de tokens bloqués dans le Router (`queueRescue`, `executeRescue`, `RESCUE_DELAY = 48 hours`, annoncé on-chain par `RescueQueued`). **Ce qu’elle ne peut pas faire :** changer le taux des frais, la répartition ou les planchers, car ce sont des constantes de compilation sans setters ; prendre le capital, car il n’y a ni proxy, ni chemin de mise à niveau, ni `selfdestruct`, ni approbation permanente des fonds des utilisateurs au Router, et le Router ne détient rien au repos — les fonctions qui le feraient ne sont pas désactivées, elles sont absentes.

**Ce que peut faire une clé hostile du Hub :** lister une place ou un hook (`addFactory`, `allowHook`), amorcer des pools (`seedPool`, via un siège d’opérateur), mettre en pause l’apprentissage du registre (`setPaused`), repointer la vue qu’a le Hub du Router, du Solver et du Quoter (`setRoles`), et repointer le gestionnaire V4 (`setV4Manager`, le levier unique du tamis). **Ce qu’elle ne peut pas faire :** mal coter une exécution, car le registre ne décide que de ce qui est vu (§9.6) — ce qu’une inscription hostile peut atteindre de plus tranchant, c’est l’écart entre un devis et *votre minimum* ; le capital est hors de portée, votre tolérance de slippage ne l’est pas, ce qui est une raison de plus pour que vous fixiez vous-même un minimum serré.

### §14.7 La Porte à Sens Unique

L’autorité administrative prend fin de façon irréversible, dans le bytecode, et non par une promesse. Le Hub et le Router exposent tous deux `renounceControl`, et chacun refuse d’être appelé pendant une pause (`RouterE(2)`, `HubE(2)`) : un protocole mis en pause puis ayant renoncé au contrôle serait un état terminal que personne ne veut et dont personne ne peut sortir, et la mise en pause vaut précisément la peine parce que le contrôle est conservé pour agir. Le drapeau de renonciation est écrit à `true` en exactement un endroit de chaque contrat et à `false` en aucun, donc aucune fonction du bytecode déployé ne peut rouvrir la porte — la différence entre « nous ne le ferons pas » et « nous ne le pouvons pas ».

Ce qui se ferme et ce qui reste ouvert est énuméré, non résumé. Sur le Router, tout ce qui relève de `onlyControl` meurt : `setAdmin`, `setTreasuries`, `setPermit2`, `setWeth`, `setPaused`, le chemin de sauvetage — les trésoreries, le câblage et le drapeau de pause se figent pour toujours à leurs valeurs actuelles, et le Router continue d’exécuter des swaps sous cette configuration fixe. Sur le Hub, le **niveau de contrôle** meurt : `setRoles`, `setOperator`, `setPaused`, `setV4Manager`, `removeBridge`, et la *désinscription* des hooks. Le **niveau curateur** survit, et ne fait qu’agrandir le registre : `addFactory` (qui refuse, après renonciation, toute factory dont le runtime a changé ou toute ligne de dérivation qui deviendrait une ligne d’interrogation), `addBridge` (ajout seulement, trois au plus, idempotent), `allowHook(h, true)` (qui refuse un hook listé dont le code a changé) ; les sièges d’opérateur existants conservent `seedPool` et `addV4`, et aucun nouveau siège ne peut être accordé. Une inscription malveillante après renonciation ne peut rien drainer : les pools sont validés au devis et à l’exécution, et bornés par le plancher et le minimum de l’appelant. Le registre continue d’apprendre en échangeant. Quiconque répète « renoncé » à propos de ce protocole devrait pouvoir dire quelles portes restent ouvertes — admission, ponts, inscription de hooks, amorçage curaté — et lesquelles se ferment : tout pouvoir qui redirige ou gèle.

### §14.8 Ce que ce modèle attend de vous

Le protocole ferme ce qu’un contrat peut fermer : falsification d’adresse, usurpation de callback, falsification comptable, hooks hostiles, arithmétique. Il borne ce qu’aucun contrat ne peut éliminer : l’obsolescence et l’ordonnancement, par votre minimum et le plancher. Et il publie des preuves contre ce qu’il ne peut même pas borner : une interface qui propose de mauvais minimums en votre nom. Chaque ligne converge vers le même champ. Fixez votre minimum vous-même, délibérément, à chaque fois — c’est la seule protection de cette section dont la qualité ne dépend de la compétence de personne d’autre que vous.

## §15 L’appareil de vérification, mesuré

*En clair :* chaque chiffre ici est publié à côté du dénominateur par rapport auquel il a été mesuré, est recalculé à partir d’un checkout propre par un script ou par une commande `forge` nommée (Annexe C), et est suivi de ce qu’il n’établit pas. *Précisément :* l’arbre mesuré est `main` à `8949a9d` et la branche qui l’est devenue, sur le profil de release (`optimizer_runs = 300`, `via_ir`), 2026-09-05.

### §15.1 La suite en chiffres

| Quoi | Nombre | Comment il est compté |
|---|---|---|
| tests déclarés | 1,609 fonctions `test*` / `invariant*` / `check*` réparties sur 236 fichiers `.t.sol` | `grep` sur `test/` |
| au vert sur le profil de release, suites de fork exclues | 1,319 réussis · 0 en échec · 1 ignoré, sur 1,320 | `forge test --no-match-path 'test/fork/**'` |
| au vert sur la liquidité réelle | 119 sur 119 sur les 39 suites de la voie fork, cinq chains ; plus les trois tests en conditions réelles sur Base du §12 | `forge test --match-path 'test/fork/**'` avec un RPC d’archive |
| invariants à état | 43 fonctions `invariant_*` dans 14 campagnes | `grep -rc 'function invariant_' test` |
| mutants sélectionnés, tous éliminés | 268 sur 268 | `.github/scripts/mutants.py` ; `check_targets.py` |
| tableaux de couverture des régimes | force 2 : 63 lignes, toutes les 258 paires · force 3 : 168 lignes, tous les 1,636 triplets · sur 5,184 combinaisons | `covering_array.py --check` |
| tailles livrées (octets de runtime ; limite EIP-170 24,576 ; barrière du projet 24,000) | Hub 23,648 · Router 23,781 · Solver 19,686 · Quoter 11,429 · Core 6,442 | `FOUNDRY_PROFILE=release forge build --sizes` |

### §15.2 La chaîne de preuves, et la garde de mutation sélectionnée

Une propriété ne vaut que ce que vaut le plus court chemin qui mène d’une menace à quelque chose qui échouerait si la propriété cédait : `threat → property → guard → test → mutant`. Une garde est désignée par son symbole, un test par son nom, un mutant par la ligne exacte qu’il modifie ; si l’un des trois quitte l’arbre, le build échoue. Chaque test de régression de la suite a été vu échouer une fois, pour la raison qui lui donne son nom — un correctif arrive avec le test qui était rouge contre le code dépourvu de ce correctif.

La garde de mutation contient 268 mutants écrits à la main, chacun associant une ligne exacte du code source à l’unique test qui doit échouer dès que cette ligne est modifiée — une garde supprimée, une comparaison inversée à la borne qui décide d’un refus, une autorisation élargie, un code d’erreur échangé avec celui de son voisin. Trois propriétés en font une garde plutôt qu’un score : le test associé est d’abord exécuté au vert sur l’arbre non muté ; une mutation que l’optimiseur supprime — bytecode de runtime identique — est signalée comme *inerte*, jamais comme éliminée ; et une vérification statique d’une seconde contrôle que chaque mutant pointe encore sur exactement une ligne. 203 sur 203 sont éliminés. Ce chiffre est une adéquation par rapport à *ce* registre : un score saturé est un plancher, pas un plafond.

### §15.3 Mutants visant les invariants

Jusqu’au 2026-09-05, aucun des mutants sélectionnés ne désignait un invariant à état comme le test qui doit tomber sous son effet. Les 43 fonctions `invariant_*` réparties sur 14 campagnes étaient au vert, et rien n’avait demandé si l’une d’elles pouvait passer au rouge : une campagne dont le gestionnaire n’atteint jamais l’état qu’une propriété protège certifie cette propriété sur un univers vide, et paraît identique, vue de l’extérieur, à une campagne qui l’atteint. La mesure est celle de la garde, pointée sur les campagnes : modifier une garde dans le code source, exécuter chaque invariant sur une graine, noter lesquels le remarquent (`docs/assurance/invariant-mutants.json`).

| Mutant | Ce qu’il supprime | Remarqué par |
|---|---|---|
| le transfert final paie un wei de moins | ne rien détenir | `RouterHoldsNothing`, `routerHoldsNothing`, `HoldsNothingBeyondTheSeed` |
| les frais du protocole doublent | le plafond des frais | `FeeNeverExceedsProtocolMax` |
| les frais côté entrée ne sont jamais prélevés | le plancher des frais | `FeeNeverEscapes`, `DeliveredNeverBelowUserMinOut` |
| les frais sont prélevés des deux côtés | un seul prélèvement, d’un seul côté | `FeeIsChargedOnExactlyOneSide` |
| 30 % de chaque prélèvement de frais part vers une adresse morte | la conservation | `conservationA/B/C/H`, `TokenConservation`, `LedgerConservationA` |
| la garde de paire des jambes admet une jambe sur la mauvaise paire | des hops homogènes | `DivergentLegNeverSettles`, `HoldsNothingBeyondTheSeed`, `StrandedMoneyIsNeverSwept` |
| `whenLive` ne vérifie plus `paused` | la pause | `PausedRouterNeverSettles` |
| le verrou de réentrance ne refuse plus | le verrou | `reentrancyBlocked` |
| le plancher du protocole est divisé par deux | le plancher | `DeliveredNeverBelowTheProtocolFloor` — fermé le 2026-09-05 |
| `MAX_SLOTS` devient 17 | la borne du registre | `neverExceedsMaxSlots` |
| la preuve de paire à l’enregistrement est supprimée | l’authenticité de la paire | `ActiveEntriesTradeThePair` — fermé le 2026-09-05 |
| le balayage du résidu d’entrée ignore sa ligne de base | les fonds bloqués | `StrandedMoneyIsNeverSwept`, `HoldsNothingBeyondTheSeed` |
| le balayage du résidu de pont ignore sa ligne de base | les fonds bloqués | la même paire — fermé le 2026-09-05 |
| la mesure de la sortie ignore sa ligne de base | les fonds bloqués | la même paire |
| la vérification de `userMinOut` après frais est divisée par deux | — | survit, par conception : une sentinelle de régression inatteignable dans les univers de campagne sans token de sortie à frais sur transfert ; deux mutants unitaires la surveillent |

![FIG-13 — Mutants visant les invariants : quinze mutations du code source contre quarante-trois invariants à état, les invariants passés au rouge pour chaque mutant, les trois fermés le 2026-09-05 en donnant à une campagne l’action qui lui manquait, et l’unique sentinelle qui survit par conception.](https://blazephoenix.xyz/whitepaper/figs/fr/fig-13.png)

Onze sur quinze ont été remarqués à la première mesure ; vingt noms d’invariants distincts sont passés au rouge au moins une fois. Trois des quatre survivants ont été fermés le jour même en donnant à une campagne l’action qui lui manquait — des pools étrangers proposés sous la paire, une route à deux hops sur un solde intermédiaire préalimenté, une baleine déplaçant le pool de 0–8 % contre une route cotée avant que la route périmée ne s’exécute — chacun vérifié rouge sans sa garde ; deux gardes sans aucun surveillant d’aucune sorte, tel était le chiffre que cette mesure existait pour publier, et il est désormais de zéro. Quatorze sur quinze sont remarqués ; les quatorze sont des entrées de garde associées à l’invariant qui tombe sous leur effet. Une suite au vert contenant une garde non surveillée était la forme des deux régressions documentées dans cette base de code ; la mesure qui en détecte une s’exécute désormais sur chaque garde que les campagnes revendiquent.

### §15.4 Tableaux de couverture des régimes

Les critères de couverture indexent le code ; la mutation indexe une faute injectée ; aucun des deux n’indexe l’état qu’une *fixture* fixe avant l’appel. Dix facteurs de ce type sont énumérés — la famille de place (V2, V3, Solidly), les hops (1–3), les jambes par hop (1–2), si le token d’entrée est un pont enregistré, si l’intermédiaire l’est, la forme des frais sur transfert (aucune, au prélèvement seulement, à chaque transfert), les décimales du token d’entrée (18, 6), la porte (route en calldata, résolution dans la transaction, Permit2), le contrôle (actif, renoncé) et si la paire est pleine — soit 5,184 combinaisons. `covering_array.py` génère des tableaux dans lesquels chaque paire (force 2) ou chaque triplet (force 3) de valeurs de facteurs apparaît dans au moins une ligne, chaque ligne étant une fixture passée par un harnais de test (`test/regime/RegimeHarness.sol`) avec une seule assertion : le swap se règle, avec un montant livré égal au delta de solde du destinataire, au moins égal au plancher émis par le Router, et rien ne reste sur le Router — ou il est refusé avec un sélecteur à nous. Un panic, un sélecteur étranger, une sous-livraison ou un solde bloqué constituent une troisième voie et font échouer la ligne.

| Force | Lignes | Tuples couverts | Réglées | Refusées, sélecteur à nous | Non constructibles | Troisième voie |
|---|---|---|---|---|---|---|
| 2 | 63 | 258 paires | 53 | 4 | 6 | 0 |
| 3 | 168 | 1,636 triplets | 158 | 10 — `SolverE(5)` ×7, `RouterE(13)` ×3 | 0 | 0 |

![FIG-14 — Tableaux de couverture des régimes : issue de chaque ligne générée à la force 2 (63 lignes) et à la force 3 (168 lignes) — réglée, refusée avec un sélecteur à nous, non constructible, troisième voie — sur 5,184 combinaisons de dix facteurs.](https://blazephoenix.xyz/whitepaper/figs/fr/fig-14.png)

Les refus correspondent au planificateur qui n’a aucun chemin ponté à construire et à l’exécuteur qui décline un token taxé sur une route uniquement concentrée ; les six lignes à la force 2 qui ne peuvent pas être construites (le token taxé au prélèvement seulement n’a pas de forme à six décimales) sont publiées nommément et comptent dans le dénominateur. Les familles absentes du tableau — V4, V4 natif, Algebra, la porte native, les hooks — sont indiquées dans `regimes-covering.json`. La première exécution du tableau a rendu explicite un cadre que les tests de parité n’avaient épinglé que d’un seul côté : le plancher qu’impose le Router est égal au plancher attesté lorsque les frais sont prélevés sur la sortie, et se situe dans $[\mathrm{attested} \times (1 - \mathrm{fee}), \mathrm{attested}]$ lorsqu’ils sont prélevés sur l’entrée — les deux cadres font désormais l’objet d’une assertion sur chaque ligne. Le fichier généré est vérifié contre son générateur en CI, de sorte que le tableau ne peut pas dériver des facteurs qu’il prétend couvrir.

### §15.5 La matrice des places hostiles

Les pathologies de token avaient leurs tests ; les pathologies de place n’en avaient aucun dans une matrice. `test/regime/HostileVenues.sol` contient un mauvais comportement par place et `HostileVenueMatrix.t.sol` croise chacun avec la porte calldata et la porte de résolution selon la règle du tableau de couverture :

| Place | Porte calldata | Porte de résolution |
|---|---|---|
| ne paie rien / paie la moitié | refusé `RouterE(5)` | refusé `RouterE(5)` |
| bombe de returndata sur la lecture des réserves | se règle | se règle |
| la lecture des réserves brûle tout le gas | refusé `RouterE(8)` | le planificateur ne la sélectionne jamais |
| le swap brûle tout le gas | toute la transaction revient en arrière, solde intact | toute la transaction revient en arrière, solde intact |
| `decimals()` brûle tout le gas | se règle | se règle |
| callback de paiement déclenché deux fois | refusé `RouterE(6)` | refusé `RouterE(6)` |
| réentre dans le Router avant de payer | se règle ; le swap imbriqué ne s’est jamais exécuté | se règle ; le swap imbriqué ne s’est jamais exécuté |
| `slot0()` revient en arrière | se règle sur le devis attesté | le planificateur ne la sélectionne jamais |
| la factory répond avec un pool sur d’autres tokens | jamais listé | jamais listé |

Vingt cellules sur vingt : elles se règlent ou refusent avec un sélecteur à nous, jamais par une troisième voie. La dernière ligne est ce que la première exécution de la matrice a changé — la découverte listait un pool par lequel avait répondu une factory admise par un curateur, et l’exécuteur le refusait à la couture qui paie ; un pool interrogé prouve désormais sa propre paire avant que la découverte ne le liste (§7).

### §15.6 Oracles canoniques

Chaque mock de la suite cote avec les propres formules du Core, de sorte qu’un défaut dans une formule est invisible pour tous les tests de parité qui les utilisent : l’oracle est l’objet. `test/regime/CanonicalOracles.t.sol` contient trois implémentations écrites à partir des invariants publiés des places — le produit constant avec les frais sur l’entrée, le pas V3 en prix racine carrée au sein d’un seul tick avec l’arrondi propre au pool, en défaveur du trader, la courbe stable Solidly résolue par la méthode de Newton — et confronte le Core à elles par fuzzing, 5,000 exécutions chacune, en vérifiant d’abord le sens (le Core ne promet jamais plus que ce que livrent les mathématiques de la place) et ensuite l’étroitesse.

| Famille | Sens | Étroitesse mesurée |
|---|---|---|
| Uniswap V2 | le Core ne dépasse jamais la spécification | exact au wei près |
| Solidly stable | le Core ne dépasse jamais la courbe de plus que le dernier pas du solveur lui-même | à 4 wei près |
| Uniswap V3 | le Core ne dépasse jamais la spécification de plus d’une unité en dernière position (ulp) du prix racine carrée, qui vaut $L / 2^{96}$ wei | moins d’un wei pour tout pool avec $L < 2^{96}$ |

La borne V3 est exprimée dans la quantité qui la cause — le pool arrondit son nouveau prix en défaveur du trader et le Core l’arrondit une fois, si bien que les deux peuvent différer d’une unité de $\sqrt{P}$ — et l’assertion porte sur la borne, pas sur l’échantillon (13 wei sur une sortie de $6.5 \times 10^{34}$ à $L = 10^{30}$). Les oracles sont écrits à partir des spécifications, pas à partir du bytecode des places ; c’est leur limite.

### §15.7 Relations métamorphiques

Un oracle écrit à partir de la même formule ne peut pas prendre la formule en défaut. Une relation métamorphique demande plutôt comment la sortie doit *bouger* quand l’entrée bouge, et la propre courbe de la place y répond sans implémentation de référence. `test/CoreMetamorphicRelations.t.sol` contient quatorze relations sur les mathématiques de devis du Core et `test/RouteMetamorphicRelations.t.sol` cinq sur le plan du Solver, dans un univers à deux pools avec un vrai Hub et un vrai Solver :

| Niveau | Relation | Ce qu’elle dit |
|---|---|---|
| Core, V2 · V3 · stable | MR1 monotone | plus en entrée, jamais moins en sortie |
| Core, V2 · V3 · stable | MR2 sous-additive | répartir un ordre sur le même pool n’est jamais gagnant |
| Core, V2 · stable | MR3 pas d’aller-retour | un aller, puis un retour sur les réserves mises à jour, ne rend jamais plus que ce qui est entré |
| Core, V2 · V3 · stable | MR4 équivariance d’échelle | mettre à l’échelle l’ordre et le pool ensemble met la sortie à l’échelle, à l’arrondi près |
| Core, V2 · V3 | MR5 frais monotones | des frais plus élevés ne paient jamais plus |
| Core, V3 | MR6 symétrie de sens | au prix 1.0, les deux branches de `outV3` concordent à deux ulps près |
| Core, Solidly | MR7 identité | la branche volatile *est* `outV2`, un seul producteur |
| plan | MR-R1 | la répartition ne paie jamais moins que ce que paierait le meilleur pool unique |
| plan | MR-R2 | monotone en `amountIn` |
| plan | MR-R3 | ajouter un pool à l’intérieur de la bande de prix n’abaisse jamais le plan |
| plan | MR-R4 | l’ordre d’enregistrement déplace le plan d’au plus une unité de poids de la répartition |
| plan | MR-R5 | le plancher attesté ne dépasse jamais la sortie attendue |

Trois des bornes ont été mesurées avant d’être écrites, et le chiffre de l’assertion est le mécanisme, pas l’échantillon. MR6 : à $L = 8.5 \times 10^{37}$ et 1,374 wei en entrée, une branche a coté 1,073,741,823 wei — exactement $L / 2^{96}$, un ulp — tandis que l’autre a coté zéro, son pas arrondi à rien, en échouant fermé ; la relation énonce deux ulps, un par branche. MR-R1 et MR-R3 : avec un pool dont le prix s’écartait de $10^{10}$ de celui de l’autre, le plan a payé 35 % de moins que le pool aberrant seul — la médiane pondérée par la profondeur refusant d’y croire — si bien que les relations valent sur les pools que la bande admet, et le domaine maintient les deux prix à ±4 % l’un de l’autre. MR-R4 : un pool dont le poids de profondeur est arrondi par défaut au minimum est conservé comme jambe de dust lorsqu’il est enregistré en premier et coupé lorsqu’il est enregistré en second, ce qui déplace le plan de $3 \times 10^{-7}$ de la sortie — le maintien ou la coupe, par la répartition, d’un pool de poids minimal dépend de sa position, et c’est désormais écrit là où cela a été trouvé.

### §15.8 N-version : le même code source sous une autre génération de code

La suite exécute un seul binaire — celui qu’émettent les réglages de release, et `profile_parity.py` maintient la suite sur ces réglages. Tout autre réglage de l’optimiseur est un programme différent compilé à partir du même code source, et le compilateur est un composant. `test/nversion/` compile les mathématiques de devis du Core sous deux profils supplémentaires — `optimizer_runs` 1 et 20,000, tout le reste identique — et `NVersionCore.t.sol` déploie les trois binaires côte à côte et vérifie leur égalité sur des entrées fuzzées : `outV2`, `outV3`, `outSolidly`, `outSolidlyStable`, `mulDiv`, `mulDivUp`, 3,000 exécutions chacune sur tout le domaine d’entrée. La voie vérifie d’abord que le bytecode alternatif *diffère* de celui qui est livré, de sorte qu’elle ne peut pas réussir en comparant un programme avec lui-même, et un artefact manquant est un échec, pas un test ignoré.

**Les trois binaires concordent.** Le constat ne portait pas sur le compilateur. La première exécution de la voie sur tout le domaine a fait échouer la *référence* par un revert : le test d’ajustement de la courbe stable calculait la division `max · WAD / b`, et cette division elle-même débordait sur un pool détenant moins d’une unité d’un token ; une seconde échappée — une réserve d’entrée de baleine face à une réserve de sortie de dust — passait la vérification de l’invariant et faisait déborder la dérivée. Aucun des deux n’était un chiffre faux ; tous deux étaient des reverts à l’intérieur d’un devis, et `universalQuote` est un `DELEGATECALL` de bibliothèque, si bien qu’un pool enregistré détenant un wei du token de sortie défaisait le plan du Solver pour sa paire. La réparation (§6) pose la question dont la division tenait lieu et a été intégrée avec un test écrit d’abord en rouge ; une garde supplémentaire — un plafond sur l’itéré de Newton — a été écrite, jugée inobservable par quelque test que ce soit une fois les deux vérifications en place, et supprimée plutôt que conservée. Sa classe est celle que ce dépôt nomme en premier — l’échec ouvert — trouvée par un instrument qui ne la cherchait pas.

### §15.9 Distance de projection

Chaque défaut confirmé dans ce dépôt, interne ou signalé, a une seule forme : une vérification observe un objet tandis qu’un objet *différent* décide du comportement. Cela fait que la propriété intéressante d’un refus n’est pas sa complexité mais sa distance — à quel point ce qu’il lit est éloigné de ce qui décide. `projection_distance.py` classe le prédicat qui garde chaque `revert` des cinq contrats : une constante, un immutable, un delta de solde ou une quantité calculée dans le cadre à partir de l’un d’eux est à distance 0 ; un champ de la route de l’appelant, ou une consultation du stockage mutable d’un autre contrat, est à distance 1 ; une propriété d’un contrat qui peut déléguer son comportement est non bornée. Sur 99 sites de refus : **86 à distance 0, 11 à distance 1, 2 non bornés.** Les deux sites non bornés sont tous deux des épinglages `EXTCODEHASH` — le code d’un hook admis et celui d’une factory admise — et ils ne sont pas symétriques : pour les hooks, le résidu est fermé par les bits d’adresse que l’épinglage ne porte pas (§14.2) ; pour les factories, il n’existe pas d’équivalent, et le résidu est borné par l’admission des pools, le plancher par jambe et le minimum de l’utilisateur, de sorte que le dommage est une dégradation de la route plutôt qu’un vidage, ce qui est documenté sur place. La limite du crible : il lit un seul prédicat et ne peut pas voir qu’une valeur a été contrainte trois cadres plus haut ; un champ déclaré par l’appelant qui est aussi le sélecteur du pool n’est à distance 0 que parce qu’une affectation les lie, et cette affectation est épinglée par `test/V4SievedHookIsTheExecutedHook.t.sol`.

### §15.10 L’indépendance des preuves, le registre des quantités partagées, et l’artefact

Pour chaque garde porteuse, l’appareil enregistre combien de confirmations *indépendantes* existent — par source, oracle, outil, environnement et méthodologie — et rapporte le minimum, car une chaîne de preuves cède à son maillon le plus faible ; les propriétés porteuses sont confirmées par au moins deux voies qui ne partagent aucune hypothèse. Le registre des quantités partagées (`SHARED_QUANTITIES.md`) énumère chaque quantité ayant plus d’un producteur ou d’un consommateur, énonce la *question* à laquelle elle répond, et note ce qui lie les copies — `SINGLE`, `PINNED`, `WEAK`, `OPEN`, `UNVERIFIED` — avec une règle de CI qui rétrograde une ligne dont le test n’atteint pas ce qu’il prétend épingler ; chaque quantité à deux producteurs du registre relationnel (7 sur 7) est liée par un test nommé. Et l’artefact compilé est lu, pas seulement le code source : les opcodes qui ne doivent jamais apparaître (`SELFDESTRUCT`, `CALLCODE`, `ORIGIN`) ; les opcodes transitoires toujours présents dans l’exécuteur, de sorte que le verrou de réentrance n’est pas discrètement devenu du stockage persistant ; chaque fonction déclarée présente dans le répartiteur ; trois des cinq contrats n’émettant aucune écriture de stockage ; 88.3 % du flux d’instructions de la forme livrée prouvé exécuté par une borne inférieure sûre, vérifiée contre un contrat de vérité terrain que l’instrument doit classer correctement avant de publier ; et, pour un swap V2 honnête enregistré, rejoué sur le Router de release avec l’opcode vérifié à chaque pas, 4,382 instructions du Router et 407 instructions du Hub exécutées, zéro divergence — 28.2 % et 2.5 % des deux sections de code, le complément étant du code qu’aucun scénario enregistré n’a exécuté, et non du code mort.

### §15.11 Ce que rien de tout cela n’établit

- **Aucune probabilité d’exactitude.** La littérature sur la validation d’une sûreté de fonctionnement ultra-élevée est explicite : les tests ne peuvent pas en produire une ; aucune n’est énoncée ici.
- **L’adéquation de mutation est une adéquation par rapport à ce registre.** Il est sélectionné à la main ; un score saturé est un plancher.
- **Les couvertures des menaces et des régimes sont des planchers sur ce qui a été envisagé.** Les classes et les facteurs que personne n’a nommés sont hors du dénominateur par construction.
- **Les distributions du §12 portent sur des mocks.** Base en conditions réelles apporte la portée, pas une distribution.
- **Les taux de détection sont par test, à 256 exécutions de fuzz par graine.** Un test qui rate à ce budget peut détecter à un budget plus élevé.
- **Ce dépôt est la V2, et la V2 n’est pas déployée** (§19).

Chaque instrument ci-dessus a été étalonné avant que son chiffre ne soit publié : exécuté d’abord contre un défaut déjà connu par une autre voie, et corrigé là où il le manquait — un comparateur de texte qui lisait la documentation comme du comportement parce qu’il trouvait du texte à l’intérieur des commentaires, une regex de couverture qui ne prévoyait pas de bloc d’options d’appel, une recherche de contreparties dont la liste de motifs était vide. Un instrument ne vaut rien tant qu’il ne trouve pas l’instance déjà connue ; un verdict sans recherche est de la vacuité ; et tout ce que décide le compilateur est mesuré, jamais estimé.

Nous ne connaissons aucune autre base de code DeFi publique qui publie une garde de mutation sélectionnée et nommée, avec des tests éliminateurs par mutant et le signalement des mutations inertes ; un registre des quantités partagées avec une règle de CI qui le rétrograde ; des mutants visant ses invariants à état, avec le survivant publié ; un tableau de couverture de force 3 sur ses facteurs de régime sous une seule assertion ; des tests N-version de ses mathématiques de devis ; une courbe mesurée de la façon dont ses devis vieillissent ; ou le taux de détection de ses propres mutants de frais avec un intervalle de confiance. Les artefacts sont dans le dépôt ; si vous trouvez une instance antérieure, l’affirmation est fausse et nous aimerions le savoir.

## §16 Architecture et tailles

*En clair :* cinq contrats, dont l’un, fait de mathématiques pures, est importé par les quatre autres, et aucun élément de mathématiques n’est défini deux fois. *Précisément :* le Core est une bibliothèque sans état, sans stockage, sans propriétaire et sans chemin de mise à niveau ; Hub, Solver, Router et Quoter l’importent et aucun ne redéfinit une primitive, de sorte qu’un seul audit des mathématiques couvre chaque consommateur.

### §16.1 Cinq contrats : ce que chacun détient, ce que chacun peut appeler

| Contrat | Détient | Peut appeler | Écrit un état persistant | Taille (octets de runtime) |
|---|---|---|---|---|
| **Core** (bibliothèque) | rien — des fonctions pure et view : Ω, 𝒟, Φ, Ψ, le codec du Monoslot, les transferts sûrs, `mulDiv` | les pools et les tokens, en lecture seule, chaque `staticcall` gardé étant plafonné à `GAS_CAP = 100,000` | jamais — pas de `tstore`, pas de `sstore` | 6,442 |
| **Hub** | le registre (un Monoslot par pool, ≤ 16 par paire), les lignes de factory (≤ 16), les ponts (≤ 3), la liste d’autorisation des hooks avec épinglage des codehash, les entrées V4, les rôles ; espaces de noms ERC-7201 | les pools et les factories, en lecture seule, via le Core ; le PoolManager V4 via `extsload` | lors de `recordSwap` (Router uniquement), lors de l’admission et de la configuration | 23,648 |
| **Solver** | rien — `view` uniquement | le Hub (lectures du registre, découverte), les pools via le Core | jamais | 19,686 |
| **Router** | admin, trésoreries, `permit2`, `weth`, `paused`, `controlRenounced`, file de secours ; **aucun solde de token au repos** | le token d’entrée, les pools de la route, le token de sortie, le Hub (`isBridgeToken`, `isHookLive`, `recordSwap`), le Solver (`swapBestExactIn` uniquement, `view`), WETH et le PoolManager pour V4, Permit2 pour sa porte, les trésoreries, le destinataire — jamais une cible fournie par l’appelant | sa propre configuration ; l’unique écriture externe par swap est `Hub.recordSwap` sur le chemin de succès | 23,781 |
| **Quoter** | rien | le Solver (`view`), le Hub (`isBridgeToken`, en lecture seule), les pools pour le dry-run de la Passe Exacte (appelés puis annulés par revert) | jamais | 11,429 |

![FIG-15 — Topologie à cinq contrats : le Core (mathématiques pures : Ω · 𝒟 · Φ · Ψ) importé par le Hub (registre, état Ψ, ponts, épinglages de hooks), le Solver (optimiseur de route, view), le Router (exécution atomique, le seul contrat qui déplace des fonds) et le Quoter (aperçu, view et dry-run). Les flèches sont des appels ; la seule flèche pleine vers le stockage par swap est Router → Hub.recordSwap ; toutes les autres flèches partant du Solver et du Quoter sont en view uniquement.](https://blazephoenix.xyz/whitepaper/figs/fr/fig-15.png)

La confiance ne va que dans un sens. Le Hub fait confiance au Router pour appeler `recordSwap` (`onlyRouter`) et à personne d’autre ; le Router fait confiance au Hub pour trois faits dont une route a besoin — ce token est-il un pont, ce hook est-il actif, et l’écriture d’enregistrement — et pour rien de ce qui fixe le prix d’un échange. Le Solver ne fait confiance au registre du Hub qu’en tant que liste de coordonnées à mesurer. Le Quoter tient le plan du Solver pour indicatif et re-cote les jambes concentrées contre les pools eux-mêmes. Rien ne fait confiance au calldata : une route parvient au Router comme l’affirmation d’un inconnu.

### §16.2 La vie d’un swap

![FIG-16 — La vie d’un swap : `previewAndEncode` (view) → calldata → une porte → `_execute` → places → règlement, avec la vérification de chaque étape désignée par son code de refus : minimum et échéance à la porte ; forme et continuité des hops ; arrivée mesurée ; par hop, remise à l’échelle, frais si c’est le hop de frais, plancher par jambe et barrière de couverture, agrégat du hop ; balayages ; sortie brute ; le plancher de route Φ ; les frais côté sortie le cas échéant ; livraison mesurée chez le destinataire ; `userMinOut` ; le grand livre des frais ; `recordSwap` par jambe exécutée ; `ExecutionProof`.](https://blazephoenix.xyz/whitepaper/figs/fr/fig-16.png)

1. **Aperçu** (off-chain, gratuit) : `previewAndEncode` exécute `findBestRoutePlan` — le registre et, s’il n’est pas frais, la découverte ; Ξ ; le classement Ψ ; les sondes ; la bande ; l’allocation ; l’écrêtage ; la barrière de répartition ; le plancher attesté — et renvoie le `Preview` et le calldata. Le trader fixe `userMinOut` à partir de sa propre anticipation de prix.
2. **La porte** : `swapExactIn` (ou une porte sœur) vérifie l’échéance (`RouterE(4)`), refuse un minimum nul (`RouterE(10)`), prélève l’entrée et mesure le delta.
3. **`_execute`** : nombre de hops, jambes par hop et continuité des hops (`RouterE(3)`) ; le parcours des frais ; les lignes de base établies une seule fois.
4. **Par hop** : remise à l’échelle selon l’arrivée mesurée ; prélèvement des frais si c’est le hop de frais (ou à chaque hop en épuisement) ; lecture de chaque pool une seule fois pour le montant, l’impact et le devis ; exécution de chaque jambe selon la forme que sélectionne son type (`RouterE(8)` pour un type inconnu, `RouterE(9)` pour un hook qui modifie les deltas) ; le plancher par jambe avec la barrière de couverture et l’agrégat du hop (`RouterE(5)`) ; le callback ne paie que le pool engagé (`RouterE(6)`).
5. **Balayages** : l’entrée non dépensée et les résidus de pont au-dessus de leurs lignes de base reviennent au payeur.
6. **Règlement** : la sortie brute sous forme de delta (`RouterE(8)` si elle est nulle) ; `floorBps` à partir de l’impact mesuré et de la concentration ; le plancher de route contre le devis dans le cadre du dernier hop et le maximum à trois termes (`RouterE(5)`) ; les frais côté sortie sur une route directe vers un token pont ; le transfert, la livraison mesurée chez le destinataire, `userMinOut` sur le montant livré (`RouterE(5)`) ; le grand livre des frais (`RouterE(15)`, `RouterE(16)`).
7. **L’unique écriture** : `Hub.recordSwap` par jambe exécutée, avec les montants mesurés ; puis les événements `ExecutionProof`, `Swap` et `Fee`. Rien n’est écrit lorsqu’une vérification refuse ; la transaction, swap de chaque pool compris, n’a jamais eu lieu.

### §16.3 Les tailles face à EIP-170

| Contrat | Octets de runtime | Sous la barrière de 24,000 octets | Sous EIP-170 (24,576) |
|---|---|---|---|
| Router | 23,781 | 219 | 795 |
| Hub | 23,648 | 352 | 928 |
| Solver | 19,686 | 4,314 | 4,890 |
| Quoter | 11,429 | 12,571 | 13,147 |
| Core | 6,442 | 17,558 | 18,134 |
| Total | 84,986 | | |

![FIG-17 — Tailles de runtime des cinq contrats face à la barrière de 24,000 octets du projet et à la limite EIP-170 de 24,576 octets, mesurées sur le profil de release ; le routeur V1 déployé (14,624 octets) pour donner l’échelle.](https://blazephoenix.xyz/whitepaper/figs/fr/fig-17.png)

La barrière fait l’objet d’une assertion à l’intérieur même de la suite (`DeployedSizeGate`), sur le profil même sous lequel la suite s’exécute, que `profile_parity.py` maintient identique au profil de release — une suite qui testerait un binaire que la chain ne pourrait pas accepter testerait le mauvais objet. La taille est mesurée, jamais estimée : une garde estimée à quelques dizaines d’octets en a mesuré plusieurs centaines parce que la forme choisie allongeait la durée de vie d’une variable. Le Router et le Hub se trouvent à quelques centaines d’octets du mur, c’est pourquoi la preuve de paire du §9 coûte 179 octets et la réparation de la courbe stable du §6 est de 35 octets *plus petite* que le code qu’elle a remplacé : chaque octet ajouté est payé par un octet retiré.

### §16.4 La surface d’attaque, comptée

Une surface d’attaque, c’est ce qu’un adversaire peut écrire, ce qu’un contrat détient, qui il appelle, et qui détient un pouvoir sur lui. Chacun de ces éléments est ici une quantité dénombrable.

**Ce qu’un appelant peut écrire.** Une route est une structure typée — au plus 3 hops × 5 jambes, chaque jambe ayant dix champs (`pool`, `hooks`, `kind`, `fee`, `tickSpacing`, `zeroForOne`, `stable`, `amountIn`, `expectedOut`, `auxId`) — et aucun champ ne nomme une cible d’appel ni ne transporte un blob de calldata. `docs/assurance/fields.json` classe chaque champ inscriptible par l’appelant qui atteint un état, un événement ou une garde, 23 en tout :

| Classe | Nombre | Signification | Exemples |
|---|---|---|---|
| **confirmé** | 14 | l’exécution lit le champ *et* une vérification le compare à une observation | `leg.kind` redérivé de la forme du pool (Hub `recordSwap`) ; `leg.fee` sur les pools concentrés, où le pool répond ; `leg.amountIn`, remis à l’échelle par l’arrivée mesurée ; `leg.expectedOut`, relevé par la barrière de couverture ; `hop.tokenIn`/`tokenOut`, soumis à une barrière face aux propres `token0`/`token1` du pool ; `leg.hooks` et `leg.auxId` sur V4, qui entrent dans l’id du pool exécuté |
| **d’aiguillage** | 5 | le champ dirige un mouvement de tokens, donc une valeur fausse pénalise l’appelant et n’exige aucune vérification séparée | `leg.pool` sur les places à paire et concentrées (les tokens lui sont poussés) ; `leg.zeroForOne` sur V4 (un mauvais sens échoue fermé dans la comptabilité du manager) ; `route.singleOutFloor` (monotone — il ne peut que resserrer) ; les champs indicatifs de la route (`totalOut`, `estGas`, …), que personne ne lit |
| **déclaré** | 4 | il atteint un état, un événement ou une garde, et rien ne l’observe — chacun avec sa raison consignée | `leg.fee` sur les paires V2 (aucune paire n’expose `fee()` ; borné par le plafond de 100 bps et utilisé à la fois par le devis et par l’exécution, de sorte que les deux ne peuvent pas diverger) ; `leg.hooks` sur les jambes non V4 (n’alimente que l’ordre plaçant les pools à hook en dernier ; un mensonge coûte à l’appelant sa propre route) ; `leg.expectedOut` tel que publié dans l’événement `Volume` (borné aux alentours de +25 % par le plancher par jambe qui consomme le même champ ; aucun consommateur on-chain) ; `leg.stable` (le Hub mesure `stable()` à l’enregistrement et les deux consommateurs lisent la même déclaration) |

Le classement du registre, le plancher du protocole et la base des frais sont calculés uniquement à partir de quantités confirmées. Le Hub confirme `kind` ; le Router confirme les montants, les tokens et les frais des pools concentrés ; la dérivation confirme les champs V4 par construction.

**Ce que le Router détient entre les transactions.** Un solde de token nul — `INV-1`, vérifié par `invariant_RouterHoldsNothing`, `invariant_routerHoldsNothing` et `invariant_HoldsNothingBeyondTheSeed` dans les campagnes, et surveillé par quatre mutants de garde (un transfert d’un wei de moins, et chacune des trois lignes de base supprimée). Les frais partent vers les trésoreries à l’intérieur du swap ; les résidus reviennent au payeur à l’intérieur du swap ; la seule chose qui peut se trouver dans le Router est un envoi erroné, que le secours de 48 heures existe pour restituer et que les lignes de base de chaque swap excluent du paiement. Trois des cinq contrats — Core, Solver, Quoter — n’émettent aucune écriture de stockage.

**Qui un swap appelle.** Les contreparties d’un swap forment un ensemble fermé de huit classes : le token d’entrée ; le token de sortie ; les pools que la route nomme (au plus 15) ; le Hub ; le Solver, uniquement via `swapBestExactIn` et uniquement en view ; WETH et le PoolManager, uniquement pour les jambes V4 et natives ; Permit2, uniquement via sa porte ; les trésoreries et le destinataire. Il n’y en a pas de neuvième : l’exécuteur n’accepte aucune cible d’appel fournie par l’appelant (SOK-ARBITRARY-CALL) et répartit l’interaction avec les places à partir d’un ensemble fermé de formes sélectionnées par un type détenu par le registre. Chaque lecture gardée est plafonnée à 100,000 gas. Le nombre exact d’instructions d’un swap est enregistré plutôt qu’affirmé : 4,382 instructions du Router pour un swap V2 honnête sur le binaire de release, avec l’opcode vérifié à chaque pas (§15.10).

**Qui détient le pouvoir.** Neuf fonctions de contrôle sur le Router et six sur le Hub, chacune énumérée aux §14.6–§14.7 avec ce qu’elle peut et ne peut pas atteindre ; aucune ne peut toucher au capital, aux frais, à la répartition ni aux planchers ; toutes aboutissent à `renounceControl`, et ce qui survit est le niveau curateur de quatre fonctions, qui ne peut que croître, plus les sièges d’opérateur existants. Il n’y a pas de `selfdestruct`, pas de proxy, pas d’initialiseur après l’`initialize` unique du Hub, et les seuls `delegatecall` de l’artefact sont ceux du compilateur lui-même vers la bibliothèque publique Core.

**Ce qui doit exister pour qu’un échange se règle.** Le budget du §3.1 : aucun service de routage, aucun oracle, aucun réseau de solveurs, aucun keeper, aucun proxy — cinq zéros là où la conception dominante affiche « requis », « courant » ou « variable ».

**La différence, comptée.** Face à une conception qui obtient sa route off-chain et exécute une liste d’appels générique, ou qui détient un inventaire :

| Surface | Cette conception | Exécuteur résolu off-chain | Place ou routeur détenant un inventaire |
|---|---|---|---|
| champs inscriptibles par l’appelant qui atteignent une garde | 23, dont 14 confirmés contre une observation et 0 qui nomment une cible d’appel | une liste de paires `(target, calldata)` : non bornée, et chaque cible est un appel que le contrat effectue sur la parole de l’appelant | — |
| solde de token au repos dans l’exécuteur | 0 (`INV-1`) | généralement 0 | l’inventaire lui-même |
| contrats qui écrivent dans le stockage pendant un swap | 1 (`Hub.recordSwap`, une écriture par jambe exécutée, sur le chemin de succès uniquement) | variable | la comptabilité de la place |
| qui calcule la route | la chain (`findBestRoutePlan`, `view`) ou l’appelant, remesurée dans le cadre dans les deux cas | un serveur ; le contrat vérifie, il ne choisit pas | — |
| oracles, keepers, proxys, chemins de mise à niveau | 0 | couramment ≥ 1 de chaque | couramment un proxy |
| fonctions privilégiées pouvant atteindre le capital | 0 | un admin de proxy peut remplacer la logique | variable |
| octets déployés qu’un auditeur doit lire | 84,986, cinq contrats, tous sous EIP-170 | le contrat de règlement, plus un serveur dont la taille en lignes auditables est inconnaissable | — |

La comparaison est comptée, non argumentée : la différence est une route bornée, typée et confirmée contre une liste non bornée ; zéro contre un inventaire ; une écriture contre beaucoup ; et une route que la chain a dérivée — ou que l’appelant a dérivée et que la chain a remesurée — contre une route qu’un serveur a dérivée et que la chain s’est contentée de vérifier.

## §17 Performances mesurées

*En clair :* tout ce que contient cette section a été mesuré sur des forks de chains réelles — de vraies places, de vraies réserves déposées par des inconnus, un seul bytecode — et est étiqueté exactement comme tel. *Précisément :* un chiffre n’apparaît ici que si un harnais public du dépôt le reproduit ; le harnais, la chain et la date sont imprimés à côté. Une mesure sur fork est une preuve du comportement, non un enregistrement de déploiement ; l’enregistrement de déploiement, c’est le §19. Les mesures du §12 et du §15 datent du 2026-09-05 ; la campagne de cette section a été menée en août 2026 et ses harnais se trouvent dans `test/fork/`.

### §17.1 Identité cross-chain

Le même bytecode a été exercé sur des forks d’Ethereum, d’Arbitrum, d’Optimism et de Base, en s’adaptant à la combinaison de places de chaque chain — Aerodrome et Pancake aux côtés de V4 sur Base, Camelot sur Arbitrum, Velodrome sur Optimism — sans aucune modification de code propre à une chain. Les quatre chains ont été choisies parce qu’ensemble elles exercent toutes les familles de places que le registre admet, de sorte qu’un cas particulier propre à une chain, s’il avait existé, aurait eu quatre occasions d’apparaître. Sur chaque chain, deux identités ont été mesurées par rapport à la vérité terrain : pour la dérivation CREATE2, chaque adresse de pool calculée par 𝒟, comparée à l’adresse que la factory de la chain elle-même indique pour la même paire et les mêmes paramètres, `hasCode` confirmant la présence de bytecode à chaque adresse dérivée ; pour la Passe Exacte, le swap propre à la place, appelé sur l’état réel, et ses deltas remontés dans la charge utile du revert, chain par chain, en résistant aux conventions de callback de chaque famille, y compris celles d’Algebra. Les deux identités tiennent sur les quatre chains, et la colonne des écarts dont le tableau aurait besoin est vide.

### §17.2 Fidélité aperçu → livraison sur quatre chains

La thèse se prête à une mesure directe : coter un swap, l’exécuter, comparer. Le harnais de fork `FidelityMatrix` cote, exécute et règle de vraies paires sur Arbitrum, Base, Optimism et Robinhood Chain à des blocs épinglés, et imprime l’aperçu face à la livraison pour chaque paire qu’il touche ; il porte une alarme permanente à 50 bps, de sorte qu’une régression de fidélité se signale d’elle-même. Mesuré le 2026-08-24 :

| Chain | Aperçu → livraison | Relevé en parallèle |
|---|---|---|
| Arbitrum | 0–1 bps sur chaque paire exécutée | la route du Solver correspond à une grille exhaustive de répartitions à deux voies par pas de 10 % |
| Base | 0–1 bps | à 0.13 bps près du maximum de la grille ; exécutions réelles de 222,000–326,000 gas, tout compris |
| Optimism | 0–1 bps | la première exécution routée via V4, mesurée sur fork, de ce moteur sur Optimism |
| Robinhood Chain | 0–1 bps | exact par rapport à la grille ; les premières exécutions mesurées sur la chain |

La matrice exécute de bout en bout des jambes à produit constant, concentrées, V4 — natif compris — et Solidly. La bande est d’un point de base sur chaque paire exécutée, avec une alarme à 50 bps derrière elle ; la comparaison Solver contre grille du §17.1 est le seul résultat qui concorde à l’unité, et il est présenté comme tel. Deux résultats comportementaux l’accompagnent : les intermédiaires de pont sont sélectionnés de façon adaptative pour chaque paire, comme maximiseur de la Méta-Équation, et une paire délibérément isolée ne renvoie aucune route plutôt qu’une route fabriquée.

### §17.3 Le recensement des factories

L’inscription d’une place au registre est une affirmation, et cette affirmation a son propre instrument. Le harnais de recensement (`FactoryCensus`) hérite du câblage de déploiement de production — il déploie au moyen du même script que celui que reçoivent les chains, derrière une barrière d’égalité qui refuse de s’exécuter en cas de dérive entre harnais et déploiement. Il se trouve avec ce script dans l’arborescence privée de déploiement et ne figure pas dans le dépôt public, de sorte que le tableau ci-dessous est rapporté, non reproductible depuis ce checkout ; le balayage public qui partage ses fixtures et sa barrière anti-vacuité est `test/fork/TokenSweep.t.sol`. Il indique, pour chaque factory inscrite sur chaque chain, sur un corpus fixe de tokens, trois colonnes : inscrite, détient un pool pour la paire, a remporté la paire (a fourni une jambe de la route gagnante). Les colonnes partagent un seul dénominateur, les paires de hop, avec une case explicite des non-résolus qui a mesuré zéro sur les quatre chains : chaque jambe que le moteur a routée est attribuée à une factory nommée.

| Arbitrum, 88 paires de hop | Détient un pool | A remporté la paire |
|---|---|---|
| Uniswap V4 (natif + encapsulé) | 67 | 48 |
| Uniswap V3 | 86 | 33 |
| Pancake V3 | 77 | 13 |
| Camelot V3 | 66 | 10 |

Une paire crédite chaque place qui a fourni une jambe de sa route gagnante, de sorte que la colonne peut dépasser le nombre de paires — attribution, non partition. Sur Arbitrum, la famille V4 remporte plus de paires que toute autre place inscrite, la factory V3 canonique comprise — confirmation, vue de l’extérieur, de l’ordre de sondage du §5.1, qui interroge le natif en premier. Le recensement est aussi un instrument d’élagage : une factory qui est inscrite, détient des pools et ne remporte rien sur des centaines de paires mesurées est candidate au retrait, jugée sur l’attribution plutôt que sur la réputation, et chaque balayage porte une barrière anti-vacuité — une paire signalée comme ayant de la profondeur doit aussi coter un montant non nul.

### §17.4 Gas

Chaque chiffre est traçable jusqu’à un harnais nommé, les valeurs à froid et à chaud étant rapportées séparément.

| Mesure | Valeur | Source |
|---|---|---|
| résolution à froid (première découverte, 4 factories) | 169,093 gas | `test/fork/DiscoveryColdWarm.t.sol` ; `REPORTS.md` |
| résolution à chaud (Monoslots en cache, découverte sautée) | 132,691 gas | même exécution |
| économie à chaud | −36,402 gas (−21 %) | différence |
| coût marginal par jambe supplémentaire | ≈ 28,700 gas | `LifecycleMetrics` (août 2026) |
| second hop via un pont | ≈ 59,000 gas | `LifecycleMetrics` (août 2026) |
| `previewPlan` sur Base en conditions réelles, à froid / à chaud / à froid après TTL | 1,524,221 / 1,414,633 / 1,359,925 gas | `test/fork/QuoterGasFork.t.sol` (§12.3) |

![FIG-18 — Ce que coûte un devis via l’ABI : `previewPlan` et `previewAndEncode` sur mocks (découverte contre registre frais, moyenne et dispersion sur quarante tailles) et sur Base en conditions réelles (à froid, à chaud, à froid après le TTL).](https://blazephoenix.xyz/whitepaper/figs/fr/fig-18.png)

Trois lectures structurelles tiennent sans aucune mesure, parce qu’elles découlent de l’architecture. L’économie à chaud croît avec l’étendue du registre, non avec la taille de l’échange : ce que paie le chemin à froid, c’est le balayage des factories, et la lecture en cache qui remplace ce balayage est un seul `SLOAD` dans tous les cas. Les jambes sont l’unité du coût marginal, c’est pourquoi la barrière de répartition existe (§10.4). Et les hops sont la dimension coûteuse : une route via un pont doit gagner suffisamment aux pools pour se payer à la pompe. Les appelants choisissent leur point sur cette surface à chaque échange — résoudre dans le cadre et payer la recherche, ou fournir une route et ne payer que l’exécution — et la barrière de fraîcheur du §9.4 réserve le balayage aux paires que le protocole ne connaît pas encore. Le coût d’un devis en conditions réelles est de 1.4–1.5 million de gas de calcul *view*, que personne ne paie on-chain.

### §17.5 L’exécuteur face à la concurrence

Un tableau de gas sans référence externe relève de l’autocongratulation ; la couche d’exécution a donc été comparée sur le même fork, au même bloc, avec du calldata réel de production : le `MetaAggregationRouterV2` de KyberSwap (`0x6131B5fae19EA4f9D964eAc0408E4408b66337b5`), sur un fork de Base épinglé au bloc où chaque route a été construite, le 2026-08-22, avec Paraswap coté en parallèle comme second comparateur : les routes encodées par Kyber lui-même, demandées à son API publique et rejouées contre son contrat déployé, nos routes étant exécutées à côté d’elles et les livraisons des deux côtés mesurées comme deltas de solde. Le harnais se valide d’abord lui-même : la livraison mesurée du concurrent correspond au wei près à la promesse de son API, de sorte qu’un revert d’un côté ou de l’autre ne peut se faire passer pour un résultat. Par jambe, l’exécuteur a été mesuré 2.4× à 10.9× plus économe en gas que la référence — sur la paire la plus nette, 322,172 gas pour quatre jambes contre 879,553 pour une, 2.7× par transaction — par un harnais (`BenchExec.t.sol`) qui se trouve avec le calldata capturé dans l’arborescence privée, de sorte que les chiffres sont rapportés, non reproductibles depuis ce checkout, qui est compilé avec `optimizer_runs = 999999` — la différence tient donc à la conception, non aux réglages du compilateur ; leur routeur interprète une liste d’appels générique décidée par un serveur, le nôtre exécute une structure typée, et l’interprétation a un coût. Sur la paire la plus nette, leur route à une seule jambe a coûté 879,553 gas ; quatre de nos jambes ont coûté 322,172. Sur neuf paires mesurées, la porte qui résout entièrement et la porte calldata ont livré une sortie identique. Les barres d’erreur de la méthode font partie du résultat : les devis ont été épinglés au bloc pour lequel ils avaient été construits, et les estimations de gas côté API ont été écartées après qu’une exécution réelle en a mesuré une 148 % trop basse.

### §17.6 Le taux de change octet–gas ρ*

Quelle porte un appelant doit-il utiliser — transporter une route en calldata, ou laisser la chain résoudre ? Le coût d’une transaction est affine et séparable — $21{,}000 + \mathrm{bytes} \cdot p_{\mathrm{byte}} + \mathrm{gas} \cdot p_{\mathrm{gas}}$ — de sorte que tout le compromis entre calldata et exécution se réduit à un seul scalaire : **ρ\***, le gas d’exécution qu’achète un octet de calldata. Mesuré avec `test/EquationBench.t.sol` sur le profil release, ρ* va de 66.9 gas par octet sur les routes à une jambe à 105.5 sur les routes à quatre et cinq jambes, transaction entière. Face à lui se tient le prix courant de l’octet sur chaque chain, lu dans les predeploys de frais de la chain elle-même — l’oracle de prix du gas de l’OP-Stack, `ArbGasInfo` d’Arbitrum — sans clé et sans flux tiers.

| Chain, prix lus en direct 2026-08 | $p_{\mathrm{byte}}$ (gas/octet) | Part des données dans un swap | Porte économique |
|---|---|---|---|
| Base | 7.2 | 0.9 % | route en calldata |
| Arbitrum | 12.8 | 3.4 % | route en calldata |
| Optimism | 33.9 | 14.0 % | route en calldata |
| Rollup ZK payant une disponibilité des données au prix du calldata | 243,256 | 99.9 % | résolution dans le cadre — 14× moins chère de bout en bout |

Là où $p_{\mathrm{byte}} < \rho^{*}$, c’est la porte calldata qui est économique ; là où $p_{\mathrm{byte}} > \rho^{*}$, c’est la résolution dans le cadre. Sur les chains OP-Stack et Arbitrum, le terme de données représente 0.9–14 % du coût d’un swap, de sorte que c’est l’exécution qui absorbe l’argent — et c’est là que l’efficacité de l’exécuteur décrite plus haut se cumule. Les quatre portes du Router ne sont pas une fonctionnalité de confort ; elles sont le modèle de coût affine, transformé en interface.

## §18 Le DEX, réduit à l’os

Dix-sept sections de mécanisme se réduisent à une page. Si vous ne lisez rien d’autre, lisez celle-ci.

**Une seule tâche.** Prendre deux tokens, un montant, votre minimum, un destinataire et une échéance. Rendre la plus grande sortie que la liquidité publique de la chain peut livrer — ou refuser, avec un code qui dit pourquoi. Il n’y a pas de troisième issue : dans la matrice des places hostiles, vingt fixtures sur vingt, combinant pathologie de place et porte, règlent à l’intérieur des planchers ou refusent avec un sélecteur qui nous est propre (§15.5).

**Cinq contrats, une seule mathématique.**

| Contrat | Ce qu’il est, en une ligne |
|---|---|
| **Core** | La bibliothèque sans état : la forme close de chaque place, la dérivation 𝒟, le plancher Φ, le score de vitalité Ψ. Définie une fois, importée par les quatre autres. |
| **Hub** | Le registre qui apprend la liquidité en l’échangeant — et prouve chaque paire avant de la stocker. Il ne fixe aucun prix. |
| **Solver** | Construit la route et la répartition à partir de la sortie marginale et de la profondeur *mesurées*. `view` uniquement. |
| **Router** | Le seul contrat qui déplace des fonds. Mesure chaque delta de solde, impose le plancher, ne détient rien au repos. |
| **Quoter** | L’aperçu gratuit — le même évaluateur que celui avec lequel le Router règle, de sorte qu’aperçu et livraison concordent. |

**Six types de places, quatre familles.** Uniswap V2, V3 et V4 (ETH natif compris), les pools Algebra à frais dynamiques et les paires Solidly stables et volatiles — le prix de chacun étant donné par une seule forme close du Core, un seul répartiteur Ω pour tous (§4). Curve et Balancer ont été supprimés, et leurs numéros de type retirés pour toujours (§4.1).

**Quatre portes, un seul exécuteur.** `swapExactIn`, `swapExactInWithPermit2` et `swapExactInNative` prennent une route en calldata ; `swapBestExactIn` résout et exécute de façon atomique on-chain. Les quatre atteignent le même `_execute`, les mêmes planchers et les mêmes frais (§11.1).

**Trois planchers sous chaque échange.** Un plancher par jambe, un agrégat par hop, et le plancher de route Φ re-dérivé de l’impact mesuré dans le cadre d’exécution — puis *votre* `userMinOut` sur ce qui arrive réellement au destinataire. La livraison est le plus élevé des deux, le plancher du protocole et le vôtre (§11.5).

**Des frais uniques, compilés.** 0.28 %, prélevés une seule fois sur le premier token pont que détient le Router, arrondis au supérieur pour n’être jamais nuls, et rien qui croisse avec l’écart entre devis et livraison (§13).

**Ce en quoi il refuse d’avoir confiance.** La liquidité annoncée. Les frais déclarés. La profondeur fournie en calldata. Ce que dit un pool lui-même des tokens qu’il échange. Chacun de ces éléments est soit mesuré depuis le pool, soit dérivé par l’arithmétique avant de pouvoir atteindre l’état partagé (§7, §9, §16.4).

**Ce que cela coûte.** Le gas : dériver la route dans le cadre coûte plus cher que régler une réponse calculée ailleurs, et le §17.6 vous indique, chain par chain, quelle porte est la moins chère. L’immuabilité : un défaut est divulgué et récompensé par une bounty, jamais corrigé par un patch (§21).

## §19 État du déploiement

Ce document décrit le dépôt, et le dépôt fait foi : là où ce texte et le code source divergent, le code source l’emporte. Deux générations existent et elles ne sont jamais confondues.

**V1 est ce qui est en service on-chain.** C’est une base de code différente et antérieure, archivée dans son propre dépôt — et non un commit antérieur de celui-ci. Mesuré par RPC le 2026-09-04, le routeur V1 déployé est le même contrat de 14,624 octets sur Base, Ethereum, Optimism et Arbitrum. `test/fork/DeployedParity.t.sol` et `test/fork/DeployedCodehashPin.t.sol` épinglent ce qu’est V1, sur chaque réseau que nomme le SDK — le codehash de chaque contrat déployé, consigné dans `test/fork/deployed-codehash.json` — de sorte qu’un changement de ce qui est déployé est remarqué, et ils vérifient les propriétés qui tiennent quelle que soit la génération : pas de `SELFDESTRUCT`, pas de `CALLCODE`, aucune autorité fondée sur `tx.origin`, un verrou de réentrance transitoire, une taille de runtime fixe, un câblage actif, et un contrôle auquel il n’a pas encore été renoncé.

**Ce dépôt est V2, et V2 n’est pas déployé.** L’arborescence mesurée dans ce document produit un Router de 23,781 octets et porte des fonctions externes que V1 n’a pas — parmi elles la porte à valeur native, la couture wrap/unwrap, le chemin de récupération et la porte Permit2. Les contrats V2 sont déployés sur des forks de liquidité réelle et exercés de bout en bout à chaque exécution sur fork (§15.1, §17). Rien dans le §15 ou le §17 ne protège les contrats V1 déployés ; chaque affirmation de ce document est une affirmation sur V2 tel qu’il se présente à `8949a9d`.

En conséquence, deux politiques protègent les lecteurs. Ce document ne contient aucune adresse de contrat — les adresses survivent aux documents qui les citent, et la surface d’adresses en vigueur est tenue là où elle peut être mise à jour et vérifiée par machine : les enregistrements du dépôt, ainsi que la page de vérification et le manifeste du site. Et un enregistrement de déploiement a un format spécifié — chain, adresse, commit de déploiement et hash du bytecode de runtime, écrit au moment de la diffusion, avec une étape de vérification qui re-dérive le hash à partir d’un build propre — afin que, lorsque V2 sera déployé, chaque affirmation d’adresse relève de l’arithmétique, non de la confiance.

## §20 Token — BZPX

Un milliard de BZPX, fixé pour toujours, sous la forme d’un ERC-20 simple : 18 décimales, pas de chemin de frappe, pas de frais sur transfert, pas de rebasing, pas de hooks de transfert, ni pause ni liste noire — six invariants de déploiement, vérifiés contre le bytecode du token au moment du câblage. L’offre entièrement diluée est connue dès la genèse ; la rareté est une propriété du contrat, non une politique.

| Allocation | Tokens | Part | Note |
|---|---|---|---|
| Liquidité de marché | 520,000,000 | 52.0 % | le flottant par lequel route l’agrégateur |
| Émission de staking | 180,000,000 | 18.0 % | réservée ; la conception de l’émission V4 est au §25 |
| Airdrop & Bounties | 130,000,000 | 13.0 % | un seul pool : l’ancien airdrop de 50,000,000, l’ancien fonds de recherche de 50,000,000 du §21, et 30,000,000 retirés de la liquidité de marché |
| Équipe et opérations | 103,000,000 | 10.3 % | la seule tranche d’initiés verrouillée ; vesting linéaire sur 30 mois |
| Partenaires et écosystème | 60,000,000 | 6.0 % | intégrations, partenariats avec des places de marché |
| Marketing | 7,000,000 | 0.7 % | notoriété au lancement |

**Ce qui a changé en septembre 2026.** L’airdrop et le fonds de recherche en sécurité étaient deux lignes qui payaient les mêmes personnes pour la même chose — lire le protocole sans complaisance et l’utiliser pour de vrai. Ils forment désormais une seule ligne de 130,000,000 BZPX : 50,000,000 issus de l’airdrop, 50,000,000 issus du fonds de recherche, et 30,000,000 pris sur la liquidité de marché, qui passe de 550,000,000 à 520,000,000. Rien n’a été pris sur la ligne de quiconque d’autre, et l’offre est toujours exactement d’un milliard.

L’enregistrement on-chain du contrat du token, une fois publié, est la version canonique de ce tableau. Au moment de la rédaction, le token n’a ni liquidité provisionnée ni événement de génération de token ; un volume on-chain nul est l’état attendu à ce stade, non un signal sur le code.

## §21 Programme de recherche et bilan de sécurité

Avant que ce projet n’ait un programme de recherche digne de ce nom, des chercheurs indépendants ont lu le code source de façon hostile — sans qu’on le leur demande, sans que rien leur soit promis — et ont divulgué en privé ce qu’ils ont vu. Le code que décrit ce document est le code qui a survécu à leur lecture. Ce que chacun d’eux a signalé, et ce qu’il en est advenu, se trouve là où de tels enregistrements ont leur place : dans les fichiers `SECURITY.md` et `SECURITY_HALL_OF_FAME.md` du dépôt, à côté du code, où chaque entrée peut être vérifiée dans les deux sens. Cette section consigne qui ils sont, ce que rapporte la lecture hostile, et la forme de ce qu’elle a trouvé.

**Les chercheurs.** NetGakarot · duxun · AmanDara1 · amitbhakar · auditor_1b3f2c · siam siddik · Thomas · llen · destinyae · superagent · Mohd Huzaifa · Raditya · bai bo · Josh W · Borutobro · mohaseenkatika · mohaseenbasha · Karan Rathod · Seavia Resources · acit aja · Brian Wahyu · M2000 Slash · Binod Bk · CHARIR ABDELHAMID · et un chercheur qui a choisi de rester anonyme. Vingt-cinq chercheurs ont été crédités ; aucun signalement n’a atteint la classe Critique. L’attribution n’est jamais arrondie à la baisse : les noms sont publiés exactement tels qu’ils ont été donnés.

**Le pool : Airdrop & Bounties, 130,000,000 BZPX — 13 % de l’offre fixe** (§20). Les récompenses de sécurité y sont prélevées selon les fourchettes ci-dessous, qui sont inchangées.

| Gravité | Récompense |
|---|---|
| Critique — vol direct ou gel permanent des fonds des utilisateurs ; un état d’insolvabilité atteignable | 2,500,000 – 7,500,000 BZPX |
| Élevée — vol dans des conditions particulières ; une redistribution qui paie systématiquement la mauvaise partie | 625,000 – 2,500,000 BZPX |
| Moyenne — griefing, déni de service temporaire, résidu récupérable | 125,000 – 625,000 BZPX |
| Faible — impact démontré inférieur aux niveaux ci-dessus | jusqu’à 125,000 BZPX |

Les récompenses sont payées en BZPX — quantité et calendrier promis, valeur de marché non — avec des versements commençant après octobre 2026 et des signalements triés dès maintenant. Une clause est délibérée : un signalement n’a pas besoin d’enfreindre une loi de conservation pour être Critique — une redistribution qui paie systématiquement la mauvaise partie alors que les comptes sont équilibrés est valorisée comme un vol, parce que c’est la classe que seuls les lecteurs hostiles détectent.

**Ce que contient le bilan, par classe.** Chaque découverte confirmée est devenue une propriété nommée, un test de régression qui échoue sur le code d’avant le correctif, et un mutant que le test doit éliminer ; les classes, une phrase chacune, par renvoi au registre :

- *Deux producteurs d’une même quantité* — un planificateur et un exécuteur, un aperçu et une livraison, répondant à une même question dans deux cadres (l’agrégation d’impact du plancher, le pont nommé par l’aperçu face au hop sur lequel s’ancrent les frais, le budget de jambes partagé entre deux contrats, le bit de pont figé face au prédicat en direct) : clos en ramenant à un seul producteur ou par un test de parité nommé, et inventorié sous forme de registre assorti d’une règle de CI.
- *Une quantité fournie par l’appelant là où une mesure existait* — la base des frais lue dans `route.totalOut`, les frais et les hooks du registre écrits à partir du calldata, une profondeur déclarée plutôt que mesurée, un volume publié qui rapportait la déclaration : clos en mesurant, et par la classification des champs du §16.4.
- *L’absence lue comme une permission* — un hop final qui ne pouvait pas être coté dans le cadre et laissait le plancher inerte, une barrière de couverture qu’un appelant pouvait désactiver en écrivant zéro, une liste de frais vide qui cotait une famille entière à zéro : clos en se rabattant sur le chiffre attesté ou mesuré, jamais sur rien.
- *Un épinglage sur le mauvais objet* — l’origine de dérivation d’une factory qu’un proxy pouvait déplacer tandis que son codehash restait le même, le runtime d’un hook épinglé là où son implémentation pouvait bouger : clos en attestant la réponse là où le code ne peut pas être épinglé, et en énonçant le résidu à l’endroit où il ne peut pas être clos.
- *Échec ouvert dans un devis* — la garde de domaine de la courbe stable, qui débordait elle-même sur du dust, trouvée par la voie N-version : clos par un test écrit d’abord en rouge, avec une réparation plus petite que le code qu’elle a remplacé.
- *Un correctif appliqué à l’un de deux canaux symétriques* — la signature de défaut que cette base de code nomme en premier, rencontrée à plusieurs reprises ; chaque fermeture recherche désormais le jumeau avant d’être fusionnée.

L’invitation est l’objet même de cette section, et elle est permanente : les lecteurs indépendants sont invités, publiquement, et payés, dans une monnaie dont la quantité est fixe. Signalez en privé, par le canal de divulgation indiqué dans le `SECURITY.md` du dépôt.

## §22 Ce en quoi vous devez avoir confiance, et ce en quoi vous n’en avez pas besoin

Tout protocole prétend se passer de confiance ; presque aucun ne sait énumérer ses exceptions. Voici la liste complète, chaque pouvoir étant énoncé avec la ligne qui le borne.

- **Le registre des places, la liste des ponts et la liste d’autorisation des hooks V4** — sélectionnés ; le pire cas d’une inscription hostile est borné par le plus strict de votre minimum et du plancher Iron-Law Φ ; le capital, elle ne peut l’atteindre (§9.6, §14.6).
- **La clé de contrôle du Router, avant renonciation** — peut rediriger la destination des frais, mettre les swaps en pause, réorienter les contrats WETH et Permit2 qu’appellent ses portes native et Permit2, et récupérer des tokens envoyés par erreur derrière un délai public de 48 heures ; ne peut ni modifier le taux des frais, la répartition ni les planchers (constantes de compilation, aucun setter), ni atteindre le capital (pas de proxy, pas de chemin de mise à niveau, pas de `selfdestruct`, rien de détenu au repos).
- **La clé de contrôle du Hub, avant renonciation** — peut réorienter la vue qu’a le Hub du Router, du Solver et du Quoter, réorienter le manager V4, mettre en pause l’apprentissage du registre et accorder des sièges d’opérateur ; ne peut pas fausser le prix d’une exécution, parce que rien de ce qui fixe le prix d’un échange ne lit le registre.
- **Les trésoreries** — fixées à la construction, et la répartition 30 / 70 n’a pas de setter.
- **Les contrats canoniques dont dépendent les portes** — le Permit2 d’Uniswap pour la porte Permit2, le WETH de la chain pour la porte native, le PoolManager pour les jambes V4 : chacun n’est appelé que par sa porte ou sa jambe, et le Router évalue chaque jambe à partir de son propre delta de solde mesuré, de sorte qu’une contrepartie malhonnête fait s’effondrer la sortie et que votre borne refuse le swap.
- **Le token et la correspondance** — que BZPX satisfait ses six invariants de déploiement, et que les adresses que vous appelez correspondent à ce texte : vérifiable dès la publication des enregistrements du §19, par la procédure que spécifie le §19.

Ce pour quoi vous n’avez à faire confiance à personne : fixer le prix de votre échange — la route est dérivée dans le cadre d’exécution à partir de l’état public, ou y est re-mesurée si vous l’avez fournie ; en assurer un routage équitable — le plancher est re-dérivé on-chain à partir de la sortie mesurée et les frais n’ont aucun terme qui croisse avec l’écart entre devis et livraison ; compter les frais — le Router refuse de régler sans les payer ; et continuer à vous servir — si tous les serveurs qu’exploite ce projet disparaissaient ce soir, les contrats coteraient, routeraient et régleraient demain exactement comme ils l’ont fait aujourd’hui.

Et si vous retenez une habitude de ce document, que ce soit le minimum que vous fixez sur vos propres échanges. Chaque borne de l’agrégateur se rabat sur ce chiffre ; c’est la seule protection du système dont la qualité ne dépend de la compétence de personne d’autre que vous.

## §23 Travaux connexes

**Routage.** Angeris, Evans, Chitra et Boyd formulent le routage entre teneurs de marché à fonction constante comme un programme convexe dont l’optimum égalise les prix marginaux entre les places ; Diamandis, Resnick, Chitra et Angeris en donnent un algorithme efficace par décomposition duale. Cette conception se tient délibérément en deçà de cet optimum on-chain : l’Allocateur de répartition est proportionnel à la profondeur en une seule passe (§10.4), ce qui ne requiert ni itération ni modèle de courbure par place et reste robuste là où un solveur itératif est une facture de gas et une surface d’attaque ; la voie métamorphique mesure la distance que coûte ce choix — la répartition ne paie jamais moins que le meilleur pool seul (MR-R1) et la matrice de fidélité place la route du Solver à 0.13 bps près d’une grille exhaustive sur Base. La forme close exacte qui égalise les rendements marginaux pour la famille à produit constant tient en deux sommes et une racine carrée par place, et elle est candidate pour une version ultérieure ; l’objectif est plat près de son optimum, c’est pourquoi la règle robuste est livrée en premier. Les conceptions à solveur off-chain déplacent l’optimisation vers un serveur et réduisent le contrat à un vérificateur ; le Budget de dépendances du §3.1 est l’énoncé, dans ce document, de ce que cela coûte. Des travaux récents étendent le modèle au gas : *Optimal Routing across Constant Function Market Makers with Gas Fees* montre que le gas élargit les régions sans échange et interagit de façon non triviale avec la fragmentation, et *Routing Frictions and Executable Liquidity in Fragmented Markets* distingue la liquidité annoncée de la liquidité exécutable — la distinction que cette conception mesure dans le cadre plutôt que de la modéliser.

**Agrégation robuste.** Le centre de crédibilité du §10.3 est une médiane pondérée par la profondeur, dont le point de rupture est la moitié du poids. Müller, Moumeni et Messaoudi, *Cost of Manipulation in AMM-Based Oracles* (FC 2026), prouvent la borne correspondante pour les oracles qui agrègent des prix d’AMM : avec des poids égaux, l’attaque la moins chère déplace les pools les moins profonds, tandis qu’avec des poids de liquidité, contrôler la moitié du poids exige au moins la moitié de la profondeur totale de cotation. Ormer propose un oracle on-chain qui estime une médiane à la place de la moyenne TWAP, et *From Means to Medians* étudie quels indices de référence sont les plus difficiles à manipuler. E25 applique le même principe au routage : un pool n’est cru que dans la mesure où du capital à risque se tient derrière lui.

**Places.** Uniswap v2 fournit la référence à produit constant et la convention de 30 bps qu’adopte par défaut la branche V2 ; Uniswap v3, le modèle de liquidité concentrée, `slot0` et la virgule fixe Q64.96 que suit la forme close du §4.2 ; Uniswap v4, le singleton, la comptabilité flash et l’encodage des permissions de hook dans l’adresse, sur lesquels reposent le §5 et le §14.2 — la conception présentée ici est, à notre connaissance, la première à dériver, prouver, évaluer, filtrer et régler des pools V4 entièrement dans le cadre. Le StableSwap d’Egorov définit l’invariant amplifié de la famille Curve, que ce protocole a supprimée plutôt que de la prendre en charge sur une hypothèse de confiance non mesurée (§4.1) ; la courbe stable Solidly du §6 n’a pas d’article évalué par les pairs et est citée via ses implémentations déployées.

**MEV.** Daian et al. ont nommé l’adversaire de l’ordonnancement et montré son ampleur ; Zhou et al. ont mesuré les attaques sandwich sur les plateformes d’échange décentralisées et formalisé leur rentabilité. Ce document ne revendique aucune élimination — l’ordonnancement est décidé avant que le code du contrat ne s’exécute — et mesure plutôt la borne : la courbe sandwich du §14.3, du côté de l’attaquant, avec le seuil de refus et l’aller-retour négatif de l’attaquant au-delà.

**Vérification.** Les hyperpropriétés de Clarkson et Schneider nomment la classe à laquelle appartiennent les découvertes de redistribution du §21 — des propriétés d’ensembles de traces, non d’une seule trace — et la raison pour laquelle une loi de conservation peut tenir alors que la mauvaise partie est payée. L’interprétation abstraite de Cousot et Cousot est le cadre théorique de la forme close à tick unique : une abstraction de la mathématique de la place, exacte dans un intervalle et sûre dans une direction en dehors de celui-ci, le plancher servant de contrôle de concrétisation. Chen, Cheung et Yiu ont introduit le test métamorphique, le second juge du §15.7 là où il n’existe aucun oracle indépendant de la formule. DeMillo, Lipton et Sayward ont introduit l’analyse de mutation, dont l’hypothèse de couplage sous-tend la garde sélectionnée du §15.2. La programmation N-version d’Avizienis est l’ancêtre du §15.8, appliquée à la génération de code plutôt qu’à des équipes indépendantes. Les tests combinatoires de Kuhn, Kacker et Lei donnent aux tableaux de couverture du §15.4 leur construction et leur affirmation : chaque interaction d’ordre t entre facteurs de régime exercée par construction, avec le dénominateur imprimé. Le résultat de Littlewood et Strigini sur la validation d’une sûreté de fonctionnement ultra-élevée est la raison pour laquelle aucune probabilité d’exactitude n’est énoncée nulle part dans ce document.

**Standards.** Le stockage transitoire EIP-1153 porte le contexte de jambe de la Doctrine du Callback Unique, le grand livre des frais et le verrou de réentrance ; EIP-170 est le mur de 24,576 octets dont le Router et le Hub se tiennent à quelques centaines d’octets ; EIP-1014 est l’arithmétique CREATE2 de 𝒟 ; EIP-2612 et le Permit2 d’Uniswap définissent le transfert par signature de la deuxième porte ; ERC-7201 organise en espaces de noms le stockage du Hub ; les comptes délégués EIP-7702 appellent les mêmes portes que n’importe quel contrat.

## §24 Conclusion

Nous avons entrepris de construire un agrégateur sur une seule discipline — mesurer, ne pas croire — et de la tenir en chaque point où un chiffre aurait pu, au lieu de cela, être cru sur la parole de quelqu’un : la route, le prix, le plancher, la base des frais, l’ensemble des places, la paire qu’un pool prétend échanger, et jusqu’au modèle de coût du protocole lui-même.

Le résultat est un petit système à la forme affirmée. La fragmentation de la liquidité s’est révélée être un problème de langage, non de mathématiques : sous la prolifération des places se trouve une petite famille d’invariants, et un protocole qui exprime chacun une seule fois — un répartiteur, une dérivation, un plancher, un champ de vitalité — peut évaluer le paysage sans cas particulier par place, dériver la route à l’intérieur de la transaction qui l’exécute, et revérifier sa propre promesse contre la sortie mesurée avant qu’un token ne vous parvienne. Le bilan mesuré est le résumé le plus court de ce que cela apporte : un devis sans dérive qui se règle exactement à sa prédiction 64 fois sur 64, sur mocks et sur Base en conditions réelles ; un plancher qui plafonne un sandwich sur un échange de 1 % à environ 2.7 % de celui-ci et refuse au-delà ; un aperçu et une livraison séparés de 0–1 point de base sur quatre chains ; et un appareil qui publie, avec ses dénominateurs, comment on le truquerait et ce qu’il ne peut pas voir.

Ce que cela coûte est tout aussi clair. Le gas : dériver une route dans le cadre coûte plus cher que régler une réponse calculée ailleurs — le prix d’une garantie que vous pouvez vérifier au lieu d’une garantie que vous devez croire sur la parole de quelqu’un — et le taux de change octet–gas vous indique, chain par chain, quelle porte rend ce prix le plus faible. L’immuabilité : les défauts sont divulgués et récompensés par des bounties, non corrigés par patch — le prix d’un contrat que vous auditez une fois, et le §21 est ce que ce prix achète.

La thèse, reformulée : ce qui choisit est ce qui échange. Un devis et son exécution sont le même calcul, de sorte que l’écart où vivait autrefois l’honnêteté d’un opérateur est fermé par l’arithmétique ; un registre qui apprend la liquidité en l’échangeant, et prouve chaque paire qu’il stocke ; des frais, un plancher et une surface d’entrée que personne — les auteurs compris — ne peut modifier, parce qu’ils sont compilés. Le protocole ne demande pas qu’on lui fasse confiance. Il demande à être vérifié, et il travaille à rendre la vérification bon marché, publique et permanente.

## §25 Staking V4 : conception

Staking V4 est la base de code actuelle ; l’adresse est publiée au lancement.

Ce chapitre décrit le moteur de staking tel que son code source se présente : `src/BlazePhoenixStaking.sol` dans le dépôt `Blaze-Phoenix-Staking`, `main` à `cc614f9` (2026-10-14), qui déclare `VERSION = "4.0.0"` (L169). Chaque numéro de ligne de ce chapitre (L*n*) renvoie à ce fichier à ce commit ; là où ce texte et le code source divergent, le code source prévaut. Le chapitre ne contient aucune adresse, aucune date de déploiement et aucun rendement : chaque nombre qui y figure est une constante compilée dans le contrat, ou en est dérivé, et la dérivation est imprimée à côté.

Le moteur est soumis à la discipline du reste de ce document. Il détient un seul token, BZPX, et chaque créance sur ce BZPX est un terme du grand livre dont le contrat revérifie la somme autour de chaque changement d’état (E35). Un staker verrouille des BZPX pour un nombre de jours de son choix et perçoit deux flux — une émission planifiée (E27–E29) et les intérêts que paient les emprunteurs (E31–E34) — chacun pondéré par un boost qui croît avec l’engagement (E30). Un staker peut emprunter des BZPX adossés à son stake (E41). Lorsqu’une perte survient, elle suit un ordre fixe unique (E37), et le contrat indique, par une vue publique, s’il détient ce qu’il doit (E36).

### §25.1 Architecture et rôles

Un seul contrat, `BlazePhoenixStaking`, qui hérite de `AccessControl`, `Pausable` et `ReentrancyGuard` (L167). Le token, la trésorerie et la fenêtre d’émission (`emissionStart`, `emissionEnd`) sont immuables (L266–269) ; le constructeur refuse une adresse de token qui ne porte aucun code (L477). Il n’y a ni oracle, ni chemin de mise à niveau, ni fonction de modification des paramètres : chaque constante ci-dessous est compilée dans le contrat.

| Rôle | Détenu par | Peut | Ligne |
|---|---|---|---|
| `DEFAULT_ADMIN_ROLE` | le déployeur, à la construction | accorder et révoquer les rôles (aucun appel à `_setRoleAdmin` n’existe, il administre donc chaque rôle) | L485 |
| `ADMIN_ROLE` | le déployeur, à la construction | `fundEmission` (financement cumulé ≤ `TOTAL_REWARDS`) ; `withdrawReserve` (vers la trésorerie immuable uniquement, au plus `protocolReserve`, fermé en cas de violation grave) ; `sweepUndistributedEmission` (uniquement après `emissionEnd`) ; `unpause` ; `cancelEmergency` (uniquement sur un bilan solvable) | L486, L516, L533, L563, L580, L618 |
| `GUARDIAN_ROLE` | personne à la construction ; accordé par un administrateur de rôle | `pause` ; `declareEmergency` — aucun des deux ne déplace de fonds | L171, L578, L608 |
| n’importe qui | — | `liquidate` ; `tripBreaker`, qui échoue (revert) sauf si le bilan est en violation grave ; `absorbBadDebt` ; `lockStep` | L1066, L597, L639, L1291 |

Aucun rôle ne peut déplacer le capital d’un staker, et la seule destination vers laquelle un rôle peut envoyer des revenus est la trésorerie immuable.

| Constante | Valeur | Signification | Ligne |
|---|---|---|---|
| `TOTAL_REWARDS` | 180,000,000e18 | budget d’émission, et plafond du `fundEmission` cumulé | L192 |
| `HALVING_PERIOD` | 2 × 365 jours | durée d’une période d’émission | L193 |
| `EMISSION_PERIODS` | 8 | périodes avant la clôture du programme (`EMISSION_LENGTH` = 16 ans) | L194–195 |
| `INITIAL_REWARD_PER_SEC` | (`TOTAL_REWARDS` / 2) / `HALVING_PERIOD` | taux de la période 0, $R_{0}$ | L196 |
| `MAX_STAKE_PER_WALLET` | 30,000,000e18 | plafond de stake par position | L198 |
| `MIN_EMISSION_WEIGHT` | 1,000e18 | en dessous de ce poids total, les deux flux sont bridés au prorata | L208 |
| `MAX_LTV` | 50 | plafond d’emprunt, % du stake net | L210 |
| `LIQ_THRESHOLD` | 95 | liquidable lorsque la dette ≥ 95 % du stake | L211 |
| `LIQ_BONUS_BPS` | 500 | surplus de liquidation versé à l’appelant, en bps de la dette | L212 |
| `RESERVE_FACTOR_BPS` | 300 | part de chaque tranche d’intérêts conservée dans `protocolReserve` | L213 |
| `MIN_LOCK_DAYS`, `MAX_LOCK_DAYS` | 90, 2,555 | bornes du verrouillage, en jours | L221–222 |
| `BOOST_BASE`, `BOOST_LINEAR`, `BOOST_QUAD` | 10,000, 750, 250 | polynôme de boost, bps | L224–226 |
| `MIN_DEPOSIT_BLOCKS` | 10 | ancienneté en blocs qu’un dépôt doit atteindre avant un retrait, une réclamation du rendement pur ou une modification du verrouillage | L228 |
| `RATE_R0`, `RATE_RK`, `RATE_UK`, `RATE_S1`, `RATE_S2` | 100, 500, 0.8e18, 500, 72,500 | courbe d’intérêt à coude, bps annuels | L231–235 |
| `MAX_PROTOCOL_UTIL_WAD` | 0.75e18 | plafond d’utilisation du bilan pour un nouvel emprunt | L242 |
| `CONSERVATION_DUST` | 1e10 | tolérance de la garde de conservation, en wei | L247 |
| `MAINT_BASE`, `MAINT_DENSITY`, `MAINT_GAP_UNIT`, `MAINT_MAX_SCAN`, `MAINT_MAX_LOCK_ACTIONS` | 1, 50, 15 minutes, 10, 4 | budget de maintenance autonome (E42) | L252–255, L263 |

### §25.2 Le déroulement : stake, unstake, réclamation, emprunt

Chaque point d’entrée utilisateur exécute un même pipeline, dans l’ordre que fixe `deposit` : prendre un instantané de l’identité de conservation ; refuser si le bilan est en violation grave ; faire avancer l’indice d’intérêt et la fenêtre d’émission (`_updateGlobal`, L1428) ; régler les deux flux au poids que détenait la position ; attribuer les intérêts que doit la position (`_accrueInterestFor`, L1627) ; lever un verrouillage écoulé ; effectuer l’action ; recalculer les poids de la position par l’écrivain unique (`_resync` → `_applyBoost` → `_checkpoint`, L1726–1756) ; exécuter la fenêtre de maintenance autonome (E42) ; et vérifier de nouveau l’identité de conservation (E35). Les portes ne diffèrent que par les barrières qu’elles ajoutent.

| Point d’entrée | En pause | Urgence | Violation grave | Conditions supplémentaires |
|---|---|---|---|---|
| `deposit(amount, lockDays)` — stake | fermé | fermé | fermé (L760) | $90 \le$ `lockDays` $\le \min(2{,}555,\ \text{days left to } \mathtt{emissionEnd})$ ; stake de la position ≤ `MAX_STAKE_PER_WALLET` ; avant `emissionEnd` (L743–760) |
| `lock(lockDays)` | fermé | fermé | — | `MIN_DEPOSIT_BLOCKS` depuis le dernier dépôt (L964–971) |
| `withdraw(amount)` — unstake | ouvert | fermé | fermé (L902) | aucune dette ; verrouillage écoulé ; `MIN_DEPOSIT_BLOCKS` depuis le dernier dépôt (L890–895) |
| `claimRewards()` | ouvert | fermé | fermé (L932) | règle l’émission et le rendement pur (L930) |
| `claimPureYield()` | ouvert | fermé | fermé (L948) | aucune dette ; `MIN_DEPOSIT_BLOCKS` depuis le dernier dépôt (L944–948) |
| `borrow(amount)` | fermé | fermé | fermé (L826) | E41 (L820–840) |
| `repay(amount)` | ouvert | fermé | ouvert | — (L858) |
| `liquidate(user)` | fermé | fermé | ouvert | E39 (L1066) |
| `emergencyWithdraw()` — sortie | — | uniquement | — | E40 (L658) |
| `absorbBadDebt(amount)` | ouvert | ouvert | ouvert | E38 (L639) |

**Stake.** `deposit` fait entrer des BZPX dans la position et démarre ou prolonge son verrouillage ; le verrouillage ne peut jamais s’étendre au-delà de la fin du programme d’émission, et le boost qu’il achète (E30) n’est versé que tant que le verrouillage est en vigueur. **Unstake.** `withdraw` restitue le capital dès que la position ne doit plus rien et que son verrouillage est écoulé ; il reste ouvert pendant une pause du gardien, de sorte qu’une pause arrête les nouveaux risques sans piéger personne. En situation d’urgence, la seule sortie est `emergencyWithdraw`, qui verse les fonds propres nets, réduits au prorata si le contrat détient moins que ce qui est dû aux positions (E40), et fait perdre à la position ses flux non réclamés. **Réclamation.** La récolte, ce sont les deux réclamations : `claimRewards` verse les deux flux, `claimPureYield` fait de même pour une position sans dette ; il n’existe pas de fonction de récolte distincte. **Emprunt, remboursement, liquidation.** Un staker peut emprunter des BZPX adossés à son propre stake dans la limite de deux plafonds (E41) ; les intérêts sont prélevés sur le stake, et non ajoutés à la dette (E34) ; `repay` reste ouvert pendant une pause et en cas de violation grave, car solder une dette ne fait que rendre le bilan plus sûr. Une position dont la dette atteint 95 % de son stake peut être clôturée par n’importe qui, et celui qui la clôture reçoit le surplus de E39.

![FIG-19 — Les points d’entrée du staking et leur pipeline unique : les barrières qu’ajoute chaque porte, puis l’instantané de conservation, la barrière de violation, la mise à jour globale, le règlement, l’attribution des intérêts, l’expiration du verrouillage, l’action, la resynchronisation par l’écrivain unique, la maintenance autonome et la vérification de conservation ; la sortie d’urgence est à part.](https://blazephoenix.xyz/whitepaper/figs/fr/fig-19.png)

### §25.3 Comptabilité des récompenses

**Émission.** Soit $T$ = `TOTAL_REWARDS`, $P$ = `HALVING_PERIOD`, $R_{0}$ = `INITIAL_REWARD_PER_SEC`, $s$ = `emissionStart`, et pour un instant $t$ soit $\tau = t - s$ et $p = \lfloor \tau / P \rfloor$. L’émission planifiée cumulée est une forme close (`_emittedAt`, L1458–1467) :

$$
E(t) \;=\; \begin{cases} 0 & t \le s \\ \left(T - (T \gg p)\right) \;+\; (R_{0} \gg p)\,(\tau - pP) & s < t < s + 8P \\ T - (T \gg 8) & t \ge s + 8P \end{cases}
$$
^e27

*Dérivation.* En arithmétique exacte, le taux de la période $q$ est $T/(2^{q+1}P)$, donc une période $q$ complète émet $T/2^{q+1}$ et l’émission antérieure à la période $p$ est $\sum_{q<p} T/2^{q+1} = T(1 - 2^{-p})$. $T = 1.8\times 10^{26}$ est divisible par $2^{8}$, donc $T \gg p = T/2^{p}$ exactement pour $p \le 8$, et le premier terme est cette somme sans aucun arrondi. Le terme de taux est arrondi à l’entier inférieur : $R_{0} \gg p = \lfloor\lfloor T/(2P)\rfloor / 2^{p}\rfloor = \lfloor T/(2^{p+1}P)\rfloor$, en deçà du taux exact de moins d’un wei par seconde. À l’intérieur d’une période, $E$ croît donc linéairement et reste sous la courbe exacte ; à chaque changement de période, elle augmente de $T/2^{p+1} - (R_{0} \gg p)\,P$, un saut positif ou nul et inférieur à $P$ wei, qui la ramène sur la série exacte. $E$ est monotone non décroissante, et elle se clôt à $T - T/2^{8} = \tfrac{255}{256}\,T$ : le dernier $T/256$ n’est jamais planifié et, avec ce qu’ont laissé les fenêtres vides ou non financées, n’est récupérable vers la trésorerie que par `sweepUndistributedEmission` après la clôture (L563). Une évaluation en entiers exacts sur les huit changements de période confirme que chaque saut est positif et inférieur à $P$.

![FIG-20 — Émission planifiée E27 en fonction du temps : le taux est divisé par deux tous les deux ans et la courbe cumulée se clôt à 255/256 de TOTAL_REWARDS au bout de seize ans ; planifiée, non versée — une fenêtre sans poids n’émet rien.](https://blazephoenix.xyz/whitepaper/figs/fr/fig-20.png)

**La fenêtre.** À chaque point d’entrée, avec $W$ = `totalBoostedEffective`, $W_{\min}$ = `MIN_EMISSION_WEIGHT`, $t_{\ell}$ = `lastRewardTime` et $t' = \min(t, \mathtt{emissionEnd})$, la fenêtre écoulée est versée dans un accumulateur par unité de poids $A$ = `accRewardPerShare` (`_updateGlobal`, L1428–1448) :

$$
r \;=\; \min\!\left( \left\lfloor \frac{\left(E(t') - E(t_{\ell})\right)\cdot \min(W, W_{\min})}{W_{\min}} \right\rfloor,\ \mathtt{rewardReserve} \right), \qquad A \;\leftarrow\; A + \left\lfloor \frac{r \cdot 10^{18}}{W} \right\rfloor
$$
^e28

`rewardReserve` diminue de $r$ et `totalRewardDistributed` augmente de $r$. Avec $W = 0$, l’horloge avance et rien n’est capté, de sorte que personne n’hérite d’un arriéré ; en dessous de $W_{\min}$, la fenêtre est bridée au prorata, de sorte qu’une position de dust ne peut pas capter seule le calendrier ; et comme chaque fenêtre est plafonnée à la réserve, `fundEmission` règle l’horloge avant que le nouveau financement n’arrive (L523), de sorte qu’un complément ne paie jamais une fenêtre rétroactivement.

**Règlement.** À une position de poids d’émission $w_{u}$ et de point de contrôle $c_{u}$ (`rewardDebt`), il est dû (`_settlePendingRewards`, L1487–1497 ; le jumeau du rendement pur, L1499–1517, utilise $w^{\mathrm{p}}_{u}$, $A^{\mathrm{p}}$ et `pureYieldDebt`) :

$$
\mathrm{due}_{u} \;=\; \left(\left\lfloor \frac{w_{u}\,A}{10^{18}} \right\rfloor - c_{u}\right)^{+}, \qquad c_{u} \;\leftarrow\; \left\lfloor \frac{w_{u}\,A}{10^{18}} \right\rfloor
$$
^e29

et après chaque changement de poids, l’écrivain unique refixe le point de contrôle $c_{u} = \lfloor w_{u}A/10^{18}\rfloor$ (`_checkpoint`, L1736), de sorte qu’une position ne gagne que sur les fenêtres qui se clôturent après la mise en place de son poids.

**Boost.** Le poids est le stake de la position multiplié par un boost qui est un polynôme du verrouillage pour lequel elle est *actuellement* rémunérée (`boostByDays`, L496–505 ; `_effectiveLockDays`, L1687 ; `_computeBoost`, L1695–1700). Avec $\ell$ le verrouillage en jours — nul dès que la date de déverrouillage est passée, que la position ait été normalisée ou non — plafonné à 2,555 :

$$
b(\ell) \;=\; 10^{4} + \left\lfloor \frac{750\,\ell}{365} \right\rfloor + \left\lfloor \frac{250\,\ell^{2}}{365^{2}} \right\rfloor \ \text{bps}, \qquad w_{u} = \left\lfloor \frac{(s_{u} - d_{u})^{+}\, b}{10^{4}} \right\rfloor, \qquad w^{\mathrm{p}}_{u} = \begin{cases} \lfloor s_{u}\, b / 10^{4} \rfloor & d_{u} = 0 \\ 0 & d_{u} > 0 \end{cases}
$$
^e30

avec $s_{u}$ le stake de la position et $d_{u}$ sa dette. Valeurs exactes : $b(90) = 10{,}199$, $b(365) = 11{,}000$, $b(730) = 12{,}500$, $b(1{,}825) = 20{,}000$, $b(2{,}555) = 27{,}500$ bps. Le poids d’émission porte sur le stake net ; le poids du rendement pur n’existe que pour une position sans dette — un emprunteur ne perçoit pas les intérêts qu’il paie.

### §25.4 Intérêts : la courbe et l’indice

**La courbe.** L’utilisation est $U = \min(\lfloor D \cdot 10^{18} / S \rfloor, 10^{18})$, avec $D$ = `totalDebt` et $S$ = `totalStaked` ($U = 10^{18}$ si $S = 0 < D$ ; le taux de base si les deux sont nuls), et le taux annuel en bps présente un coude à 80 % (`_interestRate`, L1768–1782) :

$$
r(U) \;=\; \begin{cases} 100 + \left\lfloor 500\,U / 10^{18} \right\rfloor & U \le 0.8 \cdot 10^{18} \\ 500 + \left\lfloor 72{,}500\,(U - 0.8\cdot 10^{18}) / 10^{18} \right\rfloor & U > 0.8 \cdot 10^{18} \end{cases}
$$
^e31

Les deux branches se rejoignent au coude — $100 + 0.8 \times 500 = 500$ — et la courbe atteint $500 + 0.2 \times 72{,}500 = 15{,}000$ bps à pleine utilisation. Un emprunt ne peut pas porter le bilan au-delà de 75 % (E41), de sorte qu’aucun emprunt n’entre à lui seul dans la branche raide.

**L’indice.** Chaque tranche écoulée $\Delta t$ est estampillée du taux en vigueur sur toute sa durée, avant toute écriture dans $D$ ou $S$ (`_interestSlice`, L1589–1619 ; `_updateInterestIndex`, L1539–1583), avec $Y$ = 365 jours :

$$
\delta \;=\; \left\lfloor \frac{\lfloor 10^{18}\, r(U) / 10^{4} \rfloor \cdot \Delta t}{Y} \right\rfloor, \qquad \sigma \;=\; \left\lfloor \frac{D\,\delta}{10^{18}} \right\rfloor, \qquad I \leftarrow I + \delta, \quad S \leftarrow S - \sigma
$$
^e32

Si $\sigma$ devait dépasser $S$, l’avancée de l’indice est redérivée comme $\delta = \lfloor S \cdot 10^{18}/D \rfloor$ et $\sigma$ est recalculé à partir de celle-ci, de sorte que les prélèvements par position ne totalisent jamais plus que le débit global. L’arithmétique sature à zéro au lieu de provoquer un revert, de sorte que les points d’entrée qui s’appuient sur elle ne peuvent pas se bloquer, et en situation d’urgence l’horloge glisse sans prélever (L1547). Une seule fonction calcule la tranche pour l’exécution et pour chaque devis, de sorte que les deux ne peuvent pas arrondir différemment.

**La répartition.** La tranche quitte le stake et est répartie dans la même étape (L1560–1580 ; `_dampPure`, L1623–1625), avec $W_{\mathrm{p}}$ = `totalBoostedPure` et $A^{\mathrm{p}}$ = `accPureYieldPerShare` :

$$
\kappa_{r} = \left\lfloor \frac{300\,\sigma}{10^{4}} \right\rfloor, \quad \pi = \sigma - \kappa_{r}, \quad \eta = \left\lfloor \frac{\pi \cdot \min(W_{\mathrm{p}}, W_{\min})}{W_{\min}} \right\rfloor, \quad A^{\mathrm{p}} \leftarrow A^{\mathrm{p}} + \left\lfloor \frac{\eta \cdot 10^{18}}{W_{\mathrm{p}}} \right\rfloor, \quad \mathtt{protocolReserve} \mathrel{+}= \kappa_{r} + (\pi - \eta)
$$
^e33

donc $\sigma = \kappa_{r} + \eta + (\pi - \eta)$ exactement ; en l’absence de poids pur, la totalité de $\pi$ va à la réserve. Le bridage est le même que celui de l’émission (E28), avec la même constante.

**Attribution.** Le côté global a été réglé au moment où les intérêts ont couru ; la part propre d’une position lui est attribuée la prochaine fois qu’elle est touchée (`_accrueInterestFor`, L1627–1671), avec $I_{u}$ son point de contrôle de l’indice :

$$
i_{u} \;=\; \left\lfloor \frac{d_{u}\,(I - I_{u})}{10^{18}} \right\rfloor; \qquad i_{u} > s_{u} \;\Rightarrow\; \lambda = i_{u} - s_{u} \ \text{enters E37},\ S \mathrel{+}= \lambda,\ i_{u} \leftarrow s_{u}; \qquad s_{u} \leftarrow s_{u} - i_{u}, \quad I_{u} \leftarrow I
$$
^e34

Les intérêts sont payés sur le stake ; la dette elle-même n’augmente pas. Une position sans dette ne fait que refixer son point de contrôle, de sorte qu’elle n’hérite jamais de la croissance de l’indice d’une période où elle ne devait rien. Les intérêts qu’une position ne peut pas payer ont néanmoins été distribués par E33 ; les réintégrer à $S$ maintient $S = \sum_{u} s_{u}$, et la perte est comptabilisée par E37 au moment où elle est connue.

### §25.5 La réserve, la solvabilité et la cascade des pertes

**Conservation.** Avec $B$ le solde de BZPX du contrat, $D$ = `totalDebt`, $\mathit{BD}$ = `totalBadDebt`, $S$ = `totalStaked`, $\mathit{RR}$ = `rewardReserve`, $\mathit{PR}$ = `protocolReserve` et $\Pi$ = `totalRewardDistributed − totalRewardsPaid` (distribué, pas encore payé), chaque fonction qui porte le modificateur `conserves` prend un instantané des deux côtés avant son corps et provoque un revert après celui-ci, sauf si (L419–438 ; `_conservationSnapshot`, `_conservationCheck`, L449–457) :

$$
\left|\, \Delta(B + D + \mathit{BD}) \;-\; \Delta(S + \mathit{RR} + \mathit{PR} + \Pi) \,\right| \;\le\; \varepsilon, \qquad \varepsilon = \mathtt{CONSERVATION\_DUST} = 10^{10}\ \text{wei}
$$
^e35

Chaque terme est une unique variable d’état non négative, sommée sans plancher, de sorte que l’identité est linéaire dans tous les régimes — y compris la détresse terminale, où les outils de redressement (`repay`, `liquidate`, `absorbBadDebt`) doivent continuer à fonctionner — et seule la variation au cours d’un appel est comparée, de sorte que l’historique ne peut pas s’accumuler en une fausse alerte.

**Solvabilité.** Ce que le contrat doit, et le test public indiquant s’il le détient (`_owed`, L1794–1798 ; `_hardBreach`, L1808–1810 ; `isSolvent`, L1824) :

$$
\mathrm{owed} \;=\; (S + \mathit{RR} + \mathit{PR} - D)^{+} \;+\; \Pi, \qquad \mathrm{hardBreach} \iff B + \varepsilon < \mathrm{owed}, \qquad \mathtt{isSolvent}() = \neg\,\mathrm{hardBreach}
$$
^e36

*Dérivation du lien avec la créance irrécouvrable.* Là où $S + \mathit{RR} + \mathit{PR} \ge D$, E35 donne $\Delta(B - \mathrm{owed} + \mathit{BD}) = 0$ (à $\varepsilon$ près) sur chaque appel gardé, donc $B - \mathrm{owed} = c - \mathit{BD}$, avec $c$ le solde détenu au-delà du grand livre. La créance irrécouvrable enregistrée est donc exactement le terme qui pousse le bilan vers une violation grave, et le bilan apparaît insolvable dès que $\mathit{BD} > c + \varepsilon$. En cas de violation grave, les portes qui paieraient au pair ou ajouteraient de l’argent neuf à une cagnotte déficitaire provoquent un revert — `deposit`, `borrow`, `withdraw`, `claimRewards`, `claimPureYield` et le règlement du rendement pur qu’elles contiennent, `withdrawReserve`, `cancelEmergency` (L760, L826, L902, L932, L948, L1510, L539, L620) — tandis que `repay`, `liquidate` et `absorbBadDebt` restent ouvertes, et n’importe qui peut appeler `tripBreaker` pour faire passer le contrat en situation d’urgence (L597).

**La cascade.** Une perte $\lambda$ ne survient qu’en trois endroits : une liquidation dont la dette dépasse le stake (L1101–1106, L1128), une sortie d’urgence sous l’eau (L704–709), et des intérêts qu’une position ne peut pas payer (L1657–1663). Les trois appliquent le même ordre :

$$
\kappa \;=\; \min(\lambda,\ \mathit{PR}), \qquad \mathit{PR} \leftarrow \mathit{PR} - \kappa, \qquad \mathit{BD} \leftarrow \mathit{BD} + (\lambda - \kappa)
$$
^e37

La réserve — constituée à partir du facteur de réserve de chaque tranche d’intérêts (E33) et des bonus d’auto-liquidation — absorbe en premier ; seul le reste non couvert devient une créance irrécouvrable enregistrée. La recapitalisation est une porte publique qui prend d’abord la réserve, puis au plus ce que l’appelant offre et jamais plus que ce qui reste non couvert (`absorbBadDebt`, L639–649), avec $a$ le montant de l’appelant :

$$
\rho \;=\; \min(\mathit{BD},\ \mathit{PR}), \qquad \gamma \;=\; \min(a,\ \mathit{BD} - \rho), \qquad \mathit{PR} \leftarrow \mathit{PR} - \rho, \qquad \mathit{BD} \leftarrow \mathit{BD} - \rho - \gamma, \qquad B \leftarrow B + \gamma
$$
^e38

Elle provoque un revert lorsque $\rho + \gamma = 0$, ne fait rien sortir et ne verse rien à l’appelant ; E35 tient par construction ($\Delta \mathrm{LHS} = \gamma - \rho - \gamma = -\rho = \Delta \mathrm{RHS}$). Dès que $\mathit{BD}$ est revenu dans la marge de dust, `isSolvent()` renvoie vrai et l’administrateur peut annuler l’urgence.

**Liquidation.** Une position est liquidable si et seulement si $d_{u} > 0$ et ($s_{u} = 0$ ou $100\,d_{u} \ge 95\,s_{u}$) (`_isLiquidatable`, L1133–1137). Sa clôture (`_executeLiquidation`, L1079–1131) :

$$
\mathrm{seized} \;=\; \min\!\left(d_{u} + \left\lfloor \frac{500\,d_{u}}{10^{4}} \right\rfloor,\ s_{u}\right), \qquad \mathrm{bonus} \;=\; (\mathrm{seized} - d_{u})^{+}, \qquad \lambda \;=\; (d_{u} - s_{u})^{+} \ \text{to E37}
$$
^e39

Le bonus va à l’appelant, sauf lorsqu’un emprunteur clôture sa propre position, auquel cas il reste dans `protocolReserve`. Les flux acquis par la position sont versés avant sa clôture, et tout stake restant demeure sous la forme d’une position sans dette qui conserve son verrouillage.

**Sortie d’urgence.** Avec $\mathrm{pot} = B$ et $E_{+}$ = `totalPositiveEquity` $= \sum_{u} \max(s_{u} - d_{u}, 0)$ tel que suivi, la sortie verse (`emergencyWithdraw`, L658–725 ; la mise à l’échelle, L689–696) :

$$
x_{0} \;=\; (s_{u} - d_{u})^{+}, \qquad E' \;=\; E_{+} - e_{u} + x_{0}, \qquad x \;=\; \min\!\left( \begin{cases} \lfloor x_{0}\,\mathrm{pot} / E' \rfloor & \mathrm{pot} < E' \\ x_{0} & \text{otherwise} \end{cases},\ \mathrm{pot} \right)
$$
^e40

où $e_{u}$ est le terme suivi de celui qui sort, remplacé par ses fonds propres exacts après l’attribution des intérêts. Sur un bilan solvable, la mise à l’échelle ne s’enclenche jamais et les fonds propres nets sont versés intégralement ; sur un bilan déficitaire, chaque sortie reçoit la même fraction de sa créance, et une sortie sous l’eau comptabilise son déficit via E37 (L704–709).

![FIG-21 — La cascade des pertes E37–E38 : protocolReserve absorbe en premier, le reste est enregistré comme totalBadDebt et lu par isSolvent(), et absorbBadDebt recapitalise depuis la réserve, puis depuis l’appelant, jamais au-delà de ce qui est non couvert ; proportions illustratives, aucun montant de token.](https://blazephoenix.xyz/whitepaper/figs/fr/fig-21.png)

**Plafonds d’emprunt.** Un emprunt de $a$ doit satisfaire les deux conditions (`borrow`, L834–840 ; `_ltvCap`, L1908–1909) :

$$
d_{u} + a \;\le\; \left\lfloor \frac{50\,(s_{u} - d_{u})^{+}}{100} \right\rfloor, \qquad \left\lfloor \frac{(D + a)\cdot 10^{18}}{S} \right\rfloor \;\le\; 0.75 \cdot 10^{18}
$$
^e41

la première sur le stake net de la position, la seconde sur l’ensemble du bilan, maintenu sous le coude à 80 % de E31.

**Maintenance.** Chaque transaction utilisateur, sauf si le contrat est en pause ou en situation d’urgence, parcourt aussi une fenêtre du registre des emprunteurs (en liquidant ce qui est sous l’eau) et du registre des verrouillages (en normalisant les verrouillages expirés), chacune de taille (`_windowBudget`, L1148–1157 ; `_autoMaintain`, L1170–1178) :

$$
c(n) \;=\; \min\!\left(1 + \left\lfloor \frac{n}{50} \right\rfloor + \left\lfloor \frac{t - t_{m}}{15\ \mathrm{min}} \right\rfloor,\ 10,\ n\right)
$$
^e42

avec $n$ la taille du registre et $t_{m}$ le dernier balayage ; au plus quatre normalisations de verrouillage s’exécutent par transaction (L263). Le calendrier est une fonction pure de l’état on-chain — ni keeper, ni gouvernance — et son gas est borné dans tous les cas.

**Invariants.** La conception ajoute neuf invariants, INV-23 à INV-31 (Annexe B) : l’identité de conservation, le plafond d’émission, l’ordre des pertes, les portes fermées en cas de violation, la borne de recapitalisation, l’écrivain unique des poids, les plafonds d’emprunt, la sortie d’urgence bornée et la règle d’ancienneté des dépôts.

### §25.6 Durcissement dans V4

Trois modifications fusionnées ont façonné le code source de V4 après la conception qui est déployée sur Base ; chacune est décrite ici au niveau de la conception qu’elle a produite.

- **#22 (`b44e238`, 2026-10-11).** Les intérêts sont estampillés par tranche par une seule fonction partagée entre l’exécution et chaque devis (E32), de sorte qu’un aperçu et une transaction calculent le même prélèvement, au taux en vigueur sur la tranche. Le canal du rendement pur est bridé par le même plancher de poids que l’émission (E33), de sorte qu’un dénominateur résiduel ne peut pas capter le flux. Un emprunteur qui clôture sa propre position ne reçoit aucun bonus de liquidation. Des vues de risque en lecture seule ont été ajoutées (`healthFactor`, `daysToLiquidation`, `maxBorrowOf`).
- **#26 (`7fc0c2a`, 2026-10-13).** Un seul ordre des pertes pour les trois sites de perte : la réserve d’abord, puis la créance irrécouvrable enregistrée (E37) ; une porte publique de recapitalisation (E38) ; les portes qui déplacent de la valeur, fermées en cas de violation grave (E36) ; un constructeur qui refuse une adresse de token sans code ; la vérification de conservation émise une seule fois sous forme de deux fonctions auxiliaires internes au lieu d’être intégrée en ligne à chaque site gardé, l’identité restant inchangée ; et une suite de tests d’assurance.
- **#25 (`cc614f9`, 2026-10-14).** Documentation uniquement : de courtes lignes de résumé dans les guides et les en-têtes de tests. Aucun code source du contrat n’a changé.

### §25.7 Preuves par les tests

Comptés sur `origin/main` à `cc614f9`, à partir de l’arbre source, et non d’une exécution : 37 fichiers `*.t.sol` (`git ls-tree -r --name-only origin/main | grep -c '\.t\.sol$'`), déclarant 256 fonctions `test*` et 14 fonctions `invariant*` (`git grep -E '^\s*function (test|invariant)' origin/main -- '*.t.sol'`, réparties par préfixe). Le README du dépôt (ligne 84) fait état d’un harnais Node de neuf suites à côté d’eux. Ce sont des décomptes de ce qui est déclaré ; ce document ne rapporte pas de taux de réussite pour la suite de staking.

---

## Historique des révisions

### Version 2.3.0 (2026-10-14)

2.3.0 — ajoute le chapitre de conception du Staking V4 (§25), avec les équations E27–E42, les invariants INV-23–INV-31 et les figures FIG-19–FIG-21 ; il remplace l’énoncé ci-dessous selon lequel le moteur de staking n’est plus décrit dans ce document. Ailleurs, seuls changent les étiquettes de version, les plages d’énumération, les introductions des annexes et les lignes DOI ; le reste est le texte de la 2.3.

### Version 2.3 (2026-09-24)

La version 2.3 prolonge la ligne 2.2 ; la numérotation 3.0 employée brièvement pour un brouillon de ce texte est retirée, comme l’avait été avant elle la numérotation 3.0 du 2026-09-01. Ce qui a changé par rapport à la 2.2 : les dix-huit innovations sont nommées, numérotées I-1 à I-18 et énoncées avec leurs mathématiques en tête du document, avec un tableau des scores du bilan mesuré ; chaque chiffre est re-mesuré sur `main` à `8916545` ; le moteur de staking n’est plus décrit ici (sa propre spécification le porte) ; la section du token consigne l’unique pool Airdrop & Bounties de 130,000,000 BZPX ; et les errata de la 2.2, ci-dessous, sont intégrés au texte qu’ils corrigeaient.

### Errata de la 2.2 (2026-09-07)

Appliqués après une relecture hostile du texte du 2026-09-06 contre `main 8949a9d` ; la ligne de version est restée 2.2. Chaque point nomme ce que disait le texte et ce que dit le checkout.

1. §4.2 — l’écrêtage de borne `sqrtBoundary` est défini dans le Core mais pas encore câblé dans le classement ni dans la promesse dans le cadre ; le texte disait qu’il était appliqué du côté de la promesse. Point ouvert : le câbler (`Router._v4LegQuote`, branche de classement du Core), avec un test écrit d’abord en rouge qui livre moins que le devis net au-delà du tampon E21 lorsqu’une jambe V4 sort de sa plage.
2. §10.4 — l’écrêtage de capacité exclut les jambes V4 (le Solver saute la lecture de solde pour les types 4 et 8) ; le texte disait que toute jambe concentrée était écrêtée. Point ouvert : une borne physique pour le singleton.
3. §5.4 — une clé V4 à frais statiques ignore les frais de protocole du manager (nuls sur chaque chain servie aujourd’hui) ; le texte qualifiait les frais de la clé d’univoques.
4. §9.3 — la marge d’éviction descend jusqu’à environ 25 % (titulaire 51, nouveau venu 64), et non 3 % ; le chiffre précédent violait E13 et avait été copié d’un commentaire du code source.
5. §15 et le résumé — 43 fonctions `invariant_*` (la commande imprimée en renvoie 43), 217 fichiers `.t.sol`.
6. §14.1 — `HubE(6)` n’existe pas ; l’épuisement des factories est `HubE(4)`, l’épuisement des emplacements est `_canInsert`.
7. §14.2 — « une seule couche suffit » est retiré : les couches bornent l’altération des deltas ; le résidu de substitution des frais du §5.4 est borné par le plancher.
8. §11.4, INV-21 — la règle de forme de route est l’ordre canonique sur toute la route (`sawHooked`), et non « hooké seulement dans le dernier hop ».
9. §11.3, E24 — la marge du plancher agrégé des hops est divisée par BPS : 20 % de la jambe attestée moyenne.
10. §17.3 et l’annexe C — le harnais `FactoryCensus` vit dans l’arbre de déploiement privé ; le tableau est rapporté, non reproductible depuis ce checkout.
11. §17.5 — la référence de gas est nommée (KyberSwap `MetaAggregationRouterV2`, fork de Base, 2026-08-22, Paraswap comme second comparateur) et le rapport par transaction (2.7×) est imprimé à côté du rapport par jambe ; le harnais et le calldata capturé sont privés, de sorte que les chiffres sont rapportés, non reproductibles depuis ce checkout.
12. §17.2 — « la première agrégation routée par V4 de la chain » est retiré ; « exact » est remplacé par la bande mesurée (0–1 bps).
13. INV-8, INV-10, INV-15, INV-20, INV-22 — citations d’application corrigées vers les tests et les codes qui les portent ; INV-22 nomme les quatre contrats déployés.
14. §1 — l’étude des agrégateurs est bornée aux sept nommés, telle que documentée le 2026-09-07.
15. Références 16 et 20 — EIP-1153 est de A. Akhunov et M. Salem ; EIP-7702 de V. Buterin, S. Wilson, A. Dietrichs et M. Garnett.
16. §2.3, §15.11, §21 — les comptes d’étalonnage d’instruments et de motifs de défauts sont remplacés par la règle d’étalonnage elle-même.
- §14.2, couche 2 (2026-09-07, admission des hooks) : la liste d’autorisation organise la porte automatique et l’épinglage du codehash met en pause un hook listé dont le code a bougé, sur chaque porte. Un hook dont les bits d’adresse ne portent aucune permission de swap ne s’exécute jamais dans un swap et n’a besoin ni de liste ni d’épinglage. Un hook qui s’exécute dans le swap et que personne n’a listé est accepté sur la porte explicite comme choix signé de l’appelant, borné comme toute place par la promesse dans le cadre, la barrière d’attestation et `userMinOut` ; il n’est jamais proposé par la porte automatique. Enregistrer un pool à hook admet et épingle son hook, de sorte qu’un pool à hook est une seule étape d’opérateur. Les trois couches tiennent ; « fermé par défaut » décrit la porte automatique.

### Errata de la 2.2 (2026-09-24)

Appliqués après la neuvième vague de bounties ; la ligne de version est restée 2.2.

1. §4.2, et point 1 des errata du 2026-09-07 — clos. Un pool V4 est coté en parcourant son propre carnet : son prix, sa liquidité, son bitmap de ticks et ses ticks, lus par `extsload`, avec l’arithmétique de pas propre au PoolManager, pour le classement comme pour la promesse (`Core.v4WalkOut`). L’écrêtage de borne `sqrtBoundary` est retiré. L’énoncé selon lequel le répartiteur ne réplique pas l’arithmétique de franchissement de ticks ne vaut désormais que pour la famille V3.
2. §10.4, et point 2 des errata du 2026-09-07 — clos pour l’usage qu’il servait : le parcours ne compte que la liquidité que le carnet détient dans les ticks qu’il franchit, de sorte qu’une jambe V4 est bornée par ce que détient son propre pool (`test_AOneTickRangeWithNothingBeyondPaysWhatItHolds`).
3. Point 3 des errata du 2026-09-07 — clos : une clé à frais statiques compose les frais de protocole du manager avec les frais LP de la clé dans le sens du swap (`Core.effV4Fee` ; `test_StaticKey_ProtocolFee_ReachesThePromise`).
4. §4 — chaque lecture que le chemin de devis fait d’un pool est plafonnée en gas et copie un nombre fixe de mots ; les deux qui étaient des appels de haut niveau (le `getAmountOut` de la paire Solidly, et `factory()` puis `getFee`) interrogent par `Core._askWord` (`test/APoolsAnswerIsBoundedByTheReader.t.sol`).

## Annexe A · Les équations primitives

Toute affirmation quantitative de ce document se ramène à l’une des suivantes. Chacune est implémentée une seule fois, dans le contrat nommé, et importée partout ailleurs. La numérotation E1–E22 est stable depuis l’édition précédente ; E23–E26 sont nouvelles et ajoutées à la suite. E27–E42 (2.3.0) sont celles du moteur de staking, définies dans `src/BlazePhoenixStaking.sol` du dépôt de staking à `cc614f9` (§25) ; « Staking » dans la quatrième colonne désigne ce contrat, avec ses numéros de ligne.

| # | Nom | Équation | Défini dans | § |
|---|---|---|---|---|
| E1 | Méta-Équation | $R^{\star} = \arg\max_{R}\ \mathrm{net}(R)\cdot\prod_{\ell}\hat{\Psi}(p_{\ell})\cdot\mathbf{1}[\mathrm{net}(R)\ge\Phi(R)]\cdot\Xi(R)$ | Pipeline du Solver | §3 |
| E2 | Sortie à produit constant | $y = \lfloor x(\mathrm{BPS}-f)\,r_{\mathrm{out}} / (r_{\mathrm{in}}\,\mathrm{BPS} + x(\mathrm{BPS}-f)) \rfloor$ | Core `outV2` | §4.2 |
| E3 | Sortie à liquidité concentrée (dans un tick) | $\sqrt{P}' = L\sqrt{P}/(L + x_{f}\sqrt{P}/Q_{96})$, $y = L(\sqrt{P}-\sqrt{P}')/Q_{96}$; forme duale pour l’autre sens | Core `outV3` | §4.2 |
| E4 | Emplacement de base du storage V4 | $\mathrm{slot} = \mathrm{keccak256}(\mathrm{abi.encode}(\mathrm{poolId}, 6))$; $\sqrt{P}$ dans les bits [0,160), frais LP dans [208,232) du mot 0 ; $L$ dans le mot 3 | Core `v4SqrtAndLiq` | §5 |
| E5 | Invariant Solidly stable | $k(x,y) = x^{3}y + xy^{3}$ | Core `_solK` | §6 |
| E6 | Pas de Newton Solidly | amorce $y_{0} = Y$ ; pas montant $\max(1, \lfloor (K-k)/f' \rfloor)$; pas descendant $\max(1, \lfloor (k-K)/f' \rfloor)$, remplacé par $\lfloor y/2 \rfloor$ lorsqu’il franchirait zéro ; sortie à $\lvert y_{n+1}-y_{n}\rvert \le 1$ avec une majoration d’une unité contre le preneur ; plafond 64 → amorce → devis 0 | Core `_solY` | §6 |
| E7 | Normalisation décimale | $X = r_{\mathrm{in}}10^{18-d_{\mathrm{in}}}$, $Y = r_{\mathrm{out}}10^{18-d_{\mathrm{out}}}$, remis à l’échelle après la résolution | Core `_solidlyStable` | §6 |
| E8 | Dérivation CREATE2 | $\mathrm{pool} = \mathrm{addr}_{20}(\mathrm{keccak256}(\mathtt{0xff} \Vert \mathrm{origin} \Vert \mathrm{salt} \Vert \mathrm{initCodeHash}))$ | Core `create2Address`, `deriveAddress` | §7 |
| E9 | Empaquetage du Monoslot | $s = a \,\vert\, f\!\ll\!8 \,\vert\, k\!\ll\!32 \,\vert\, r\!\ll\!40 \,\vert\, (c\wedge\mathtt{0xFFF})\!\ll\!48 \,\vert\, b\!\ll\!60 \,\vert\, \dots \,\vert\, B_{\mathrm{last}}\!\ll\!224$ | Core `encodeSlot` | §8 |
| E10 | Champ de Vitalité | $\Psi = V \cdot 2^{b} \cdot (1 + 0.25\,\mathbf{1}_{\mathrm{bridge}}) \cdot (1 + 0.05\,\mathbf{1}_{\mathrm{conc}})$ | Core `psi` | §9.2 |
| E11 | Décroissance de l’activité | $V = \mathrm{swapCount} \gg \lfloor (t - t_{\mathrm{last}})/24{,}576 \rfloor$; 0 dès que le décalage dépasse 31 | Core `_decayedSwapCount` | §9.2 |
| E12 | Palier de profondeur | $b = \min(15, \lfloor \log_{10}(d/10^{15}) \rfloor)$ | Core `depthBucket` | §9.2 |
| E13 | Seuil d’éviction | $2^{b(d_{\mathrm{new}})} > \Psi_{\mathrm{worst}} + \lfloor \Psi_{\mathrm{worst}}/4 \rfloor$ | Hub `_canInsert` | §9.3 |
| E14 | Barrière de répartition | retenir la répartition ssi $\mathrm{out}_{\mathrm{split}} \ge \mathrm{out}_{\mathrm{single}}\cdot(1 + 25/10^{6})$, $\mathrm{out}_{\mathrm{single}}$ étant le meilleur du candidat le plus profond et du candidat au meilleur taux, à pleine taille | Solver `_buildHop` | §10.4 |
| E15 | Plancher Iron-Law | $\mathtt{floorBps} = \max(9{,}600 - \mathtt{legShv} - \min(\delta, 10^{4}) - \sigma_{\ln}/10^{14},\ 8{,}000)$ | Core `ironFloorBpsShv` | §11.5 |
| E16 | Minimum effectif | $\mathtt{effMin} = \max(\mathtt{userMinOut},\ \mathtt{route.singleOutFloor},\ \lceil \mathtt{finalHopQuote}\cdot\mathtt{floorBps}/10^{4}\rceil)$; le deuxième terme supprimé et le troisième remis à l’échelle par le ratio net mesuré lorsque des frais sur transfert sont détectés | Router `_execute` | §11.5 |
| E17 | Frais de protocole | $\mathrm{fee} = \lceil 28 \cdot \mathrm{base} / 10^{4} \rceil$, base = l’entrée mesurée du hop de frais (ancré), la sortie brute (route directe vers un token pont) ou l’entrée mesurée de chaque hop (épuisement); $t_{1} = \lfloor 3{,}000\cdot\mathrm{fee}/10^{4} \rfloor$, $t_{2} = \mathrm{fee} - t_{1}$ | Router `_chargeHopFee`, `_payFee` | §13 |
| E18 | Barrière de hook | $(\mathrm{uint160}(\mathrm{hook}) \,\&\, \mathtt{0x3FFF}) \,\&\, ((1\ll 3)\mid(1\ll 2)) \ne 0 \Rightarrow$ jambe refusée, aucun appel effectué | Core `hookAltersDeltas`; Router | §14.2 |
| E19 | Barrière de frais dynamiques | $f_{\mathrm{eff}} = f_{\mathrm{key}}$ si statique ; non cotable si la sentinelle accompagne des frais de protocole non nuls ; sinon les frais LP en direct lus dans `slot0` / `globalState` / le `fee()` du pool | Core `effV4Fee`, `quoteV3Fee` | §5.4 |
| E20 | Plafond des frais V2 | $f_{\mathrm{eff}} = f_{\mathrm{decl}}$ si $0 < f_{\mathrm{decl}} \le 100$ bps, sinon 30 | Core `effV2Fee`; même règle pour le repli déclaré de Solidly | §4.2 |
| E21 | Tampon de sécurité de l’aperçu | $s = \min(\max(0, n-2) + 5a,\ 10)$ bps | Quoter `_pack` | §12.1 |
| E22 | Choix de surface | porter la route en calldata ssi $p_{\mathrm{byte}} < \rho^{*}$; $\rho^{*}$ mesuré à 66.9–105.5 gas/octet selon la forme de la route | `test/EquationBench.t.sol` | §17.6 |
| E23 | Rognage de jambe dû à la concentration | $\mathtt{legShv} = \sum_{\mathrm{hops}} \lfloor 200\,((\sum a)^{2} - \sum a^{2})/\sum a^{2} \rfloor$ | Core `legShaveBps` | §11.5 |
| E24 | Plancher agrégé des hops | $\sum \mathrm{got} + \lfloor (\mathrm{BPS} - 8{,}000)\cdot \lfloor \sum \mathrm{attested}/n \rfloor / \mathrm{BPS} \rfloor \ge \sum \mathrm{attested}$ | Router `_execute` | §11.3 |
| E25 | Centre de crédibilité | la médiane pondérée par la profondeur : trier $(r_{i}, d_{i})$ selon $r_{i}$, renvoyer le premier $r_{i}$ pour lequel $\sum_{j \le i} d_{j} \ge \lceil \sum d / 2 \rceil$; bande $[0.95, 1.05] \times$ centre | Solver `_depthWeightedMedian` | §10.3 |
| E26 | Prédicat de régime de frais | $\mathrm{feeHop} = \min\{h : \mathrm{isBridge}(\mathrm{hops}[h].\mathrm{tokenIn})\}$; prélever en $h$ ssi $\neg\mathrm{feeOnOut} \wedge (\mathrm{feeHop} = \infty \vee h = \mathrm{feeHop})$; $\mathrm{feeOnOut} \iff$ un seul hop et $\mathrm{isBridge}(\mathrm{tokenOut})$ | Router `_execute` | §13.1 |
| E27 | Forme close de l’émission | $E(t) = (T - (T \gg p)) + (R_{0} \gg p)(t - s - pP)$, $p = \lfloor (t - s)/P \rfloor$; $0$ jusqu’à $s$ ; $T - (T \gg 8)$ à partir de $s + 8P$ | Staking `_emittedAt` (L1458–1467) | §25.3 |
| E28 | Fenêtre d’émission | $r = \min(\lfloor (E(t') - E(t_{\ell}))\min(W, W_{\min})/W_{\min} \rfloor,\ \mathit{RR})$; $A \mathrel{+}= \lfloor r \cdot 10^{18}/W \rfloor$ | Staking `_updateGlobal` (L1428–1448) | §25.3 |
| E29 | Règlement | $\mathrm{due}_{u} = (\lfloor w_{u}A/10^{18} \rfloor - c_{u})^{+}$, puis $c_{u} \leftarrow \lfloor w_{u}A/10^{18} \rfloor$ | Staking `_settlePendingRewards`, `_settlePendingPureYield` (L1487–1517) | §25.3 |
| E30 | Boost de verrouillage et poids | $b(\ell) = 10^{4} + \lfloor 750\ell/365 \rfloor + \lfloor 250\ell^{2}/365^{2} \rfloor$ bps, $\ell$ étant le verrouillage en vigueur ; $w_{u} = \lfloor (s_{u} - d_{u})^{+} b/10^{4} \rfloor$; $w^{\mathrm{p}}_{u} = \lfloor s_{u} b/10^{4} \rfloor$ ssi $d_{u} = 0$ | Staking `boostByDays`, `_computeBoost` (L496–505, L1695–1700) | §25.3 |
| E31 | Courbe d’intérêts | $r = 100 + \lfloor 500U/10^{18} \rfloor$ pour $U \le 0.8 \cdot 10^{18}$, sinon $500 + \lfloor 72{,}500(U - 0.8 \cdot 10^{18})/10^{18} \rfloor$ bps par an | Staking `_interestRate` (L1768–1782) | §25.4 |
| E32 | Indice d’intérêts et tranche | $\delta = \lfloor \lfloor 10^{18} r/10^{4} \rfloor \Delta t / Y \rfloor$, $\sigma = \lfloor D\delta/10^{18} \rfloor$; si $\sigma > S$, $\delta = \lfloor S \cdot 10^{18}/D \rfloor$ et $\sigma$ recalculé | Staking `_interestSlice`, `_updateInterestIndex` (L1539–1619) | §25.4 |
| E33 | Répartition de la tranche | $\kappa_{r} = \lfloor 300\sigma/10^{4} \rfloor$ à la réserve ; $\eta = \lfloor (\sigma - \kappa_{r})\min(W_{\mathrm{p}}, W_{\min})/W_{\min} \rfloor$ aux stakers purs ; le reste à la réserve | Staking `_updateInterestIndex`, `_dampPure` (L1560–1580, L1623–1625) | §25.4 |
| E34 | Attribution des intérêts | $i_{u} = \lfloor d_{u}(I - I_{u})/10^{18} \rfloor$, imputés à $s_{u}$ ; tout excédent sur $s_{u}$ est une perte versée à E37 | Staking `_accrueInterestFor` (L1627–1671) | §25.4 |
| E35 | Garde de conservation | $\lvert \Delta(B + D + \mathit{BD}) - \Delta(S + \mathit{RR} + \mathit{PR} + \Pi) \rvert \le 10^{10}$ | Staking `conserves` (L419–457) | §25.5 |
| E36 | Dû et solvabilité | $\mathrm{owed} = (S + \mathit{RR} + \mathit{PR} - D)^{+} + \Pi$; violation franche ssi $B + 10^{10} < \mathrm{owed}$ | Staking `_owed`, `_hardBreach`, `isSolvent` (L1794–1824) | §25.5 |
| E37 | Cascade des pertes | $\kappa = \min(\lambda, \mathit{PR})$; $\mathit{PR} \mathrel{-}= \kappa$; $\mathit{BD} \mathrel{+}= \lambda - \kappa$ | Staking L1101–1106, L704–709, L1657–1663 | §25.5 |
| E38 | Recapitalisation | $\rho = \min(\mathit{BD}, \mathit{PR})$, $\gamma = \min(a, \mathit{BD} - \rho)$; $\mathit{BD} \mathrel{-}= \rho + \gamma$ | Staking `absorbBadDebt` (L639–649) | §25.5 |
| E39 | Liquidation | liquidable ssi $d_{u} > 0 \wedge (s_{u} = 0 \vee 100 d_{u} \ge 95 s_{u})$; $\mathrm{seized} = \min(d_{u} + \lfloor 500 d_{u}/10^{4} \rfloor, s_{u})$; perte $(d_{u} - s_{u})^{+}$ | Staking `_executeLiquidation`, `_isLiquidatable` (L1079–1137) | §25.5 |
| E40 | Décote d’urgence | $x_{0} = (s_{u} - d_{u})^{+}$, $E' = E_{+} - e_{u} + x_{0}$; $x = \lfloor x_{0}\,\mathrm{pot}/E' \rfloor$ si $\mathrm{pot} < E'$, sinon $x_{0}$; $x \le \mathrm{pot}$ | Staking `emergencyWithdraw` (L689–696) | §25.5 |
| E41 | Plafonds d’emprunt | $d_{u} + a \le \lfloor 50(s_{u} - d_{u})^{+}/100 \rfloor$ et $\lfloor (D + a) 10^{18}/S \rfloor \le 0.75 \cdot 10^{18}$ | Staking `borrow`, `_ltvCap` (L834–840, L1908–1909) | §25.5 |
| E42 | Budget de maintenance | $c(n) = \min(1 + \lfloor n/50 \rfloor + \lfloor (t - t_{m})/900\ \mathrm{s} \rfloor,\ 10,\ n)$ par registre | Staking `_windowBudget` (L1148–1157) | §25.5 |

## Annexe B · Les invariants

Ce sont les propriétés qui tiennent dans tout état atteignable : chacune est imposée par la forme du code au site nommé, et une transaction qui en violerait une ne s’exécute pas. INV-1–INV-18 gardent leurs numéros de l’édition précédente, avec la colonne d’application ramenée au code ; INV-19–INV-22 sont nouveaux. INV-23–INV-31 (2.3.0) sont ceux du moteur de staking (§25), imposés dans `src/BlazePhoenixStaking.sol` du dépôt de staking à `cc614f9`.

| # | Invariant | Application |
|---|---|---|
| INV-1 | Le Router ne détient aucun fonds entre les transactions ; son solde au repos est nul. | Balayages et paiement bornés par la ligne de base (§11) ; `invariant_RouterHoldsNothing`, `HoldsNothingBeyondTheSeed` |
| INV-2 | Tout swap est atomique : il n’existe aucun état dans lequel une partie d’un échange a été réglée. | Sémantique des transactions de l’EVM ; un seul cadre |
| INV-3 | Aucun oracle : aucun flux de prix externe n’entre dans l’admission, l’ordonnancement, la répartition, le plancher ou les frais. | Aucun appel à un flux n’existe dans les cinq contrats |
| INV-4 | Le taux de frais, la répartition des frais et les constantes du plancher n’ont aucun setter dans le bytecode déployé. | Constantes à la compilation dans le Core et le Router (§13) |
| INV-5 | Les champs de route fournis par l’appelant peuvent renforcer la protection, jamais l’affaiblir. | Maximum à trois termes d’E16 ; la barrière de couverture relève une attestation, ne l’abaisse jamais (§11) |
| INV-6 | Un swap à minimum nul ne s’exécute pas. | `RouterE(10)` à chaque porte |
| INV-7 | Ψ classe et tronque les candidats ; aucun devis, plancher ni allocation ne le lit. | Flux de données : rien en aval du classement ne consomme Ψ (§9.6) |
| INV-8 | Le registre d’une paire détient au plus seize pools, et l’éviction garde les plus aptes selon le Ψ complet avec la marge d’E13. | Hub `_canInsert` ; `invariant_neverExceedsMaxSlots` (le plafond) ; `test_RecordSwap_EvictsWeakestWhenFullAndNewcomerClearsMargin` (l’éviction) |
| INV-9 | Aucune adresse sans bytecode runtime n’entre dans une route. | `hasCode` sur chaque dérivation (§7) |
| INV-10 | Le callback universel ne paie que le pool engagé pour la jambe en cours, seulement tant qu’une jambe est en vol, et jamais plus que le budget enregistré de la jambe. | Contexte de jambe transitoire ; `RouterE(6)` pour le pool, `RouterE(8)` pour le budget (§11.4) |
| INV-11 | Un hook V4 dont l’adresse déclare des permissions altérant les deltas est rejeté avant tout mouvement de token, sans être appelé. | E18 ; `RouterE(9)` ; Solver `_topKPools` |
| INV-12 | Aucun produit de pricing ne peut déborder : chaque multiplication dangereuse passe par un `mulDiv` sur 512 bits, et un état qui ne peut être borné cote zéro. | Core (§4.2, §6) |
| INV-13 | L’arrondi va contre la sur-promesse : les devis et les paiements arrondissent à l’inférieur, les planchers de protection et les frais arrondissent au supérieur. | Core `mulDiv` / `mulDivUp` à chaque site |
| INV-14 | Là où une borne ne peut être prouvée, le calcul renvoie zéro et la place est abandonnée — jamais une supposition, jamais un revert planifiable à l’intérieur d’un devis. | Posture à échec fermé dans tout le Core ; `_solKFits` (§6) |
| INV-15 | La base des frais est le prélèvement mesuré, plafonné par l’entrée engagée de la route — jamais une sortie déclarée ; `route.totalOut` n’est lu par rien. | `_chargeHopFee` ; `test_FeeBase_IgnoresLiedAboutTotalOut` |
| INV-16 | La renonciation est à sens unique : le drapeau est écrit `true` en un site et `false` en aucun ; après elle, seul subsiste le niveau curateur à croissance seule, et une factory ou un hook déplacé ne peut être réarmé. | `renounceControl` sur le Hub et le Router (§14.7) |
| INV-17 | Des frais de repli déclarés pour un produit constant ou Solidly ne sont crus que dans un plafond de 100 bps ; au-delà, les 30 bps canoniques valorisent le pool. | E20 |
| INV-18 | Aucun hop ne porte plus de quatre jambes et aucune route plus de onze dans le plan du Solver ; le Router accepte au plus trois hops et cinq jambes par hop. | Budgets du Solver ; Router `_execute` → `RouterE(3)` |
| INV-19 | Un pool n’entre dans le registre que s’il prouve qu’il échange la paire : `token0()`/`token1()` pour les types en forme de paire, nouvelle dérivation de l’id du pool pour V4 ; un pool obtenu sur demande prouve la même chose avant que la découverte ne le liste. | Hub `recordSwap`, `_probe` ; `invariant_ActiveEntriesTradeThePair` |
| INV-20 | Un règlement paie les frais de protocole exactement une fois sur une route ancrée et une fois par hop sur une route épuisée ; un règlement qui n’a rien payé n’existe pas. | Grand livre des frais, `RouterE(15)`/`RouterE(16)` ; `invariant_SettledSwapEmitsExactlyOneFee` (ancré) ; `test/FeeSeals.t.sol`, `test/ExhaustionRegimePreviewParity.t.sol` (épuisement) |
| INV-21 | Les hops s’enchaînent — l’entrée de chaque hop est la sortie du hop précédent — et aucune jambe sans hook ne s’exécute après une jambe à hook, dans ce hop ou dans tout hop ultérieur (`sawHooked` vaut sur toute la route). | Router `_execute` → `RouterE(3)` ; `sawHooked` |
| INV-22 | Les quatre contrats déployés — Hub, Solver, Router, Quoter — sont livrés sous la barrière de 24,000 octets du projet sur le profil release, la bibliothèque Core est mesurée séparément (6,477 octets sur 24,576), et la suite s’exécute sur ce profil. | `DeployedSizeGate` ; `profile_parity.py` |
| INV-23 | Sur chaque point d’entrée du staking qui porte `conserves`, le solde plus la dette plus la créance irrécouvrable enregistrée varie du même montant que le stake plus les deux réserves plus les récompenses distribuées mais non payées, à 1e10 wei près (E35). | Staking `conserves`, L419–457 → `Staking__InvariantBreached` |
| INV-24 | L’émission planifiée ne dépasse jamais `TOTAL_REWARDS − (TOTAL_REWARDS >> 8)` ; le financement cumulé ne dépasse jamais `TOTAL_REWARDS` ; aucune fenêtre d’émission ne paie plus que ne détient `rewardReserve`. | `_emittedAt` L1458–1467 ; `fundEmission` L518 → `Staking__CapExceeded` ; `_updateGlobal` L1442 |
| INV-25 | Toute perte réalisée — un déficit de liquidation, une sortie d’urgence en moins-value, des intérêts irrécouvrables — puise d’abord dans `protocolReserve` ; seul le reste non couvert est ajouté à `totalBadDebt` (E37). | Staking L1101–1106, L704–709, L1657–1663 |
| INV-26 | Tant que le solde plus 1e10 est inférieur à `owed`, aucun dépôt, emprunt, retrait, réclamation de récompense ou de rendement pur, retrait de réserve ou annulation d’urgence ne s’exécute (E36). | Les barrières `_hardBreach` L760, L826, L902, L932, L948, L1510, L539, L620 → `Staking__InvariantBreached` |
| INV-27 | `absorbBadDebt` ne sort aucun BZPX, ne paie rien à l’appelant, et abaisse `totalBadDebt` d’au plus sa valeur courante : la réserve d’abord, le montant de l’appelant plafonné au reste non couvert (E38). | `absorbBadDebt` L639–649 |
| INV-28 | Les totaux du poids d’émission, du poids de rendement pur et de l’équité positive ne sont écrits que par `_applyBoost`, qui remplace le terme suivi d’une position par son terme recalculé ; un verrouillage ne gagne de boost que tant qu’il est en vigueur (E30). | `_applyBoost` L1726–1734 ; `_effectiveLockDays` L1687–1690 |
| INV-29 | Un emprunt ne s’exécute que si la nouvelle dette est au plus la moitié du stake net de la position avant lui et si l’utilisation du livre après lui est au plus de 75 % (E41). | `borrow` L834 → `Staking__LTVExceeded` ; L838–840 → `Staking__UtilTooHigh` |
| INV-30 | Une sortie d’urgence paie au plus l’équité nette de la position et au plus le solde du contrat, mis à l’échelle par pot/E′ lorsque le solde est inférieur à l’équité suivie (E40). | `emergencyWithdraw` L689–696 |
| INV-31 | Aucun retrait, aucune réclamation de rendement pur ni aucun changement de verrouillage ne s’exécute dans les `MIN_DEPOSIT_BLOCKS` = 10 blocs suivant le dernier dépôt de la position. | L894, L947, L971 → `Staking__FlashLoanProtection` |

## Annexe C · Commandes de reproduction

Chaque chiffre de ce document qui provient du dépôt est reproduit par l’une des commandes suivantes, depuis un checkout propre de `Blaze-Phoenix-Dex` à `8949a9d` avec Foundry et Python 3 installés ; les suites fork exigent une clé RPC d’archive dans `DRPC_KEY`.

```bash
forge test --no-match-path 'test/fork/**'                        # the suite, release settings (§15.1)
forge test --match-path 'test/fork/**'                           # the fork lane, five chains (§15.1, §17)
FOUNDRY_PROFILE=release forge build --sizes --skip test --skip script   # sizes against EIP-170 (§16.3)
python3 .github/scripts/mutants.py                                # the curated mutation guard, 268/268 (§15.2)
python3 .github/scripts/check_targets.py                          # every mutant still points at one line
python3 .github/scripts/assurance/covering_array.py --check       # both covering arrays current (§15.4)
forge test --match-contract RegimeCoverageTest -vv                # strength 2, 63 rows
forge test --match-contract RegimeCoverageT3Test -vv              # strength 3, 168 rows
forge test --match-path test/regime/HostileVenueMatrix.t.sol      # ten pathologies × two doors (§15.5)
forge test --match-path test/regime/SandwichCurve.t.sol -vv       # the sandwich curve (§14.3)
forge test --match-path test/regime/CanonicalOracles.t.sol        # the quote maths against the specifications (§15.6)
forge test --match-path 'test/*MetamorphicRelations.t.sol'        # 14 + 5 relations (§15.7)
FOUNDRY_PROFILE=nver1 forge build --skip '*.t.sol' && FOUNDRY_PROFILE=nver2 forge build --skip '*.t.sol'
NVERSION_LANE=1 forge test --match-path 'test/nversion/*'         # the three binaries agree (§15.8)
forge test --match-test invariant_ --fuzz-seed 0xb1a2ef00          # the stateful campaigns (§15.3)
forge test --match-path test/FeeSeals.t.sol                       # the fee rule, both regimes (§13.4)
forge test --match-path test/QuoteDelayStatistics.t.sol -vv       # how a quote ages, 240 samples (§12.2)
forge test --match-path test/QuoterGasStatistics.t.sol -vv        # what a quote costs (§12.3)
forge test --match-path test/fork/QuoteDelayFork.t.sol -vv        # live Base, 1,000 USDC → WETH (§12.2)
forge test --match-path test/fork/FidelityMatrix.t.sol -vv        # preview → delivery, four chains (§17.2)
forge test --match-path test/fork/DiscoveryColdWarm.t.sol -vv     # cold and warm solve (§17.4)
forge test --match-path test/EquationBench.t.sol -vv              # ρ* (§17.6)
forge test --match-path 'test/fork/Deployed*.t.sol'               # what V1 is, pinned by codehash (§19)
python3 .github/scripts/assurance/projection_distance.py          # 86 / 99 (§15.9)
python3 .github/scripts/assurance/metrics.py                      # every assurance number, with its denominator
bash .github/scripts/shared-quantities.sh                         # the register agrees with the tree
```

Les hachages de l’Exemple détaillé 4 sont reproduits par toute implémentation de keccak-256 appliquée à `abi.encode(currency0, currency1, fee, tickSpacing, hooks)` ; les chiffres du document ont été produits avec un Keccak-f[1600] en Python pur, vérifié contre les vecteurs de test de la chaîne vide et de `"abc"` et contre le sélecteur `extsload(bytes32)` que le Core code en dur.

## Glossaire

**Les opérateurs.**

| Symbole | Nom | En une ligne |
|---|---|---|
| Ω | Répartiteur Octuple | cote chaque jambe — en forme close là où une forme close est exacte, en interrogeant la place là où le bytecode propre de la place est la seule source honnête ; « Octuple » par héritage, six types actifs aujourd’hui |
| 𝒟 | Dérivation Déterministe | calcule les adresses de pool par arithmétique CREATE2 là où le déploiement est reproductible, interroge puis prouve la paire là où il ne l’est pas, dérive et prouve les ids de pool pour le singleton V4 ; `hasCode` écarte ce qui n’a pas de bytecode |
| Φ | Plancher Iron-Law | le plancher de rétention propre au protocole : 96 % de base, se relâchant avec l’impact mesuré et la concentration mesurée, écrêté durement à 80 %, appliqué au devis dans le cadre du dernier hop |
| Ψ | Champ de Vitalité | qualité d’une place calculée à partir du seul Monoslot ; classe et tronque l’ensemble des candidats — il peut masquer une place, jamais la mal valoriser |
| Ξ | projecteur anti-arnaque | retire de la maximisation toute route touchant un hook altérant les deltas ou inadmissible, avant le classement, sans appeler le hook |

**Les termes.** **Régime ancré** — les frais prélevés une seule fois, sur le premier token pont que détient le Router (ou sur la sortie d’une route directe vers un token pont). **Bande de crédibilité** — ±5 % autour de la médiane pondérée par la profondeur des taux marginaux des candidats ; inerte sur un ensemble d’un seul candidat. **Barrière de couverture** — la borne attestée d’une jambe relevée au devis mesuré lorsque l’attestation en couvre moins de la moitié. **Passe Exacte** — l’aperçu exact par pool du Quoter : le propre swap de la place exécuté puis annulé par extraction du revert ; net des frais depuis le 2026-09-03. **Régime d’épuisement** — aucun token pont en entrée d’un hop : chaque hop paie 28 bps sur sa propre entrée mesurée ; immunisé par construction contre un préfixe sans valeur. **Grand livre des frais** — un décompte transitoire des paiements de frais par règlement, refusant zéro et refusant deux sur une route ancrée. **Conception Métrologique** — construire le contrat comme un instrument : réduire la diversité à des coordonnées, mesurer chaque coordonnée mesurable, rendre le reste monotone. **Méta-Équation** — l’agrégateur entier comme une seule maximisation, un produit de sorte qu’un seul facteur défaillant annule la route. **Monoslot** — tout l’état de routage d’un pool dans un seul mot de 256 bits, sans prix à l’intérieur. **Doctrine du Callback Unique** — un seul `fallback` répond au callback de flash accounting de toute place en forme de V3, gardé de trois façons. **Porte à Sens Unique** — la renonciation comme bytecode : le niveau de contrôle meurt, le niveau curateur à croissance seule subsiste. **Preuve de paire** — un pool prouve ses `token0`/`token1` (ou son id re-dérivé) avant que le registre ne le stocke ou que la découverte ne le liste. **Distance de projection** — l’écart entre l’objet que observe un refus et l’objet qui décide. **Registre Auto-Réparateur** — le protocole apprend la liquidité en l’échangeant : seize emplacements par paire, éviction classée par aptitude, décroissance par arithmétique. **Règle du Surplus** — le prélèvement du protocole est de 28 bps d’une seule base mesurée et de rien qui varie avec l’écart entre devis et livraison. **Troisième voie** — tout résultat d’une fixture qui n’est ni un règlement dans les planchers sans rien laisser sur le Router, ni un refus avec un sélecteur à nous.

## Références

1. G. Angeris, A. Evans, T. Chitra, S. Boyd. *Optimal Routing for Constant Function Market Makers.* arXiv:2204.05238, 2022 (ACM EC '22). — Le routage sur les CFMM comme programme convexe ; l’optimum dont l’allocateur proportionnel du §10.4 reste en deçà.
2. T. Diamandis, M. Resnick, T. Chitra, G. Angeris. *An Efficient Algorithm for Optimal Routing Through Constant Function Market Makers.* arXiv:2302.04938, 2023 (Financial Cryptography 2023). — L’algorithme de décomposition duale pour le même problème (§23).
3. H. Adams, N. Zinsmeister, D. Robinson. *Uniswap v2 Core.* 2020. https://uniswap.org/whitepaper.pdf — La référence du produit constant et les 30 bps par défaut du §4.2.
4. H. Adams, N. Zinsmeister, M. Salem, R. Keefer, D. Robinson. *Uniswap v3 Core.* 2021. https://uniswap.org/whitepaper-v3.pdf — La liquidité concentrée, `slot0`, le point fixe Q64.96 (§4.2, §15.6).
5. Uniswap Labs (H. Adams et al.). *Uniswap v4 Core.* 2024. https://uniswap.org/whitepaper-v4.pdf — Le PoolManager singleton, le flash accounting et l’encodage des permissions de hook dans l’adresse (§5, §14.2).
6. M. Egorov. *StableSwap — Efficient Mechanism for Stablecoin Liquidity.* 2019. https://curve.fi/files/stableswap-paper.pdf — L’invariant amplifié de la famille excisée au §4.1.
7. P. Daian, S. Goldfeder, T. Kell, Y. Li, X. Zhao, I. Bentov, L. Breidenbach, A. Juels. *Flash Boys 2.0: Frontrunning in Decentralized Exchanges, Miner Extractable Value, and Consensus Instability.* IEEE S&P 2020. DOI 10.1109/SP40000.2020.00040. — L’adversaire d’ordonnancement que le §14.3 borne sans prétendre l’éliminer.
8. L. Zhou, K. Qin, C. F. Torres, D. V. Le, A. Gervais. *High-Frequency Trading on Decentralized On-Chain Exchanges.* IEEE S&P 2021. DOI 10.1109/SP40001.2021.00027. — Attaques sandwich mesurées et formalisées (§14.3).
9. P. Cousot, R. Cousot. *Abstract Interpretation: A Unified Lattice Model for Static Analysis of Programs by Construction or Approximation of Fixpoints.* POPL 1977. DOI 10.1145/512950.512973. — Le cadre pour la forme close à tick unique comme abstraction saine (§23).
10. M. R. Clarkson, F. B. Schneider. *Hyperproperties.* Journal of Computer Security 18(6), 2010. DOI 10.3233/JCS-2009-0393. — Propriétés d’ensembles de traces ; la classe à laquelle appartiennent les constats de redistribution (§21, §23).
11. T. Y. Chen, S. C. Cheung, S. M. Yiu. *Metamorphic Testing: A New Approach for Generating Next Test Cases.* HKUST-CS98-01, 1998; arXiv:2002.12543. — Le second juge du §15.7.
12. R. A. DeMillo, R. J. Lipton, F. G. Sayward. *Hints on Test Data Selection: Help for the Practicing Programmer.* IEEE Computer 11(4), 1978. DOI 10.1109/C-M.1978.218136. — L’analyse par mutation et l’hypothèse de couplage (§15.2).
13. A. Avizienis. *The N-Version Approach to Fault-Tolerant Software.* IEEE TSE SE-11(12), 1985. DOI 10.1109/TSE.1985.231893. — L’ancêtre du §15.8.
14. D. R. Kuhn, R. N. Kacker, Y. Lei. *Practical Combinatorial Testing.* NIST SP 800-142, 2010. DOI 10.6028/NIST.SP.800-142. — Les tableaux de couverture et la couverture des interactions t-way (§15.4).
15. B. Littlewood, L. Strigini. *Validation of Ultrahigh Dependability for Software-Based Systems.* Communications of the ACM 36(11), 1993. DOI 10.1145/163359.163373. — Pourquoi aucune probabilité de correction n’est énoncée (§15.11).
16. A. Akhunov, M. Salem. *EIP-1153: Transient Storage Opcodes.* https://eips.ethereum.org/EIPS/eip-1153 — Le contexte de jambe, le grand livre des frais et le verrou de réentrance (§11.4, §13.2).
17. V. Buterin. *EIP-170: Contract Code Size Limit.* https://eips.ethereum.org/EIPS/eip-170 — Le mur de 24,576 octets (§16.3).
18. V. Buterin. *EIP-1014: Skinny CREATE2.* https://eips.ethereum.org/EIPS/eip-1014 — L’arithmétique d’adresses de 𝒟 (§7).
19. M. Lundfall et al. *EIP-2612: Permit Extension for EIP-20 Signed Approvals.* https://eips.ethereum.org/EIPS/eip-2612 · Uniswap Labs. *Permit2.* https://github.com/Uniswap/permit2 — La porte Permit2 (§11.1).
20. V. Buterin, S. Wilson, A. Dietrichs, M. Garnett. *EIP-7702: Set Code for EOAs.* https://eips.ethereum.org/EIPS/eip-7702 — Les comptes délégués appellent les mêmes portes (§11.1).
21. *ERC-7201: Namespaced Storage Layout.* https://eips.ethereum.org/EIPS/eip-7201 — L’espace de noms de storage du Hub (§16.1).
22. MariaDB Corporation Ab. *Business Source License 1.1.* https://mariadb.com/bsl11/ — La licence du code.
23. Fable & Mitra. *BlazePhoenix-Dex: an on-chain DEX aggregator with measured routing.* Dépôt, `main` 8949a9d, 2026-09-05. https://github.com/blazephoenixxyz-crypto/Blaze-Phoenix-Dex — La source canonique de chaque affirmation de ce document ; l’annexe C en reproduit les chiffres.
24. S. Müller, N. Moumeni, A. Messaoudi. *Cost of Manipulation in AMM-Based Oracles.* arXiv:2606.03548, 2026 (Financial Cryptography 2026, DeFi workshop). — La borne de la médiane pondérée par la liquidité derrière E25 (§10.3, §23).
25. *Ormer: A Manipulation-resistant and Gas-efficient Blockchain Pricing Oracle for DeFi.* arXiv:2410.07893. — Un oracle on-chain estimant la médiane (§23).
26. *From Means to Medians: Optimal Benchmark Design.* arXiv:2506.22142. — Quels benchmarks sont les plus difficiles à manipuler (§23).
27. *Optimal Routing across Constant Function Market Makers with Gas Fees.* arXiv:2603.02844. — Le routage avec le gas (§23).
28. *Routing Frictions and Executable Liquidity in Fragmented Markets.* arXiv:2609.19013. — Liquidité annoncée contre liquidité exécutable (§23).

## Comment citer

Mitra. *BlazePhoenix Technical Whitepaper: an on-chain liquidity aggregator whose quote and execution are one computation.* Version 2.3.0, 2026-10-14. Zenodo. DOI [10.5281/zenodo.23084091](https://doi.org/10.5281/zenodo.23084091) (la v2.3 déposée). Toutes les versions : [10.5281/zenodo.23084090](https://doi.org/10.5281/zenodo.23084090). DOI de concept de la première série de dépôts (v2.2) : [10.5281/zenodo.22526574](https://doi.org/10.5281/zenodo.22526574). BibTeX et autres formats : https://blazephoenix.xyz/research/whitepaper
