<!-- BlazePhoenix — Whitepaper Técnico, Versión 2.3.0 (legible por máquina, texto completo) · Español -->
> PDF canónico: https://blazephoenix.xyz/docs/BlazePhoenix_Whitepaper.pdf
> DOI: https://doi.org/10.5281/zenodo.23084091 (la versión depositada, v2.3) · todas las versiones: https://doi.org/10.5281/zenodo.23084090 · DOI conceptual de la primera serie de depósitos (v2.2): https://doi.org/10.5281/zenodo.22526574
> Litepaper (complemento en lenguaje sencillo): https://blazephoenix.xyz/docs/BlazePhoenix_Litepaper.pdf
> Informe de Verificación y Pruebas de Swap (septiembre de 2026), las mediciones que cita este documento:
>   https://blazephoenix.xyz/verification-report-2026-09.md · https://blazephoenix.xyz/swap-tests-report-2026-09.md
> Este archivo es el texto completo del whitepaper en markdown, publicado para su
> ingesta por IA y su citación. Licencia del contenido: CC BY 4.0 — cítalo y referéncialo con
> atribución a BlazePhoenix y un enlace. El código fuente Solidity que describe el
> documento está bajo BUSL-1.1 hasta 2030: cita la arquitectura, no lo vuelvas a desplegar ni
> lo clones. Superficies para máquinas: /llms.txt · /facts.json · /knowledge-graph.jsonld
> Enumeraciones citables incluidas: innovaciones I-1–I-18, ecuaciones primitivas E1–E42
> (Apéndice A) e invariantes impuestos INV-1–INV-31 (Apéndice B).

# BlazePhoenix-Dex

*Traducción del original en inglés v2.3.0; prevalece el texto en inglés.*

**La cotización es la operación.**

**El exchange medido: un agregador de liquidez on-chain donde la cotización y la ejecución son el mismo cálculo**

*Whitepaper Técnico — Versión 2.3.0*

| | |
|---|---|
| Versión | 2.3.0 — 2026-10-14 |
| Fecha | 2026-10-14 |
| Autor | Mitra |
| DOI | [10.5281/zenodo.23084091](https://doi.org/10.5281/zenodo.23084091) (v2.3) · todas las versiones [10.5281/zenodo.23084090](https://doi.org/10.5281/zenodo.23084090) · DOI conceptual de la primera serie de depósitos (v2.2) [10.5281/zenodo.22526574](https://doi.org/10.5281/zenodo.22526574) |
| Afiliación | BlazePhoenix — https://blazephoenix.xyz |
| Licencia del texto | CC BY 4.0 — cítalo y referéncialo con atribución y un enlace |
| Licencia del código | BUSL-1.1 (Change Date 2030-06-01) — cita la arquitectura, no lo vuelvas a desplegar |
| Repositorio | `Blaze-Phoenix-Dex`, `main` en `8916545`, 2026-09-24 — https://github.com/blazephoenixxyz-crypto/Blaze-Phoenix-Dex |

> El precio que se te muestra lo calcula el código público que ejecuta tu operación, en el marco que la ejecuta. Nadie — ni siquiera los autores — puede cambiar la comisión ni los suelos: están compilados.

## Diez líneas. Sin marketing.

1. **Ningún servidor elige tu ruta.** La deriva la transacción que la ejecuta, a partir del estado público de los pools.
2. **Ningún oráculo pone precio a tu operación.** Lo ponen los pools — leídos en el marco que la liquida.
3. **La cotización y el suelo son un solo evaluador.** No hay ninguna costura por la que el precio pueda irse a la deriva.
4. **Tu mínimo es obligatorio.** El Router rechaza un swap que no lo tenga.
5. **Las afirmaciones de un pool se falsifican gratis; su salida medida, no.** El enrutamiento se basa en lo segundo.
6. **La comisión, el reparto y los suelos están compilados.** No existe ningún setter. Nadie — ni siquiera los autores — puede moverlos.
7. **Sin proxy, sin ruta de actualización, sin `selfdestruct`, sin saldo en reposo.** No hay nada que robar del Router entre operaciones.
8. **Un pool hostil te cuesta precio y después revierte.** Nunca entrega por debajo de tu suelo.
9. **Cada número de este documento se publica junto a su denominador** — y junto a la forma en que se manipularía.
10. **Si todos los servidores que operamos desaparecieran esta noche, los contratos cotizarían, enrutarían y liquidarían mañana.**

Todo lo que sigue es la prueba, con la línea de código que impone cada afirmación y el límite que la acota.

## Resumen

BlazePhoenix-Dex es un agregador de liquidez on-chain construido sobre una afirmación: una cotización y su ejecución son el mismo cálculo. La ruta se deriva dentro de la transacción que la ejecuta, a partir del estado público de los pools, mediante una biblioteca sin estado que cualquier lector puede volver a ejecutar; el número que obliga en la liquidación lo vuelve a derivar el mismo despachador en el marco de ejecución. No hay servicio de enrutamiento, ni oráculo de precios, ni keeper, ni ruta de actualización. Este documento especifica los cinco contratos — Core, Hub, Solver, Router, Quoter — y los operadores de la Meta-Ecuación: el Despachador Óctuple Ω, la Derivación Determinista 𝒟, el suelo Iron-Law Φ, el Campo de Vitalidad Ψ y el proyector antiestafa Ξ. Detrás de ellos hay una disciplina, el Diseño Metrológico: medir cada coordenada que pueda medirse, y hacer monótono el resto para que una entrada falsa perjudique solo a quien la suministra. Cada garantía se enuncia con su punto de imposición y su límite. El aparato de verificación se publica con sus denominadores: 1,609 tests declarados en 236 archivos, 268 de 268 mutantes seleccionados eliminados, 14 de 15 mutantes detectados entre los dirigidos a 43 invariantes con estado, matrices de cobertura de fuerza 2 (63 filas, 258 pares) y 3 (168 filas, 1,636 tríos), 14 + 5 relaciones metamórficas, tres builds del optimizador en concordancia y un estudio de 240 muestras sobre cómo envejece una cotización. Dieciocho innovaciones con nombre sostienen el diseño, desde la Meta-Ecuación hasta la inmunidad por agotamiento; cada una se cataloga con su matemática antes de que empiece la especificación. La generación en producción en Base, Ethereum, Optimism y Arbitrum es la V1; este documento especifica la V2, ejercitada de extremo a extremo sobre forks de la liquidez real de cada chain a la que da servicio.

## Cómo leer este documento

Cada mecanismo recibe una frase sencilla antes de recibir una precisa, así que el documento se lee a dos profundidades: las frases sencillas y las figuras dan la arquitectura; las frases precisas, las tablas y los apéndices te permiten comprobarla. Cada garantía es una frase — la afirmación, el punto que la impone, el límite que la acota. Seis ejemplos resueltos desarrollan la aritmética a mano: una cotización de producto constante (§4), una división entre dos pools (§10), el suelo Iron-Law en una ruta de dos hops (§11), un identificador de pool de Uniswap V4 derivado de su clave (§5), los dos regímenes de comisión en una misma ruta (§13) y la lectura de una liquidación (§11). Los números se publican junto al denominador contra el que se midieron, y el comando que los reproduce está en el Apéndice C. El repositorio es canónico: donde este texto y el código fuente discrepen, prevalece el código fuente.

## Las innovaciones

BlazePhoenix-Dex no es un agregador con un servidor mejor. Es un agregador **sin servidor** — y llegar ahí exigió un conjunto de mecanismos que no existían antes. Dieciocho de ellos sostienen el diseño. Cada uno se presenta aquí con lo que es, la matemática que lo define, por qué le importa a quien opera y la sección que lo especifica por completo.

### I-1 · Cotización ≡ Ejecución

**Qué.** El número que se te cotiza y el número que obliga tu operación salen de un solo evaluador — el despachador Ω del Core —, ejecutado una vez para planificar y otra vez dentro del marco de ejecución para liquidar. No existe un segundo modelo de precios en ninguna parte del sistema.

**Matemática.** Para cada pata $\ell$, el planificador y el ejecutor evalúan la misma función $\Omega(p_{\ell}, x_{\ell})$ sobre el mismo estado público; el suelo de la ruta se vuelve a derivar a partir de ese valor dentro del marco (E15, E16), y nunca se lee de la ruta.

**Por qué importa.** Toda clase de exploit de agregadores que vive en la brecha entre “lo que dijo el servidor” y “lo que hizo la chain” se queda sin sitio donde vivir. Medido: las cotizaciones sin deriva se liquidan exactamente según su predicción **64 veces de 64**, en mocks y en Base en producción. (§3, §11, §12)

### I-2 · El solver on-chain, gratuito de llamar

**Qué.** `Solver.findBestRoutePlan(tokenIn, tokenOut, amountIn)` es una función `view`. Cualquiera obtiene una mejor ruta que no requiere confianza con un `eth_call` gratuito — sin clave de API, sin relayer, sin firma — y `swapBestExactIn` resuelve y liquida de forma atómica en una sola transacción.

**Matemática.** Todo el Solver es la maximización con restricciones de la Meta-Ecuación (E1) sobre el espacio de rutas $\mathcal{R}_{\mathcal{D}}$, ejecutada como un pipeline determinista: derivación → clasificación → sondeo → filtrado → asignación → compuerta → suelo.

**Por qué importa.** La inteligencia de enrutamiento de la industria corre en servidores cerrados sin un recuento de líneas auditable. La nuestra son 29,183 bytes de bytecode público que cualquiera puede volver a ejecutar para obtener la misma respuesta. (§2, §10, §11.1)

### I-3 · La Meta-Ecuación

**Qué.** Todo el agregador, escrito como una sola expresión.

**Matemática.**
$$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)$$

**Por qué importa.** La forma de producto es una propiedad de seguridad: un solo factor que falle — una pata que cotiza cero, un pool sin reputación, una ruta por debajo de su suelo, un hook hostil — lleva la puntuación de la ruta exactamente a cero. Sin penalizaciones suaves, sin pesos ajustables, sin que se cuele ninguna ruta que casi cumple. (§3)

### I-4 · El Despachador Óctuple Ω

**Qué.** Seis tipos de venue — Uniswap V2, V3, V4, Algebra, Solidly estable y Solidly volátil — se reducen a cuatro familias matemáticas detrás de una sola tabla de atributos y un solo despachador.

**Matemática.** Producto constante $y = \lfloor x(\mathrm{BPS}-f)\,r_{\mathrm{out}} / (r_{\mathrm{in}}\mathrm{BPS} + x(\mathrm{BPS}-f)) \rfloor$ (E2); liquidez concentrada en Q64.96, $\sqrt{P}' = L\sqrt{P}/(L + x_{f}\sqrt{P}/Q_{96})$, $y = L(\sqrt{P}-\sqrt{P}')/Q_{96}$ (E3); todo producto peligroso se factoriza mediante `mulDiv` de 512 bits.

**Por qué importa.** La fragmentación resulta ser un problema de lenguaje, no de matemática. Una fórmula auditada por familia pone precio a cada venue en cada chain; un nuevo fork de una familia conocida es una fila de tabla, no código nuevo. (§4)

### I-5 · Derivación Determinista 𝒟

**Qué.** Los pools se **calculan**, no se piden. El Hub deriva la dirección de cada candidato a partir de la factory, el par y el init-code hash, y solo admite direcciones que contienen bytecode de runtime.

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

**Por qué importa.** La respuesta de una factory puede falsificarse; la aritmética no. El descubrimiento no cuesta ninguna llamada externa por candidato y funciona de forma idéntica en cada chain EVM, con los pool managers y las factories como configuración de runtime, nunca codificados de forma fija. (§7)

### I-6 · Uniswap V4, enteramente dentro del marco

**Qué.** Los pools V4 no tienen dirección y no responden a ninguna llamada. BlazePhoenix deriva sus ids, **prueba** que cada uno está vivo contra el storage del PoolManager, lee el precio y la liquidez mediante `extsload`, recorre el libro de ticks a lo largo de hasta dieciséis tramos, liquida a través del singleton — ETH nativo incluido — y filtra los hooks solo con aritmética.

**Matemática.** Base de storage $\mathrm{slot} = \mathrm{keccak256}(\mathrm{abi.encode}(\mathrm{poolId}, 6))$, con $\sqrt{P}$ en los bits [0,160) y la comisión LP en [208,232) (E4); la compuerta de hooks $(\mathrm{uint160}(\mathrm{hook})\,\&\,\mathtt{0x3FFF})\,\&\,((1\ll3)\mid(1\ll2)) \ne 0 \Rightarrow$ rechazar, sin ninguna llamada (E18); la compuerta de comisión dinámica E19.

**Por qué importa.** Derivar, probar, poner precio, filtrar y liquidar V4 sin confiar en una sola respuesta del venue — la integración V4 más profunda que puede tener un agregador, y toda ella verificable on-chain. (§5)

### I-7 · El solver Solidly estable

**Qué.** Un solver exacto y que falla cerrado para la curva estable cuártica que usan los venues de la clase de Aerodrome y Velodrome.

**Matemática.** Invariante $k(x,y) = x^{3}y + xy^{3}$ (E5), resuelto mediante una iteración de Newton acotada, con un ajuste de una unidad en contra del tomador y un tope de 64 pasos (E6), tras normalizar los decimales a 18 posiciones (E7).

**Por qué importa.** El flujo de stablecoins y de liquid staking se concentra en estas curvas, donde entregan casi toda la entrada como salida. El solver las cotiza con exactitud — y, donde no puede probarse una cota, devuelve cero en lugar de una conjetura. (§6)

### I-8 · El Monoslot

**Qué.** Todo el estado de enrutamiento de un pool — actividad, clase de profundidad, comisión, tipo, flags, recencia — empaquetado en **una sola palabra de 256 bits**.

**Matemática.** $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).

**Por qué importa.** Un `SLOAD` clasifica un pool; un `SSTORE` sobre un `SLOAD` lo mantiene al día. Y la palabra no contiene ningún precio, así que la memoria del registro es estructuralmente incapaz de poner un precio erróneo a una operación. (§8)

### I-9 · El Registro Autorreparable y el Campo de Vitalidad Ψ

**Qué.** El protocolo aprende la liquidez operando con ella. Dieciséis slots por par, una puntuación de aptitud ganada con swaps reales, decaimiento por aritmética y una **prueba del par**: un pool prueba qué tokens intercambia antes de ser almacenado.

**Matemática.** $\Psi = V \cdot 2^{b} \cdot (1 + 0.25\,\mathbf{1}_{\mathrm{bridge}}) \cdot (1 + 0.05\,\mathbf{1}_{\mathrm{conc}})$ (E10); la actividad se reduce a la mitad cada 24,576 segundos de silencio, $V = \mathrm{swapCount} \gg \lfloor (t - t_{\mathrm{last}})/24{,}576 \rfloor$ (E11); clase de profundidad $b = \min(15, \lfloor \log_{10}(d/10^{15}) \rfloor)$ (E12); un recién llegado desplaza al más débil solo si supera un margen, $2^{b(d_{\mathrm{new}})} > \Psi_{\mathrm{worst}} + \lfloor \Psi_{\mathrm{worst}}/4 \rfloor$ (E13).

**Por qué importa.** Sin keeper, sin cron, sin curador que decida qué pools importan: el registro se repara solo con el flujo que los usuarios ya estaban enviando. (§9)

### I-10 · La División Anclada en Capital

**Qué.** La calidad del precio y la profundidad del pool se miden por separado. Una sonda diminuta mide la tasa marginal de cada pool; una **banda de credibilidad** anclada en el capital descarta los valores atípicos; los supervivientes se reparten la orden en proporción a la profundidad medida.

**Matemática.** Centro = la mediana ponderada por profundidad — el primer $r_{i}$ en el que $\sum_{j \le i} d_{j} \ge \lceil \sum d / 2 \rceil$ — con la banda $[0.95, 1.05]\times$ centro (E25); la división sobrevive solo si $\mathrm{out}_{\mathrm{split}} \ge \mathrm{out}_{\mathrm{single}}\cdot(1 + 25/10^{6})$ (E14).

**Por qué importa.** Para mover el ancla, un atacante tiene que depositar más que la mitad de la liquidez real del par — capital en riesgo, no una donación gratuita. La garantía permanente: **una división nunca paga menos que el mejor pool único.** Medida contra una rejilla exhaustiva en Base, la ruta del Solver queda a no más de 0.13 bps del óptimo. (§10)

### I-11 · El suelo Iron-Law Φ

**Qué.** Una salida mínima que se vuelve a derivar **on-chain, en la ejecución**, a partir del impacto de precio medido y de la concentración de la ruta — con independencia de cualquier cosa que haya aportado el llamador.

**Matemática.** $\mathtt{floorBps} = \max(9{,}600 - \mathtt{legShv} - \min(\delta, 10^{4}) - \sigma_{\ln}/10^{14},\ 8{,}000)$ (E15), con la merma por concentración $\mathtt{legShv} = \sum_{\mathrm{hops}} \lfloor 200\,((\sum a)^{2} - \sum a^{2})/\sum a^{2} \rfloor$ (E23); la entrega debe superar $\mathtt{effMin} = \max(\mathtt{userMinOut},\ \mathtt{route.singleOutFloor},\ \lceil \mathtt{finalHopQuote}\cdot\mathtt{floorBps}/10^{4}\rceil)$ (E16).

**Por qué importa.** Los datos del llamador solo pueden endurecer la protección, nunca relajarla. En una operación del 1 % de la profundidad, el suelo limita un sandwich a cerca del 2.7 % de la operación y **rechaza** más allá — el viaje de ida y vuelta del atacante se vuelve negativo pasado ese límite. (§11.5, §14.3)

### I-12 · Medición en cada costura

**Qué.** El Router nunca da por hecho que una transferencia ocurrió; la mide. Cada extracción, la llegada a cada hop y cada entrega son una diferencia de saldo real, desde la primera instrucción del swap hasta la cartera del destinatario.

**Matemática.** Cada hop se reescala según su llegada medida; los tokens con comisión por transferencia en patas de producto constante se cotizan sobre la diferencia que el pool realmente ve, la cantidad exacta que comprobará el propio invariante del pool.

**Por qué importa.** Los tokens que aplican impuestos, hacen rebase o informan mal no pueden empujar al Router a prometer lo que no tiene — y el Router no guarda **nada en reposo** entre transacciones (INV-1). (§11.2, §11.4)

### I-13 · La Doctrina del Callback Único

**Qué.** Un único `fallback` responde al callback de contabilidad flash de todos los venues con forma de V3 — Uniswap V3, Algebra, Pancake V3, Sushi V3, los forks concentrados de Solidly —, con el contexto de la pata en curso guardado en transient storage EIP-1153.

**Matemática.** El token adeudado se lee de los signos de los dos deltas; el pago está acotado de tres formas — solo al pool comprometido, solo mientras hay una pata en curso, nunca más que el presupuesto registrado de la pata (INV-10).

**Por qué importa.** Un punto de entrada pequeño y con triple guarda reemplaza un zoológico de callbacks por fork — menos bytecode, menos superficie, una sola cosa que verificar. (§11.4)

### I-14 · La Regla del Excedente y la inmunidad por agotamiento

**Qué.** Una comisión del 0.28 %, compilada, cobrada una sola vez sobre la primera moneda puente que tiene el Router — y **ningún término de comisión que crezca con la diferencia entre la cotización y la entrega.**

**Matemática.** $\mathrm{fee} = \lceil 28 \cdot \mathrm{base} / 10^{4} \rceil$, reparto $t_{1} = \lfloor 3{,}000\cdot\mathrm{fee}/10^{4} \rfloor$, $t_{2} = \mathrm{fee} - t_{1}$ (E17); el predicado de régimen $\mathrm{feeHop} = \min\{h : \mathrm{isBridge}(\mathrm{hops}[h].\mathrm{tokenIn})\}$ (E26). Una ruta que evita todas las monedas puente paga en cada hop, así que ningún prefijo de dust puede llevarse la comisión.

**Por qué importa.** Todo lo que los pools entregan por encima de la cotización te llega íntegro. La comisión no puede subirse en contra de quien opera ni bajarse para un integrador favorecido — nadie tiene esa llave. (§13)

### I-15 · El tipo de cambio byte–gas ρ*

**Qué.** Las cuatro puertas del Router son un modelo de coste afín convertido en interfaz: resolver on-chain, o llevar en el calldata una ruta ya resuelta — lo que sea más barato en esa chain.

**Matemática.** Llevar la ruta en el calldata si y solo si $p_{\mathrm{byte}} < \rho^{*}$, con $\rho^{*}$ medido en 66.9–105.5 gas por byte según la forma de la ruta (E22).

**Por qué importa.** Cada chain tiene un precio para los bytes y un precio para el cómputo. BlazePhoenix mide el tipo de cambio entre ellos y le da a quien opera la puerta más barata. (§17.6)

### I-16 · El proyector antiestafa Ξ

**Qué.** Todo pool cuyo hook V4 declare permisos que alteran los deltas se elimina del conjunto de candidatos **antes de la clasificación**, sin que el hook llegue a ser llamado.

**Matemática.** $\Xi(R) \in \{0,1\}$ en la Meta-Ecuación, evaluado mediante la prueba de bits E18 sobre la dirección del hook.

**Por qué importa.** Un hook hostil nunca tiene la ocasión de mentir: nunca se le pregunta. Tres capas independientes — los bits de la dirección, una lista de permitidos con codehash fijado y Ξ — se interponen entre quien opera y un venue malicioso. (§5.5, §14.2)

### I-17 · La Puerta de un Solo Sentido

**Qué.** La autoridad administrativa termina de forma irreversible, en el bytecode. `renounceControl` escribe un flag que ningún camino de código puede desactivar; después, solo queda un nivel de curador que únicamente puede crecer.

**Por qué importa.** “No lo tocaremos” se convierte en “no podemos tocarlo”. La comisión, el reparto y los suelos nunca fueron configurables, para empezar (INV-4); una vez cerrada la puerta, tampoco lo es nada más. (§14.7)

### I-18 · El Diseño Metrológico y el denominador publicado

**Qué.** La disciplina que hay detrás de todo lo anterior — medir cada coordenada que pueda medirse, hacer monótono el resto para que una entrada falsa perjudique solo a quien la suministra — y un aparato de verificación que publica **cada número junto a su denominador**.

**Matemática.** 1,609 tests declarados en 236 archivos; 268 de 268 mutantes seleccionados eliminados, cada uno emparejado con el test concreto que debe fallar; matrices de cobertura de fuerza 2 (258 de 258 pares) y 3 (1,636 de 1,636 tríos) sobre los factores de régimen; 14 + 5 relaciones metamórficas; tres builds del optimizador que coinciden bit a bit; pruebas simbólicas en Halmos y en el Certora Prover.

**Por qué importa.** La mayoría de los proyectos publica un recuento de tests. BlazePhoenix publica cómo podría manipularse su evidencia y contra qué mide — y lo recalcula en cada commit. (§2.3, §15)

### El marcador

| Propiedad medida | Resultado |
|---|---|
| Cotizaciones sin deriva que se liquidan exactamente según su predicción (estudio de 240 muestras) | **64 / 64** |
| Patologías de venue × puertas que liquidan o rechazan con un selector nuestro — nunca de una tercera forma | **20 / 20** |
| Mutantes seleccionados eliminados | **268 / 268** |
| Acciones de control cuyo rechazo se ejercita por su código exacto | **31 / 31** |
| Cantidades calculadas en dos lugares que un test con nombre vincula entre sí | **7 / 7** |
| Pares de valores de factores de régimen ejercitados por construcción | **258 / 258** |
| Tríos de valores de factores de régimen ejercitados por construcción | **1,636 / 1,636** |
| Builds del optimizador de la matemática de cotización en concordancia (runs 1 / 300 / 20,000) | **3 / 3** |
| Vista previa → entrega en cuatro chains | **0–1 bps** |
| Ruta del Solver frente a una rejilla exhaustiva en Base | **a no más de 0.13 bps** |
| Mayor sandwich que el suelo admite en una operación del 1 % de la profundidad, antes de rechazar | **≈ 2.7 %** |
| Investigadores de seguridad externos acreditados · Hallazgos críticos | **25 · 0** |


## Índice

- Las innovaciones (I-1–I-18) y el marcador
- §1 El problema: una cotización es una promesa que calculó otro
- §2 La respuesta y la disciplina: diseño guiado por invariantes y Diseño Metrológico
- §3 La Meta-Ecuación y el presupuesto de dependencias
- §4 El Despachador Óctuple Ω — tipos, familias, formas cerradas
- §5 Uniswap V4 de extremo a extremo — singleton, prueba de existencia, compuerta de comisión, hooks
- §6 El solver Solidly
- §7 Derivación Determinista 𝒟
- §8 El Monoslot
- §9 El Registro Autorreparable y el Campo de Vitalidad Ψ
- §10 El enrutamiento y el Asignador de División
- §11 La capa de ejecución y el suelo Iron-Law Φ
- §12 Superficies de cotización, y cómo envejece una cotización
- §13 Política de comisiones: los dos regímenes y la Regla del Excedente
- §14 Modelo de seguridad
- §15 El aparato de verificación, medido
- §16 Arquitectura y tamaños
- §17 Rendimiento medido
- §18 El DEX, reducido a lo esencial
- §19 Estado del despliegue
- §20 Token — BZPX
- §21 Programa de investigación e historial de seguridad
- §22 En qué debes confiar, y en qué no necesitas hacerlo
- §23 Trabajo relacionado
- §24 Conclusión
- §25 Staking V4: diseño
- Apéndice A · Las ecuaciones primitivas (E1–E42)
- Apéndice B · Los invariantes (INV-1–INV-31)
- Apéndice C · Comandos de reproducción
- Glosario
- Referencias

---

## §1 El problema: una cotización es una promesa que calculó otro

Imagina que vendes un automóvil en una ciudad con veinte concesionarios. Un buen intermediario visita los veinte, encuentra el mejor precio y quizá reparte la venta entre dos de ellos. La mayoría de los “intermediarios” on-chain consultan los precios en su propia base de datos — posiblemente desactualizada — y te muestran un resultado. No puedes verificar que miraron los veinte, ni que te mostraron el mejor. Solo puedes verificar que, hasta ahora, han sido honestos.

Todos los agregadores cuya documentación leímos — 1inch, CoW Swap, Velora (ParaSwap), KyberSwap, OpenOcean, Jupiter y UniswapX, según su documentación a fecha de 2026-09-07 — funcionan así: un servicio calcula la ruta off-chain y el contrato ejecuta la ruta que se le entrega. El contrato no elige; comprueba. Es ingeniería sólida — es la razón por la que esos sistemas son rápidos y baratos — y tiene una consecuencia que rara vez se dice con claridad: **lo más fuerte que puede afirmar un sistema así es que su operador es honesto.**

La brecha no suele ser fraude. Es deriva, con tres orígenes corrientes. La cotización se calculó sobre reservas que se movieron antes de que se incluyera la transacción. La búsqueda fue solo tan exhaustiva como lo permitía el presupuesto del operador. Y cada corrección entre la cotización y la ejecución — un margen de slippage, una comisión por el camino — la aplicó un código que el usuario no leyó, sobre un número que el usuario no puede reproducir. Nada de eso requiere mala intención. Todo ello es invisible.

### §1.1 La fragmentación es real, y menos profunda de lo que parece

Un trader que quiere el mejor precio se enfrenta a una paradoja. La liquidez existe, repartida entre decenas de exchanges descentralizados, cada uno con sus propios contratos de pool, su propia curva de precios, su propio esquema de direcciones — pero ningún venue la contiene toda. El precio de la mejor ejecución es el costo de hablar a la vez el idioma de cada exchange, y de confiar en cada traductor entre el precio mostrado y la operación que se ejecuta.

La respuesta establecida integra los venues de uno en uno: un conector para Uniswap, otro para los forks de Solidly, otro para cada variante de liquidez concentrada, cada uno con cotización a medida, búsqueda de direcciones a medida, ejecución a medida. La superficie de integración crece linealmente con el número de venues, y cada conector es un lugar nuevo para un defecto de precios o para una discrepancia silenciosa entre el número cotizado y el número ejecutado.

La fragmentación es menos profunda de lo que aparenta. Bajo la diversidad de la superficie, los creadores de mercado automatizados que contienen la mayor parte de la liquidez on-chain se rigen por un pequeño conjunto de invariantes. Los pools de producto constante obedecen $x \cdot y = k$. Los pools de liquidez concentrada siguen la raíz cuadrada del precio en punto fijo sobre una liquidez activa constante por tramos. La familia Solidly obedece una cuártica, $x^{3}y + xy^{3} = k$, en sus pares estables y el producto constante en los volátiles. Hay un puñado de formas, no sesenta.

Si las formas son pocas, el costo de la agregación no tiene por qué crecer con el número de venues; solo necesita crecer con el número de formas. Un protocolo que implementa cada forma una sola vez, correctamente, y despacha cada venue a la forma que le corresponde reduce la superficie de integración de lineal a constante. Añadir un venue deja de ser ingeniería y pasa a ser configuración: se asignan un tipo y un modo de derivación de direcciones, y la matemática existente le pone precio, el router existente lo liquida, el registro existente lo aprende.

**La afirmación central del diseño:** cada venue por el que enruta BlazePhoenix es una parametrización de una de las funciones de salida de una pequeña familia — cuatro familias operativas en una taxonomía de seis tipos (§4) — contenidas una sola vez, en una única biblioteca sin estado, y nunca redefinidas. Añadir un venue significa asignar un tipo y un modo de derivación, no escribir código de precios nuevo.

### §1.2 La vía de escape off-chain

La arquitectura de agregador predominante cotiza rutas en una flota de servidores, organiza una subasta entre solvers off-chain, indexa la liquidez de los pools con bots keeper y envía el resultado a un contrato de liquidación on-chain delgado — a menudo detrás de un proxy actualizable cuya clave de administración puede reemplazar la lógica por completo. Un servicio web produce el precio, una base de datos contiene el grafo de liquidez, un operador privilegiado puede cambiar las reglas, y la chain se convierte en un venue de liquidación para decisiones tomadas en otro lugar, por un software que el usuario no puede ver ni puede verificar.

Ese esquema reintroduce exactamente la confianza que la tecnología debía eliminar. Una cotización off-chain puede diferir de la ejecución on-chain, y el usuario no puede demostrar que no debería haber diferido. Un keeper puede apagarse y el router queda ciego. El administrador de un proxy puede, en una sola transacción, convertir un contrato honesto en uno hostil.

| Aspecto | Agregador resuelto off-chain | BlazePhoenix-Dex |
|---|---|---|
| Cotización | Servidores off-chain o una subasta de solvers | Un solo despachador on-chain, compartido por la vista previa y por la ejecución dentro del marco |
| Descubrimiento de liquidez | Bots keeper indexan y envían listas | Registro Autorreparable — liquidez aprendida al operarla — más descubrimiento determinista |
| Selección de ruta | Optimizador off-chain propietario | Solver on-chain, reproducible por cualquiera a partir del estado público |
| Superficie de actualización | Proxy controlado por un administrador | Core sin estado; sin proxy, sin ruta de actualización, sin `selfdestruct` |
| Poder privilegiado sobre los fondos | A menudo presente | El Router no guarda nada en reposo; ninguna función mueve el principal del usuario |
| Consistencia entre chains | Lógica de backend por chain | Un bytecode por chain |
| Supuesto de confianza | La honestidad del operador | El mínimo propio del usuario, que el contrato se niega a aceptar como cero |

BlazePhoenix rechaza las dos mitades del trato. Rechaza la superficie de integración lineal mostrando que la superficie no es lineal, y rechaza la vía de escape off-chain manteniendo toda la decisión — descubrimiento, cotización, puntuación, enrutamiento y liquidación — dentro de contratos que cualquiera puede leer y que producen la misma respuesta en cada chain. El costo es real: cotizar un pool concentrado on-chain es más caro que leerlo de una base de datos. Lo que el costo compra es la eliminación de una clase de fallo — la divergencia silenciosa e indemostrable entre lo que prometió un servidor y lo que entregó la chain. Cada decisión de diseño que sigue es un caso de ese compromiso.

## §2 La respuesta y la disciplina

**Lo que elige es lo que opera.**

BlazePhoenix-Dex tiene un punto de entrada que recibe seis valores — los dos tokens, la cantidad, tu mínimo, un destinatario, un plazo — y calcula la ruta dentro de la transacción que la ejecuta, a partir de reservas de pools que son estado público: qué venues, cuánto a cada uno, en qué orden. Como la ruta se derivó del estado público mediante código público, cualquiera puede volver a derivarla. Como el número que obliga en la liquidación lo vuelve a derivar el mismo despachador, en el mismo marco, a partir del mismo estado en vivo, la cotización y la ejecución no son dos artefactos que conciliar; son un solo cálculo, y lo que separa una vista previa de una ejecución es el tiempo, no un segundo modelo.

Otros tres puntos de entrada aceptan una ruta precalculada desde la calldata, para llamadores que quieren el camino más barato y están dispuestos a aportar su propia respuesta; la misma maquinaria de medición, suelo y liquidación se aplica a las cuatro puertas (§11). No afirmamos que el enfoque off-chain sea erróneo, ni afirmamos ser más baratos. Afirmamos una clase distinta de garantía — auditable en lugar de prometida — para el usuario que la quiere y pagará gas por ella.

Cuatro decisiones de este diseño son, hasta donde podemos establecer, inusuales en el sector, y cada una se enuncia con el límite que la mantiene honesta.

- **Las direcciones se derivan, no se obtienen.** Allí donde el despliegue de una familia de venues es reproducible, la dirección de un pool se calcula con aritmética CREATE2 — un teorema que cualquiera puede volver a comprobar a partir de tres entradas públicas — en lugar de leerse del storage de una factory; donde la aritmética no es fiable, el protocolo pregunta, y un pool consultado prueba su propio par antes de ser listado (§7).
- **El estado de enrutamiento de un pool es una sola palabra.** El Monoslot empaqueta todo lo necesario para clasificar un venue en un único slot de 256 bits y, deliberadamente, no contiene ningún precio, de modo que la memoria del registro puede sesgar qué pools se consultan, pero es estructuralmente incapaz de ponerle precio a una operación (§8–§9).
- **El suelo se vuelve a derivar de la operación que realmente ocurrió.** El Router mide el impacto realizado de cada pata y la concentración realizada de cada hop dentro del marco de ejecución y recalcula su suelo a partir de esas mediciones, de modo que los campos aportados por el llamador pueden endurecer la protección y nunca relajarla (§11).
- **El descubrimiento es ciego al desplegador donde los venues no tienen direcciones.** Para el singleton de Uniswap V4, los identificadores de pool se derivan de parámetros públicos y después se prueba que están activos contra el propio storage del manager — una prueba que nadie más puede escribir (§5).

Ninguna de ellas elimina el residuo que arrastra todo agregador — el estado puede moverse entre la firma y la inclusión —, y por eso el mínimo que fijas tú mismo es obligatorio, y definitivo: cada puerta rechaza un mínimo cero con `RouterE(10)` antes de que se mueva un token.

### §2.1 Diseño guiado por invariantes

Detrás de la respuesta hay un método. Lo llamamos diseño guiado por invariantes: el protocolo no se especifica como una secuencia de operaciones, sino como un conjunto de propiedades que deben cumplirse en todo estado alcanzable, con el código organizado para que esas propiedades sean estructurales — impuestas por la forma del código, no por la vigilancia del programador.

La mayor parte del software se escribe de forma imperativa, y la corrección es una esperanza emergente. Para el software corriente eso es aceptable, porque los fallos son recuperables. En una blockchain pública nada de eso se cumple: el código es inmutable una vez desplegado; custodia valor directamente; se ejecuta en un entorno adversario donde se rastrea cada resquicio; y un solo desliz aritmético es una pérdida irreversible, no un cuelgue.

Un invariante es una propiedad que es verdadera independientemente de cómo llegó el sistema a su estado. *El Router no guarda fondos entre transacciones.* *Un par del registro contiene como máximo dieciséis pools, y el desalojo conserva los dieciséis más aptos.* *No existe una liquidación que no haya pagado la comisión del protocolo.* Tres principios ponen tales enunciados en el centro:

| Principio | Enunciado | Dónde aparece |
|---|---|---|
| Calcular, no confiar | Donde un valor puede derivarse o probarse, nunca lo obtengas ni lo supongas. | 𝒟 calcula las direcciones de los pools; el Router vuelve a derivar dentro del marco la base de la comisión y el suelo a partir de saldos medidos. |
| Una sola fuente de verdad | Cada primitiva matemática se define una vez y se importa; nunca se reformula. | El Core contiene toda la matemática de precios, derivación, suelo y puntuación; Hub, Solver, Router y Quoter la importan y ninguno redefine una primitiva. Un registro de cantidades compartidas (§15) califica cada cantidad que tiene dos productores. |
| Fallar cerrado | Cuando una propiedad no puede garantizarse, detente en lugar de seguir a partir de una conjetura. | El Solver no devuelve ninguna ruta en lugar de una fabricada (`SolverE(5)`); el solver estable devuelve cero en lugar de una cotización saturada (§6); la guarda de coherencia del registro revierte un venue estructuralmente imposible antes de que se escriba ningún estado (§9). |

Un cuarto principio rige la custodia: **ningún camino privilegiado hacia los fondos.** El Solver y el Quoter son contratos de solo lectura; el Router es el único contrato que mueve dinero, lo mueve de forma atómica bajo la propia cota del usuario, y cada poder administrativo que podría redirigirlo puede abandonarse de forma permanente a través de la Puerta de un Solo Sentido (§14).

### §2.2 Por qué esta disciplina encaja en este medio

Cada propiedad del entorno que hace peligroso el código imperativo es una propiedad que el código guiado por invariantes convierte en ventaja. La inmutabilidad premia la demostrabilidad: el código desplegado no puede parchearse, así que el valor de una prueba previa es proporcionalmente alto. La componibilidad premia una única fuente de verdad: una función de precios puede invocarse dentro de un vault que sus autores nunca anticiparon, y con un solo Core cada llamador obtiene la misma respuesta. El carácter adversario premia fallar cerrado: a un atacante le basta un solo camino que el autor no consideró, y un sistema que se detiene cuando sus supuestos se rompen le niega la improvisación. El valor en juego premia la legibilidad: un protocolo expresado como cinco operadores con nombre sobre un único núcleo matemático puede revisarse línea por línea por personas que no lo escribieron.

La disciplina no es gratuita, y el compromiso es deliberado: demostrar una cota de overflow lleva más tiempo que suponer que el overflow no ocurre; pasar cada producto por una multiplicación-división de precisión completa cuesta un gas que una multiplicación ingenua no costaría; derivar una dirección cuesta una ingeniería de la que obtenerla prescinde. Es más costo antes del despliegue a cambio de eliminar los fallos que no pueden recuperarse después de él.

### §2.3 Diseño Metrológico: un morfismo, coordenadas medidas, entradas monótonas

El diseño guiado por invariantes dice qué debe cumplirse siempre. Otras tres reglas dicen qué *forma* tiene el código que lo sostiene, y juntas constituyen un método que no hemos encontrado con nombre en ninguna otra parte del sector, así que este documento le da nombre: **Diseño Metrológico** — construir un contrato como un *instrumento*. Su tesis en una línea: *el código que cree es superficie de ataque; el código que mide es defensa.*

**Un morfismo, evaluado en un punto.** El agregador no contiene una integración por venue; es la evaluación de un único morfismo de precios parametrizado en un punto del espacio (forma del venue, chain, modelo de comisión). Expresados como ramas, media docena de tipos de venue en uso con sus propias convenciones de comisión se multiplican en cientos de combinaciones por pares — bytecode multiplicativo, superficie de auditoría multiplicativa, deriva multiplicativa. Expresada como morfismo, la misma cobertura es un puñado de *formas de estado*, un solo Core sin estado, convenciones de comisión llevadas en sus unidades nativas y tablas de registro — y el bytecode crece de forma *aditiva*: un venue nuevo es una fila, no una rama. La regla que protege el colapso es `KIND → SHAPE`: una familia de venues es *datos* — un tipo asignado a una forma existente y un modo de derivación —, nunca un nuevo camino de código que una entrada adversaria pueda seleccionar. El Core enuncia las formas como una tabla de bits (`THETA_ATTR`, §4), de modo que “¿este tipo lee reservas?” es un desplazamiento y un AND, y no hay ninguna rama por defecto que alguien pueda olvidar. El colapso tiene un freno: no existe una única fórmula universal. Dos formas cerradas se evalúan con exactitud — producto constante y liquidez concentrada — y para cada curva en la que replicarla sería un riesgo, se pregunta al propio bytecode del venue (§6). El morfismo colapsa la *pertenencia*, nunca la *verificación*.

**Las coordenadas se miden, nunca se creen.** El contrato nunca actúa sobre un valor modelado, nominal o aportado por el llamador cuando existe una medición on-chain — y ningún dato que cualquiera pueda aportar sin permiso elige jamás qué código se ejecuta. Los datos que aporta una parte no confiable — una ruta, una comisión declarada, una profundidad afirmada, un id de pool — son *coordenadas*: lugares a los que apuntar el instrumento, que luego el contrato lee por sí mismo, fallando cerrado. Cada defensa portante de este documento es un ejemplo: el saldo medido tras el cobro en lugar de la cantidad nominal (§11), la base de la comisión tomada de una diferencia de saldo medida en lugar de la calldata (§13), la banda de credibilidad anclada en una mediana ponderada por profundidad de las profundidades medidas en lugar de en un saldo bruto que una donación puede inflar (§10), el recorte de capacidad aplicado contra las tenencias medidas (§10), la compuerta de comisión de V4 que lee el estado en vivo en lugar de confiar en un centinela (§5), el descubrimiento por prueba de existencia que lee el propio storage del singleton (§5), y un pool consultado que prueba su propio `token0`/`token1` antes de que el descubrimiento lo liste (§7). El repositorio publica la clasificación de cada campo que el llamador puede escribir según esta regla: 23 campos, 14 confirmados contra una observación, 5 de orientación, 4 declarados con su motivo (§15).

**Donde termina la medición, empieza la monotonía.** Algunos números no pueden medirse ni volver a derivarse — el suelo de un llamador, la escala declarada de un llamador, el mínimo propio del llamador. Para ellos el diseño acepta el número y da forma a la función de modo que mentir solo pueda perjudicar al mentiroso: el suelo atestado de la ruta solo puede *elevar* el suelo impuesto (§11); la cotización atestada de una pata se eleva hasta la cotización medida cuando se queda por debajo de ella (§11); el escalado declarado está acotado por la llegada medida, así que exagerarlo no puede enrutar fondos que nunca existieron; y el único número que el protocolo no puede elegir — tu mínimo — es obligatorio. Una entrada monótona no necesita confianza, así que puede calcularse off-chain, por cualquiera, gratis, y llevarse en la calldata — así es como este protocolo traslada trabajo off-chain *sin trasladar la confianza off-chain*, y por eso el Router puede ofrecer una puerta que resuelve y tres puertas de calldata con las mismas garantías.

El Diseño Metrológico rige también la ingeniería en torno al artefacto, porque un instrumento solo es tan bueno como su calibración. Los márgenes de seguridad se fijan a partir de riesgos medidos: la compuerta de división está en 25 ppm porque el punto de equilibrio por pata se midió en 3 ppm (§10); se retiró un descuento de planificación que pesaba 27× más que su riesgo medido (§10). Las comparaciones externas llevan las barras de error de su método o no se publican (§17). Y el propio aparato de medición está medido: cada instrumento de aseguramiento se calibró contra un defecto ya conocido por otra vía antes de publicar su número (§15). Esa es toda la teoría; el resto de este documento es su práctica.

## §3 La Meta-Ecuación y el presupuesto de dependencias

Todo el agregador puede leerse como la maximización de una sola expresión. Dados un token de entrada $t_{\mathrm{in}}$, un token de salida $t_{\mathrm{out}}$ y una cantidad $x$, el Solver busca entre las rutas admisibles la que maximiza el valor neto recibido, sujeto a un suelo de factibilidad y a una compuerta antiestafa:

$$
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}}
$$

Aquí $\mathcal{R}_{\mathcal{D}}$ es el espacio de rutas sobre los pools localizados por la Derivación Determinista 𝒟 y por el registro; $\Omega(p_{\ell}, x_{\ell})$ es la salida del despachador para la pata $\ell$ que empuja $x_{\ell}$ a través del pool $p_{\ell}$; $f_{\mathrm{proto}}$ es la comisión del protocolo; $\hat{\Psi}(p_{\ell})$ es la vitalidad normalizada de la pata; $\Phi(R)$ es el suelo Iron-Law para el impacto y la concentración medidos de la ruta; y $\Xi(R) \in \{0, 1\}$ es el proyector antiestafa.

| Operador | Nombre | Papel en la ecuación | Sección |
|---|---|---|---|
| Ω | Despachador Óctuple | Pone precio a cada pata — una rama por tipo de pool — y aporta una profundidad denominada en tokens para el asignador de división | §4 |
| 𝒟 | Derivación Determinista | Localiza los pools que usa la ruta, por dirección calculada donde la familia lo permite, por id de pool probado para el singleton de V4 | §5, §7 |
| Φ | Suelo Iron-Law | Proyecta la ruta sobre el conjunto factible: un suelo bajo la salida neta, vuelto a derivar en el marco de ejecución | §11 |
| Ψ | Campo de Vitalidad | Pondera cada pool según la calidad que se ha ganado, leída de su estado on-chain empaquetado | §9 |
| Ξ | proyector antiestafa | Valora exactamente en cero cualquier ruta que no sea seguro mostrar | §5, §14 |

La factorización es deliberada. Ω responde *cuánto devuelve un pool*; Ψ responde *cuánto confiar en él*; Φ responde *si la ruta es admisible*; 𝒟 responde *dónde vive el pool*; Ξ responde *si es seguro mostrar la ruta*. La forma de producto significa que una sola pata que falla hunde la puntuación de la ruta — una pata que cotiza cero, o un pool sin reputación, anula el producto en lugar de rebajarlo — y el indicador pone a cero sin matices cualquier ruta cuya salida neta quede por debajo del suelo. Sin penalizaciones suaves, sin pesos ajustables, sin que se cuele una ruta que casi cumple.

![FIG-1 — La Meta-Ecuación como pipeline del Solver: candidatos de 𝒟 y del registro, clasificados y truncados por Ψ, cotizados por Ω, asignados entre patas, comprobados contra Φ y Ξ; el Router vuelve a evaluar Ω y Φ en el marco de ejecución.](https://blazephoenix.xyz/whitepaper/figs/es/fig-1.png)

On-chain, el Solver materializa la ecuación como un pipeline (`findBestRoutePlan`): los candidatos por par los reúne el registro y, cuando el registro no está al día, 𝒟; Ψ los clasifica y los trunca a `MAX_CANDIDATES = 8`; Ω los sondea y los cotiza; una banda de credibilidad los filtra; se asignan entre patas; y la ruta ensamblada lleva el suelo que atesta Φ. Después, el Router vuelve a evaluar Ω y Φ sobre las cantidades que realmente llegaron. La afirmación que hace la ecuación — aquella cuyo cumplimiento un auditor puede exigir al código — es que ningún paso de ese pipeline consulta nada fuera de los cinco operadores. Si una cantidad no es argumento de uno de los cinco, no puede influir en la ruta: ni el marketing de un venue, ni una reputación off-chain, ni una preferencia del operador.

### §3.1 El presupuesto de dependencias

En lugar de adjetivos — *descentralizado*, *sin servidores*, *sin confianza* han dejado de transmitir información —, esta es la lista comprobable: todo lo que debe existir, y seguir funcionando, para que se liquide una operación.

| Dependencia | Agregador típico resuelto off-chain | BlazePhoenix-Dex |
|---|---|---|
| Un servicio que calcula la ruta | Necesario | No se usa — el Solver es una función `view` que sirve cualquier nodo |
| Un oráculo o feed de precios | Varía | Ninguno — no existe ninguna llamada a un feed en los cinco contratos |
| Una red o subasta de solvers | Habitual | Ninguna |
| Un keeper, cron o relayer | Varía | Ninguno — la vitalidad decae por aritmética en la siguiente lectura; el registro aprende de las operaciones ordinarias |
| Un proxy actualizable | Habitual | Ninguno — sin proxy, sin `selfdestruct`, sin inicializador; los únicos `delegatecall` son los del propio compilador hacia la biblioteca pública del Core, fijados en tiempo de enlazado |
| Una interfaz alojada | En la práctica, sí | Solo por comodidad |

La última fila es donde la mayoría de los presupuestos fallan en silencio. La interfaz alojada es una comodidad, no una dependencia: `swapBestExactIn` recibe seis valores que una persona puede reunir a mano, y un integrador, un script o el front end de un competidor pueden llamarla sin ningún tipo de relación con nosotros. Si todos los servidores que opera este proyecto desaparecieran esta noche, los contratos cotizarían, enrutarían y liquidarían mañana exactamente igual que hoy. El costo de esa propiedad es que el llamador paga la búsqueda en gas; el presupuesto de arriba es la lista de cosas que ese gas permite eliminar.

La inmutabilidad también corta en sentido contrario. Un defecto no puede parchearse — el precio de un contrato que auditas una sola vez —, y por eso, en su lugar, los defectos se divulgan y se recompensan con bounties (§21).

### §3.2 Tres límites

El presupuesto tiene tres límites, enunciados junto a la afirmación que acotan. La interfaz está alojada, como la de todos, y un front end comprometido es la mayor exposición individual del modelo de amenazas: el contrato respeta cualquier mínimo distinto de cero que reciba, así que el mínimo que fijas es la única cota que ninguna interfaz puede quitarte. El descubrimiento de venues lee estado que escribieron transacciones anteriores — público y sin permisos, pero no creado de la nada en el momento de la operación. Y los poderes administrativos existen hasta que se renuncia a ellos a través de la Puerta de un Solo Sentido; tras la renuncia, el nivel de control — pausa, roles, tesorerías, cableado, eliminaciones — desaparece para siempre, y solo queda el nivel de curador, que solo puede crecer (§14). Núcleo sin estado, operación sin servidores, gobernanza que solo crece: la tabla es la afirmación; las palabras son su sombra.


## §4 El Despachador Óctuple Ω

*En palabras simples:* una función que sabe poner precio a cada tipo de pool. *Con precisión:* `universalQuote(QuoteCtx, amountIn)` en el Core es el despachador de cotizaciones del Solver y de la vista previa del Quoter; devuelve la salida esperada y una profundidad denominada en tokens para el asignador de división, y un pool al que no puede poner precio devuelve cero y se descarta antes de enviar nada. El Router evalúa las mismas primitivas del Core por pata en su propio marco (`_hopScaleImpactAndQuote`), una función hermana que se mantiene por razones de gas y de profundidad de pila, y el Pase Exacto del Quoter plantea a su vez otra pregunta distinta (§12); las tres constan juntas en el registro de cantidades compartidas del repositorio, y su concordancia está fijada por tests con nombre en lugar de darse por supuesta (§15). El nombre interno se conserva de la enumeración de ocho tipos con la que se lanzó el despachador; desde entonces la enumeración ha ganado un noveno miembro y ha cedido tres a una supresión deliberada. La numeración conserva sus huecos: un Monoslot registrado ayer debe decodificarse de forma idéntica para siempre.

### §4.1 Seis tipos, cuatro familias, una tabla de atributos

| Tipo | Constante | Familia | Rama de precios | Atributos Θ | Escala de gas (Solver) |
|---|---|---|---|---|---|
| 0 | `KIND_V2` | Producto constante (Uniswap V2, Sushi, forks) | forma cerrada `outV2` sobre `getReserves` | reservas · par verificable | 90,000 |
| 1 | `KIND_V3` | Liquidez concentrada (Uniswap V3 y forks con forma de V3) | forma cerrada `outV3` sobre `slot0` | concentrado en el pool · par verificable | 110,000 |
| 4 | `KIND_V4` | Singleton de Uniswap V4 | forma cerrada `outV3` sobre `extsload` | concentrado en el singleton | 180,000 |
| 5 | `KIND_SOLIDLY` | Solidly (Aerodrome, Velodrome, forks), estable y volátil | preguntar al pool `getAmountOut`; curva replicada como respaldo | reservas · par verificable | 90,000 |
| 6 | `KIND_ALGEBRA` | Algebra (clase Camelot), comisión dinámica | forma cerrada `outV3` sobre `globalState` | concentrado en el pool · par verificable | 110,000 |
| 8 | `KIND_V4_NATIVE` | Pool V4 que contiene la moneda nativa de la chain | misma rama que el tipo 4 | concentrado en el singleton | 215,000 |

Seis tipos, cuatro familias operativas: producto constante {0}, concentrada {1, 6}, Solidly {5} y el singleton de V4 {4, 8}. La columna de atributos no es prosa: es la tabla de bits `THETA_ATTR = 0x040A9400A9` del Core, cuatro bits por tipo — *lee reservas*, *concentrado en la dirección del pool*, *concentrado en el singleton*, *expone `token0()`/`token1()`* — de modo que cada pregunta de pertenencia en los cinco contratos (“¿este tipo lee reservas?”) es un desplazamiento y un AND (`kindHas`, `kindHasAny`), y un tipo sin bits falla cerrado sin ninguna rama por defecto que alguien pueda olvidar. La columna de gas es una segunda tabla, `THETA_GAS`, que solo lee el `estGas` del Solver, en unidades de 5,000 gas por pata.

**Los tipos 2, 3 y 7 faltan por supresión deliberada.** La familia ponderada (3) nunca produjo una cotización enrutable. Las dos familias Curve (2, 7) funcionaban y aun así se eliminaron, en el endurecimiento de la frontera de confianza de agosto de 2026, porque darles soporte exigía el único `approve` de tokens del Router, la única cifra de profundidad atestada por un llamador en lugar de medida en un pool, y la única excepción en la prueba de autenticidad de pares del Hub — tres superficies de ataque, cada una la última de su clase. Los números retirados nunca se reasignan: `decodeKind` lee el tipo a partir de bits del Monoslot, así que reutilizar el 2 reinterpretaría cada pool registrado alguna vez con él, y una guarda de CI hace fallar el build si vuelve un símbolo suprimido.

**El tipo de moneda nativa (8) existe porque un invariante se rompe ahí y en ningún otro sitio.** El supuesto de trabajo del Router — cada activo es un ERC-20 con `balanceOf` — falla exactamente cuando la moneda de un pool V4 es el activo nativo de la chain, y codificar esa excepción como un *tipo* la hace visible en el registro del pool, la lleva en el plan de ruta y permite que un auditor la encuentre con grep. El cálculo del precio no necesita ninguna rama: el identificador del pool se deriva de las dos monedas ordenadas, y `address(0)` queda primera por construcción. El tipo se gana su lugar empíricamente: según la medición del 2026-08-13 en las chains atendidas, el 62.9 % de la liquidez denominada en ETH dentro de V4 estaba en pools nativos (Arbitrum 99.6 %, Optimism 95.0 %, Base 48.9 %).

### §4.2 Las formas cerradas

**Producto constante.** Las reservas cumplen $x \cdot y = k$ y una comisión $f$ en puntos básicos se toma de la entrada. El Core calcula

$$
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

sin ninguna división hasta el paso final, de modo que el único redondeo es el truncamiento hacia abajo del último cociente, y una comisión de `BPS` o más hace que el pool no pueda cotizarse (devuelve cero). La comisión que entra en esta fórmula es `effV2Fee(declared)`: una comisión declarada de cero o superior a `V2_FEE_CEILING_BPS = 100` se sustituye por los 30 bps canónicos, de modo que un registro no puede rebajar su propia cotización — y con ella el suelo que esa cotización ancla — declarando una comisión inverosímil; la misma regla se replica en el camino de ejecución, de modo que ambos lados de la operación ponen precio con un solo número.

**Ejemplo resuelto 1 — una cotización de producto constante.** Un pool contiene $r_{\mathrm{in}} = 1{,}000{,}000$ USDC y $r_{\mathrm{out}} = 500$ WETH con los 30 bps canónicos; un trader vende $x = 10{,}000$ USDC. En las unidades propias del pool (USDC tiene 6 decimales, WETH 18) $x = 10^{10}$, $r_{\mathrm{in}} = 10^{12}$, $r_{\mathrm{out}} = 5 \times 10^{20}$:

1. Entrada ajustada por la comisión: $x(\mathrm{BPS}-f) = 10^{10} \times 9{,}970 = 9.97 \times 10^{13}$.
2. Numerador: $9.97 \times 10^{13} \times 5 \times 10^{20} = 4.985 \times 10^{34}$.
3. Denominador: $10^{12} \times 10^{4} + 9.97 \times 10^{13} = 1.00997 \times 10^{16}$.
4. Salida: $\lfloor 4.985 \times 10^{34} / 1.00997 \times 10^{16} \rfloor = 4.935790 \times 10^{18}$ wei, es decir, **4.935790 WETH** (con seis decimales; el contrato redondea hacia abajo el último cociente al wei).

Al precio spot de 0.0005 WETH por USDC, los mismos 10,000 USDC obtendrían 5 WETH; la ejecución queda un 1.28 % por debajo del spot, del cual el 0.30 % corresponde a la comisión. La medida de impacto basada en reservas que el Core calcula para esta pata es $\mathrm{impactV2Bps} = \lceil x \cdot \mathrm{BPS} / (r_{\mathrm{in}} + x) \rceil = \lceil 10^{14} / 1.01 \times 10^{12} \rceil = 100$ bps — el número que consume el suelo Iron-Law en §11.

**Liquidez concentrada.** Los pools con forma de V3 no almacenan reservas; almacenan la raíz cuadrada del precio $\sqrt{P}$ en punto fijo Q64.96 y una liquidez activa $L$. Dentro de un tick, para token0 → token1 con una comisión $f$ en partes por millón y $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

y para token1 → token0, $\sqrt{P}\,' = \sqrt{P} + x_{f} Q_{96}/L$ y $y = L\,Q_{96}(\sqrt{P}\,' - \sqrt{P})/(\sqrt{P}\,\sqrt{P}\,')$, con la división repartida en dos etapas. El cálculo ingenuo desborda: $L \cdot \sqrt{P}$ puede llegar a $2^{288}$. El Core nunca lo forma — cada producto se factoriza a través de un `mulDiv` de precisión completa (intermedio de 512 bits, resultado de 256 bits), de modo que cada etapa cabe de forma demostrable, y un paso que movería el precio en la dirección equivocada o a cero devuelve cero en lugar de un número. Los pools Algebra (tipo 6) comparten la rama y difieren en un aspecto que la rama gestiona: su estado y su comisión dinámica viven en `globalState`, que se lee una sola vez (§5.4).

La fórmula es exacta dentro de un único tick y no es exacta a través de los límites de tick, donde la forma de un solo tick sobrestima. El despachador no replica la aritmética de cruce de ticks: replicar la matemática interna de un venue es un riesgo estructural, porque cualquier divergencia entre la copia y el original aflora como una cotización con un precio erróneo. Dos cosas ocupan su lugar. El Core incorpora un recorte de frontera (`sqrtBoundary`) que trunca el paso en el borde del rango actual — exacto para lo que cabe dentro del rango y estrictamente por debajo para el resto, nunca por encima — y el diseño lo sitúa del lado de la *promesa*, no de la clasificación, porque recortar solo las familias que tienen ticks compararía venues bajo dos convenciones. En el árbol que mide este documento, el recorte está definido pero aún no conectado: `Router._v4LegQuote` y el brazo de clasificación del Core llaman ambos a la forma de un solo tick sin recorte, de modo que una pata concentrada que sale de su rango solo está acotada por el suelo por pata de §11.3 y el presupuesto de hops, con el suelo Iron-Law como red de seguridad. Y en la ejecución el suelo Iron-Law se impone contra una cotización que el Router vuelve a derivar dentro del marco a partir de las cantidades que realmente llegaron, de modo que una sobrestimación en un límite de tick le cuesta a una ruta su precisión de clasificación, nunca al usuario el suelo. El Pase Exacto del Quoter (§12) da a un integrador el número del propio pool cuando lo quiere.

**Profundidad.** Cada rama devuelve además una profundidad en unidades de token — el lado de reserva menor para las familias de reservas (`shortSide18`), y para las familias concentradas la reserva virtual $L/\sqrt{P}$ o $L\sqrt{P}$ que $L$ representa al precio actual (`depthFromL18`), de modo que un pool V3 y un pool V2 de igual profundidad se comparan como iguales en el asignador en lugar de diferir en un factor de $\sqrt{P}$.

### §4.3 Dos unidades de comisión, llevadas de forma nativa

Los venues de producto constante y Solidly cotizan las comisiones en puntos básicos; los venues concentrados, en partes por millón. El despachador lleva cada una en su unidad nativa y nunca convierte entre ellas, lo que elimina una clase de fallo de redondeo al precio de dos constantes en lugar de una. Dos familias de venues solo conocen su comisión en el momento de la ejecución — los pools V4 de comisión dinámica y Algebra — y ambas se gestionan midiendo la comisión a partir de la misma lectura de estado que proporciona el precio (§5.4).

### §4.4 Un despachador, dos superficies

| Tipo | Vista previa del Quoter | Dentro del marco (plan del Solver; re-derivación del Router) |
|---|---|---|
| V2 (0) | forma cerrada `outV2` | forma cerrada `outV2` |
| V3 / Algebra (1, 6) | forma cerrada `outV3` sobre `slot0` / `globalState`; la comisión vigente de Algebra se mide en la misma lectura | igual |
| Solidly (5) | preguntar al pool; curva replicada × 9,800 / 10,000 como respaldo | igual |
| V4 / V4 nativo (4, 8) | forma cerrada `outV3` vía `extsload`, compuerta de comisión que falla cerrado (E19) | igual |

Lo que separa una vista previa de una ejecución es, por tanto, el tiempo: el estado que se movió entre el bloque de la vista previa y el marco de ejecución. §12 mide exactamente esa brecha sobre 240 muestras.

## §5 Uniswap V4 de extremo a extremo

Uniswap V4 no es un nuevo contrato de pool; es una nueva forma de venue, e invalida cuatro supuestos sobre los que descansa todo router construido entre 2020 y 2024. Los pools no tienen direcciones — son entradas en el storage de un único singleton, indexadas por un hash. Dentro del swap corre código arbitrario — un hook se ejecuta en puntos definidos de la liquidación que el router está realizando. Las comisiones no tienen por qué ser constantes. Y los pools pueden contener la moneda nativa de la chain, que no tiene `balanceOf`. Esta sección responde estructuralmente a los cuatro, de modo que una pata V4 sea — para el Solver, para los suelos y para el trader — indistinguible de cualquier otra pata.

### §5.1 Localizar un pool que no tiene dirección: derivar y después probar

La Derivación Determinista 𝒟 calcula direcciones (§7), pero un pool V4 no tiene ninguna dirección que calcular. Lo que tiene es un identificador — el hash keccak de su `PoolKey` —, así que el modo 9 de la tabla de derivación deriva *poolIds* en lugar de direcciones, y después hace algo que ninguna derivación de direcciones puede hacer: prueba que el pool existe, leyendo el propio storage del PoolManager para ese id y exigiendo un precio raíz cuadrada distinto de cero y una liquidez distinta de cero. Nadie puede falsificar esa prueba, porque nadie salvo el manager escribe ese storage. Un id inventado se resuelve en ceros y se descarta.

**Ejemplo resuelto 4 — el identificador de un pool V4 a partir de su clave.** El id canónico es $\mathrm{poolId} = \mathrm{keccak256}(\mathrm{abi.encode}(c_{0}, c_{1}, \mathrm{fee}, \mathrm{tickSpacing}, \mathrm{hooks}))$ (`computeV4PoolId`), cinco palabras de 32 bytes. Toma el pool ETH nativo / USDC en Base en el nivel del 0.05 %: $c_{0} = \mathtt{0x0000…0000}$ (la moneda nativa se ordena primero), $c_{1} = \mathtt{0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913}$ (USDC), fee $= 500$, tickSpacing $= 10$, hooks $= \mathtt{0x0}$.

1. `abi.encode` dispone 160 bytes: las dos monedas rellenadas por la izquierda hasta 32 bytes cada una, luego `500`, luego `10` (un `int24`, extendido con signo a 32 bytes) y luego el hook cero.
2. $\mathrm{poolId} = \mathtt{0x96d4b53a38337a5733179751781178a2613306063c511b78cd02684739288c0a}$.
3. El campo `pool` del registro para una fila V4 son los 160 bits bajos del id, $\mathtt{0x781178a2613306063c511b78cd02684739288c0a}$ — una dirección sin código, razón por la cual a las filas V4 nunca se les pide `token0()`, nunca se les lee un saldo y, en su lugar, se prueban a sí mismas por re-derivación (§9).
4. La base de storage del pool es $\mathrm{keccak256}(\mathrm{abi.encode}(\mathrm{poolId}, 6)) = \mathtt{0x13a3a697…73b2893d}$: el slot 6 del manager es el mapping `pools`. El Core lee la palabra 0 en ese slot y la palabra 3 en $\mathrm{base} + 3$ (`v4SqrtAndLiq`, selector `extsload(bytes32)` = `0x1e2eaeaf`).
5. La palabra 0 empaqueta $\sqrt{P}$ en los bits [0, 160), el tick en [160, 184), la comisión de protocolo en [184, 208) y la comisión de LP en [208, 232); la palabra 3 lleva la liquidez activa en sus 128 bits bajos. Si $\sqrt{P}$ o $L$ es cero, el pool no existe para este protocolo.

El hermano envuelto del mismo par — $c_{0} = \mathtt{0x4200…0006}$ (WETH), mismo nivel — tiene el id $\mathtt{0x90333bb0…ebd943a0}$: un pool distinto con una profundidad distinta, razón por la cual el escaneo sondea ambas claves y sondea primero la nativa (más abajo). La disposición del paso 5 no se ha tomado de la documentación: el mismo pool leído con esta disposición y con el contrato canónico `StateView` devuelve los mismos números en un fork de mainnet, y esa identidad es lo que cita el comentario del Core.

**La escalera de costos.** El escaneo que produce ids candidatos es una escalera, con el peldaño más barato primero, porque un barrido ingenuo sobre todas las combinaciones de comisión y espaciado supondría una factura de gas inaceptable en el camino de resolución:

| Peldaño | Qué prueba | Costo cuando acierta |
|---|---|---|
| 1 | El patrón de niveles ya aprendido para este par de tokens | una sonda — el camino en régimen estable |
| 2 | Una comprobación de procedencia antes de pagar un escaneo en frío | una lectura |
| 3 | Los niveles canónicos de Uniswap, agrupados en lote mediante `extsload(bytes32[])` | una llamada para todo el lote |
| 4 | Los pares `(fee, spacing)` que declara la fila de inscripción | acotado por la inscripción |
| 5 | La rejilla generadora de arranque en frío — comisión $= 10{,}000\,j$, espaciado $= 100\,j$, $j$ descendiendo desde `V4_GRID_MAX = 99` | como mucho `V4_GRID_PROBES = 40` sondeados |

Todo el escaneo se detiene en `V4_CAP = 8` pools probados. Dos propiedades se desprenden de la construcción y no de una comprobación: este escaneo nunca puede emitir un pool con hook — deriva ids *sin hook*, y el id sin hook de un pool con hook no existe en el manager, así que no supera la prueba de vida antes de que se plantee ninguna cuestión de política —, y los ids duplicados procedentes de distintas conjeturas de nivel se colapsan en un solo candidato, así que un pool no puede saturar el embudo mediante alias. El orden de sondeo es en sí mismo una política, porque el tope lo consume lo que se sondee primero: un par contra el activo nativo de la chain tiene dos claves en V4, nativa y envuelta, y se midió que la clave nativa era la más profunda en las chains atendidas cuando se decidió el orden (3.76× en Base, 292× en Robinhood Chain), así que se sondea primero.

![FIG-2 — La escalera V4 de derivar y probar: cinco peldaños desde el patrón de niveles aprendido hasta la rejilla generadora acotada, cada id candidato probado vivo contra el storage del manager antes de contar para el tope de ocho; los ids con hook nunca existen en este camino.](https://blazephoenix.xyz/whitepaper/figs/es/fig-2.png)

**La puerta sin permisos.** Cualquiera puede llamar a `claimV4(c0, c1, fee, tickSpacing)` y lograr que se admita un pool sin hook, porque la reclamación lleva su propia prueba: el id se recalcula a partir de la clave enviada y se vuelve a leer del storage del manager, así que una reclamación falsa se resuelve en ceros y no admite nada (`HubE(9)`). Otras tres condiciones controlan la puerta: un lado del par debe ser una moneda puente enrutable, para que una plaza del registro con tope nunca se gaste en un par que el router nunca puede cruzar; la comisión efectiva debe ser cotizable según la compuerta de comisión de más abajo; y la reclamación debe superar el mismo margen de admisión que supera una inscripción impulsada por swaps (§9), con la liquidez medida del pool como su profundidad. El público puede enseñar al registro, pero no puede mentirle.

### §5.2 Leer un pool que no responde a llamadas

Con un id probado vivo, el despachador necesita precio y liquidez — y no hay ningún `slot0()` al que llamar. El Core lee directamente el storage del singleton mediante `extsload`, como en el ejemplo anterior, y la rama V4 se reduce a la forma cerrada de liquidez concentrada de §4.2: las mismas matemáticas, la misma disciplina frente al desbordamiento. El singleton cambia dónde vive el estado, no lo que el estado significa. Un manager sin código o no conforme produce palabras todas a cero — falla cerrado, nunca una reversión.

### §5.3 Liquidar con un singleton

La ejecución invierte el flujo de control habitual. En lugar de llamar a un pool y recibir una llamada de vuelta para el pago, el Router llama a `unlock` en el manager y realiza todo el swap dentro del callback que el manager le concede: swap, luego liquidar cada moneda adeudada mediante `sync → settle → take`, y luego liberar el bloqueo. Las monedas en curso cruzan la frontera del desbloqueo en transient storage (`TSLOT_V4IN`, `TSLOT_V4OUT`), así que el marco no ensucia ningún estado persistente y el contexto está demostrablemente vacío entre swaps. El `BalanceDelta` que devuelve el manager viene empaquetado — `amount0` en los 128 bits altos, `amount1` en los bajos — y el Router lo decodifica como tal antes de que se ejecute cualquier aritmética de liquidación.

La moneda nativa conserva el invariante que importa. El Router es canónico en WETH en todas partes — el valor nativo que entra por `swapExactInNative` se envuelve exactamente una vez en la puerta, y la salida nativa deliberadamente no está implementada: el destinatario recibe WETH, que ningún camino puede atrapar en silencio. Una pata V4 nativa desenvuelve *justo a tiempo* dentro de su propio marco de desbloqueo — `WETH.withdraw`, luego `settle` con valor, y el ETH recibido de un `take` nativo se vuelve a envolver en el mismo marco —, así que el ETH nativo nunca existe en reposo en el contrato. La única superficie nueva que esto requirió es un `receive()` restringido tanto como el requisito permite: un slot transitorio (`TSLOT_ETHOK`) guarda la única dirección autorizada a enviar ETH en bruto al Router en ese instante — el contrato WETH canónico durante el desenvolvimiento, el PoolManager durante un take nativo, cero en cualquier otro momento —, así que el ETH a secas de cualquier otro revierte. La puerta nativa falla cerrado hasta que se conecte `weth` (`RouterE(3)`).

### §5.4 La comisión que solo existe en el momento de la ejecución

Un pool V4 de comisión estática lleva su comisión de LP en su clave, donde es inmutable. El pool manager puede además cobrar la propia comisión de manager de Uniswap — con un tope de 1,000 pips, una décima parte del uno por ciento, por dirección — que el brazo estático del Core no suma, así que una cotización sobre una clave estática puede sobrestimar la salida entregada hasta en esa comisión de manager una vez que el controlador de comisiones de Uniswap la active (es cero en todas las chains atendidas en el momento de escribir esto); el déficit queda dentro del suelo, y la base de la comisión, al ser medida, lo ignora. Un pool de comisión dinámica lleva en cambio el centinela `0x800000`, y su comisión real vive en `slot0`. Cotizar el centinela por su valor nominal pondría precio de gratuita a la operación mientras la ejecución pagaría la tasa vigente. La compuerta son tres líneas en el 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

Una clave de comisión dinámica acompañada de una comisión de protocolo distinta de cero devuelve una comisión igual o superior a $10^{6}$, y `outV3` devuelve cero para ella: falla cerrado, porque la composición de las comisiones de protocolo y de LP no está anclada a nuestra satisfacción, y la comisión de protocolo medida en Base en el momento de escribir esto es cero, así que la rama no cuesta nada en la práctica. La misma regla cierra la misma costura en la familia vecina: los pools Algebra son de comisión dinámica por construcción y registran el centinela cero, así que `quoteV3Fee` toma como verdad una comisión configurada distinta de cero, la comisión de un pool dinámico de la lectura de `globalState` que ya suministró su precio y — para los forks de liquidez concentrada cuya comisión se indexa por espaciado de ticks y vive en el pool — el propio `fee()` del pool, fallando cerrado en `0xFFFFFF` cuando nada responde. Los pools V3 estáticos no hacen ninguna lectura extra y cotizan de forma idéntica byte a byte.

Queda una costura, que se acota en lugar de eliminarse: un hook de comisión dinámica puede sobrescribir su comisión en cada swap dentro de `beforeSwap`, así que la comisión almacenada en `slot0` no es en todos los casos la ejecutada. Ese residuo lo contienen la lista de permitidos de hooks del Hub en la admisión y el suelo Iron-Law re-derivado de la salida realizada en la ejecución, así que una sobrescritura hostil acaba en una reversión o en un déficit dentro del suelo, nunca en una ejecución por debajo del suelo (§14).

### §5.5 Hooks: filtrados por aritmética, nunca por interrogatorio

Un hook es código arbitrario que corre dentro de la liquidación que el Router está realizando. La defensa intuitiva — preguntar al hook qué pretende — no es una defensa, porque un hook hostil miente o reentra. BlazePhoenix nunca pregunta nada a un hook. En su lugar se alzan tres capas independientes, la más barata primero, y §14 las desarrolla: los bits de permiso del hook viven en los catorce bits bajos de su propia dirección, fijados en el despliegue, y los dos bits que permiten alterar los deltas del swap se rechazan con un solo AND antes de que se mueva un token (`hookAltersDeltas`, `RouterE(9)`); un hook es enrutable solo mientras está en la lista de permitidos y mientras su código de runtime sigue coincidiendo con el codehash fijado en la admisión (`isHookLive`); y el proyector Ξ elimina un hook que altera deltas del conjunto de candidatos del Solver antes de clasificar, así que la ruta ni siquiera es representable. El residuo se declara junto con el mecanismo: un hook en la lista de permitidos y libre de deltas aún puede revertir a mitad del swap y desperdiciar el gas del intento — el precio de negarse, por principio, a llamar a código no confiable para averiguar qué habría hecho.

### §5.6 Lo que vale la integración

Cada mecanismo anterior es o bien una primitiva del Core ya usada en otro lugar, aplicada a una nueva disposición del estado, o bien una primitiva genuinamente nueva que se generaliza más allá de V4 — el descubrimiento por prueba de existencia. Nada es una integración por pool, y nada es una lista curada que un humano deba mantener al día. Los pools V4 se descubren sin directorio, se prueban contra un storage que nadie más puede escribir, se cotizan con la misma forma cerrada que cualquier otro venue concentrado, pasan una compuerta de comisión basada en la medición, con un rechazo que falla cerrado donde la medición no es exacta, se filtran frente a hooks hostiles mediante aritmética inmutable sobre la dirección, se liquidan mediante el propio protocolo de bloqueo del singleton y son alcanzables en ETH nativo sin que el Router guarde ni un wei de él en reposo. §17 recoge las mediciones: en el censo de factories en Arbitrum, la familia V4 gana más pares de hops que cualquier otro venue registrado, y en Optimism el censo registró lo que es, hasta donde sabemos, la primera agregación enrutada por V4 de esa chain. No conocemos ningún otro agregador público que derive, pruebe, cotice, filtre y liquide pools V4 enteramente dentro de la transacción en ejecución, ETH nativo incluido, sin ninguna lista curada de pools; si encuentras uno, la afirmación es errónea y nos gustaría saberlo.

## §6 El solver Solidly

Algunos invariantes no tienen una forma cerrada lo bastante simple para reproducirla con seguridad, y reproducirlos es exactamente el riesgo de replicación que el protocolo rechaza en otros lugares. Para esos venues, el despachador pregunta al pool, por el canal honesto más estrecho que expone el venue — y guarda una réplica de reserva para los forks que no responden.

La familia Solidly — Aerodrome, Velodrome y sus forks — sirve activos correlacionados en pares estables con un invariante cuártico que se ciñe a la diagonal mucho más que el producto constante, sin dejar de admitir divergencia:

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

Cerca del punto de equilibrio, esta curva entrega casi toda la entrada como salida — slippage mínimo justo donde se concentra el flujo de stablecoins y de liquid staking. Los pares Solidly volátiles son de producto constante, y el brazo volátil del Core *es* `outV2` — un solo productor, fijado por la relación metamórfica MR7 (§15).

![FIG-3 — Salida frente a entrada para un par estable equilibrado: la cuártica Solidly frente al producto constante y la diagonal de slippage cero, a partir de las propias fórmulas del Core.](https://blazephoenix.xyz/whitepaper/figs/es/fig-3.png)

**Camino principal: preguntar al pool.** `getAmountOut(amountIn, tokenIn)` lo calcula el propio bytecode del par — comisión vigente, curva y redondeo incluidos —, así que un swap que pide exactamente esa cifra satisface por construcción la comprobación de invariante del pool, y no hace falta ningún margen. El disparador del fallback es una respuesta de como mucho 1 wei, alineada en los tres canales que preguntan (Solver, Router, Quoter) después de que un pool que respondía exactamente 1 enviara una vez a dos de ellos por ramas distintas.

**Fallback: replicar y luego pedir de menos.** Cuando un fork no expone el selector, el despachador lee la comisión vigente de la factory del par (`getFee(pool, stable)`; una comisión de fallback declarada solo se cree dentro del mismo techo de 100 bps que el brazo V2, y si no, 30 bps), resuelve el invariante sobre reservas normalizadas por decimales y multiplica el resultado por 9,800 / 10,000 — un descuento de 200 bps para que el redondeo del pool, que la réplica no puede observar, tenga siempre holgura. El descuento se aplica solo en el fallback y nunca cuando el pool respondió por sí mismo.

**La resolución.** Dados el saldo de entrada tras la operación, $x$, y el invariante objetivo $K$ obtenido de las reservas previas a la operación, el Core busca la raíz de $f(y) = x^{3}y + xy^{3} - K$, con $f'(y) = x(x^{2} + 3y^{2}) > 0$, por iteración de Newton con semilla en $y_{0} = Y$, la reserva opuesta previa a la operación (`_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

La semilla de aspecto natural — la aproximación de suma constante — es errónea de un modo que corrompe los precios en silencio: para un pool que absorbe una entrada varias veces mayor que su reserva, cae muy por encima de la raíz, en una región donde $f$ es extremadamente empinada, el paso se redondea hacia abajo a cero y el solver se queda clavado en un número seguro, plausible e incorrecto. Sembrar en la reserva opuesta y avanzar en ambas direcciones converge tanto para pools equilibrados como para pools muy desequilibrados. Un paso descendente que cruzaría el cero se sustituye por la mitad del iterado; todo paso fuerza al menos una unidad de avance; la iteración termina cuando iterados sucesivos difieren como mucho en una unidad y cae del lado seguro para el invariante mediante un ajuste final de una unidad en contra del tomador. El bucle tiene un tope de 64 iteraciones y el tope falla cerrado: el solver devuelve la propia semilla, que la guarda $y \ge Y$ del llamador convierte en una cotización cero — el pool se trata como imposible de cotizar, igual que el propio Aerodrome se niega en lugar de cotizar mal. Una derivada que se anula toma la misma salida.

**Normalización de decimales.** El invariante es homogéneo de grado cuatro, así que cuando ambos tokens comparten decimales la escala se cancela y las reservas en bruto funcionan directamente. Un par como DOLA (18 decimales) contra USDC (6) arrastra el desajuste a la cuártica, así que el Core escala ambos lados a una base común de $10^{18}$ antes de resolver y desescala después:

$$
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

Los tokens que declaran más de dieciocho decimales cotizan cero en lugar de provocar un underflow en el escalado. Los decimales del otro token se leen del pool, no se toman del llamador — una primitiva de productor único que aceptara la coordenada de su llamador reabriría la divergencia que existe para cerrar.

**Fallo cerrado en todo el dominio.** Cuatro guardas deciden si la curva llega siquiera a evaluarse, y cada una devuelve cero en lugar de revertir, porque una reversión dentro de una cotización es un `DELEGATECALL` de biblioteca que deshace todo el plan del Solver para el par: $X$ e $Y$ no deben superar $3.4 \times 10^{38}$; $X + A$ tampoco; y `_solKFits` exige que tanto el invariante $k = xy(x^{2}+y^{2})/\mathrm{WAD}^{3}$ como la derivada $x(x^{2}+3y^{2})/\mathrm{WAD}^{2}$ quepan en una palabra de 256 bits en la semilla, que es el punto más grande que visita Newton. Esa última prueba se plantea sin división — calcula la palabra alta del producto de 512 bits y la compara con un WAD (`_mulFitsWad`) — porque la forma anterior, $a \le \max \cdot \mathrm{WAD} / b$, desbordaba ella misma en un pool que tenía menos de una unidad de un token. El carril N-versión de §15 encontró esa fuga el 2026-09-05; la reparación es 35 bytes más pequeña que el código al que sustituyó y entró escrita primero en rojo, con un fuzz de 5,000 ejecuciones sobre todo el dominio, un barrido de 60,000 muestras de la misma aritmética en Python y tres mutantes en la guarda.

**El residuo medido y el margen de 200 bps.** La réplica se midió contra pools de Aerodrome en vivo, con el propio `getAmountOut` del pool como valor de referencia:

| Pool | Estado | Valor de referencia | Salida del Core | Desviación |
|---|---|---|---|---|
| USDC / USDbC | equilibrado (6 / 6 dec) | 999,534,071 | 999,534,071 | 0.000 % |
| DOLA / USDC | desequilibrado (18 / 6 dec) | 985,528,437 | 987,998,286 | 0.251 % |
| USDz / USDC | desequilibrado (18 / 6 dec) | 899,325,141 | 901,565,830 | 0.249 % |

El par equilibrado coincide hasta el entero; los pares desequilibrados quedan dentro del 0.5 %, por el lado alto, porque el redondeo del saldo del pool tras la comisión es invisible desde fuera. Ese residuo es la razón por la que el fallback pide 200 bps de menos: el margen garantiza que la comprobación de invariante del par tenga holgura en la ejecución, lo que convierte una posible reversión por sobrecotización en una ejecución asegurada, y se aplica de forma idéntica en las superficies de vista previa y dentro del marco, para que ambas avancen en sincronía. El carril de oráculo canónico de §15 acota la curva de forma independiente: frente a una implementación escrita a partir de la especificación del venue, la cotización estable del Core queda dentro de 4 wei en 5,000 entradas de fuzz.

## §7 Derivación Determinista 𝒟

Antes de poder cotizar un pool, hay que encontrarlo. La respuesta de la industria es preguntar: una llamada a la factory del venue lee un mapping de storage y devuelve una dirección, y el llamador se fía de la palabra de la factory. BlazePhoenix contempla esa respuesta — cuatro modos de llamada a la factory cubren los venues cuyos despliegues no son reproducibles — y prefiere una más sólida allí donde la aritmética lo permite: calcular la dirección, sin ninguna llamada externa. Una dirección calculada la puede verificar cualquiera que tenga las mismas tres entradas; de una dirección obtenida solo cabe fiarse.

El mecanismo es CREATE2. La EVM hace de la dirección de un contrato una función pura de su desplegador, un salt y el hash de su init code:

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

donde $\mathrm{addr}_{20}$ toma los últimos veinte bytes del hash. El Core escribe la preimagen de 85 bytes exactamente una vez (`create2Address`); `deriveAddress` selecciona el polinomio de salt y el origen según el modo, y la derivación Algebra atestada del Hub reutiliza el mismo productor.

| Modo | Familia | Resolución | Detalle de salt / sondeo |
|---|---|---|---|
| 0 | V2 | llamada a la factory `getPair(t0,t1)` — selector `0xe6a43905` | — |
| 1 | V3 / Algebra | llamada a la factory `getPool(t0,t1,fee)` — `0x1698ee82`; si falla, `poolByPair(t0,t1)` — `0xd9a641e1` | — |
| 2 | Solidly | llamada a la factory `getPool(t0,t1,stable)` — `0x79bc57d5` | — |
| 3 | Forks Solidly concentrados (Slipstream, Velodrome CL) | llamada a la factory `getPool(t0,t1,tickSpacing)` — `0x28af8d0b` | — |
| 4 | V2 CREATE2 | aritmética pura | `keccak256(packed(t0,t1))`, origen = factory |
| 5 | V3 CREATE2 | aritmética pura | `keccak256(encode(t0,t1,fee))`; `fee == 0` es el centinela de Algebra → `keccak256(encode(t0,t1))`, origen = el `poolDeployer()` de la factory (`0x3119049a`), atestado en la admisión |
| 6 | Clon Solidly CREATE2 | aritmética pura | `keccak256(packed(t0,t1,stable))`, origen = factory |
| 7 | CL CREATE2 | aritmética pura | `keccak256(encode(t0,t1,tickSpacing))`, origen = factory |
| 8 | *retirado* | liberado por la extirpación de Curve; una lápida — `MODES_VALID = 0x2FF` lo rechaza | — |
| 9 | Escaneo-derivación V4 | deriva poolIds sin hook y los prueba vivos en el PoolManager (§5.1) | rejilla ≤ 99 niveles, ≤ 40 sondeados en frío, parada en 8 pools |

Los modos 0–3 son un `staticcall` cada uno; los modos 4–7 son cero llamadas y solo requieren el init-code hash correcto; el modo 9 sirve a la única familia cuyos pools no pueden localizarse de ninguna de las dos formas.

**Un teorema, una afirmación y una prueba.** Una dirección derivada es un teorema sobre el par: dados el origen, el polinomio de salt y el init-code hash, la dirección se sigue solo por keccak, y cualquiera que la discuta puede recalcularla. Una dirección preguntada es una afirmación de una factory sobre su propio storage — y una factory puede responder con un pool de otros tokens. Desde 2026-09, un pool preguntado (modos 0–3) prueba su propio par antes de que el descubrimiento lo liste: el Hub lee su `token0()` y su `token1()` — las mismas dos lecturas que hace el Router en la costura que paga — y una discrepancia descarta al candidato, así que un pool que el ejecutor rechazaría nunca se lista, se planifica ni se clasifica. Lo que cambió esto fue la matriz de venues hostiles de §15: su primera ejecución listó un pool con el que respondió una factory admitida por un curador, el planificador lo clasificó y el ejecutor lo rechazó en la costura que paga — fondos nunca en riesgo, el par rechazado mientras el impostor ganaba la división.

**Dos sutilezas en las filas con forma de V3.** Algebra (clase Camelot) cobra una comisión dinámica, así que una comisión nunca puede formar parte de su identidad: una inscripción Algebra declara comisión cero como centinela, el modo 5 quita la comisión del salt, y el origen CREATE2 es el contrato `poolDeployer` separado que exponen las factories Algebra. Ese origen es la única entrada de la derivación que una factory aún podría dirigir tras la admisión — un proxy cambia su respuesta con su codehash intacto —, así que el Hub lo atesta una vez en `addFactory` (`factoryDeployer`) y a partir de entonces deriva desde el origen atestado; una vez renunciado el control, la atestación queda congelada. En el lado de la llamada a la factory, las factories Algebra exponen `poolByPair` en lugar de `getPool`, así que una búsqueda en modo 1 que no devuelve nada reintenta con ese selector antes de darse por vencida.

**Toda derivación pasa por una última compuerta.** El Hub descarta cualquier dirección derivada que no tenga bytecode de runtime — `extcodesize` es toda la comprobación (`hasCode`) —, así que un init-code hash erróneo, una inscripción obsoleta o una chain donde una familia nunca se desplegó no producen nada, en lugar de un venue fantasma. El bytecode en la dirección prueba existencia, no honestidad; por eso la admisión, la banda de credibilidad y los suelos siguen interponiéndose entre un pool localizado y una operación ejecutada. El mismo pool derivado bajo varias combinaciones `(fee, spacing)` se lista una sola vez.

**La frontera entre derivar y preguntar es empírica.** Los venues basados en clones, como Velodrome y Aerodrome, se quedan en llamada a la factory aunque su init-code hash sea conocido, porque su origen CREATE2 vive dentro de un desplegador interno de clones deterministas y no en la factory — la dirección no es reproducible a partir de entradas públicas, así que el protocolo pregunta. Uniswap V3 es el caso emblemático en la otra dirección: su init-code hash es una única constante en todas las chains porque en todas partes se despliega el mismo bytecode de pool, y la identidad `derived == getPool` se cumple en forks de Ethereum, Arbitrum, Optimism y Base frente a las propias factories de cada chain (§17). Deriva donde la aritmética es sólida, pregunta donde no lo es, y verifícalo todo en cualquier caso.

**El descubrimiento consume la tabla fila a fila.** Para cada una de las factories registradas, como mucho `MAX_FACTORIES = 16`, el Hub sondea cada combinación `(fee, spacing)` declarada — las filas Solidly en sus variantes estable y volátil, una fila V4 que reserva hasta ocho —, así que el peor caso del escaneo lo fija la inscripción, no el humor de la chain. La superficie de configuración está protegida en la puerta (§9): un venue estructuralmente imposible no puede almacenarse.

## §8 El Monoslot

El storage es el costo dominante de hacer cualquier cosa en la EVM: un `SLOAD` en frío cuesta 2,100 gas, y un registro que dispersara los atributos de un pool en slots separados lo pagaría de nuevo por cada atributo, por cada pool, en cada ruta evaluada. BlazePhoenix empaqueta todo el estado de enrutamiento de un pool en una sola palabra de 256 bits — el Monoslot —, que se lee una vez y se decodifica por aritmética pura.

| Bits | Campo | Ancho | Significado |
|---|---|---|---|
| 0 | `active` | 1 | el slot está vivo |
| 1–7 | reservado | 7 | bit 5 = curva estable (leída del propio `stable()` del pool en la inscripción); bit 6 = lado nativo intercambiado; bit 7 antes era el flag de puente, ya no se escribe — la bonificación de puente se lee en vivo del par (§9) |
| 8–31 | `fee` | 24 | nivel de comisión, en la unidad nativa del venue |
| 32–39 | `kind` | 8 | tipo de venue (§4) |
| 40–47 | `tier` | 8 | 0 = curado (sembrado por un operador), 2 = sin permisos (aprendido operando o reclamado) |
| 48–59 | `conc` | 12 | campo de concentración, enmascarado a `0xFFF` antes de empaquetar |
| 60–63 | `bucket` | 4 | bucket logarítmico de profundidad |
| 64–95 | `lastUpdateTs` | 32 | marca de tiempo de reloj de pared del último toque |
| 96–127 | `emaIn` | 32 | reservado para una EMA del volumen de entrada; se escribe como cero en la inscripción y el tick publicado no lo actualiza |
| 128–159 | `emaOut` | 32 | reservado para una EMA del volumen de salida; igual que arriba |
| 160–191 | `swapCount` | 32 | contador de actividad, que satura en $2^{32}-1$ |
| 192–223 | `regBlk` | 32 | bloque de inscripción |
| 224–255 | `lastBlk` | 32 | bloque del último toque |

![FIG-4 — Disposición de bits del Monoslot: una palabra de 256 bits, catorce campos con nombre, el campo de concentración enmascarado a doce bits para que nunca pueda invadir el bucket de profundidad.](https://blazephoenix.xyz/whitepaper/figs/es/fig-4.png)

La codificación es una secuencia de desplazamientos y ORs (`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 detalle es una guarda, no una comodidad: el campo de concentración se enmascara a doce bits *antes* del desplazamiento, así que nunca puede invadir el bucket de profundidad en los bits [63:60] — el empaquetado prohíbe la clase de solapamiento silencioso de campos que corrompe el estado empaquetado, en lugar de confiar en que los llamadores se mantengan dentro de rango. La decodificación es igual de mecánica: cada accesor es un desplazamiento y un truncamiento; el escritor del bucket borra sus cuatro bits antes de establecerlos; el contador de swaps se satura en lugar de dar la vuelta, así que un slot lleno nunca puede devolver a cero el historial de un pool concurrido. La marca de tiempo de reloj de pared, que el Hub estampa en cada tick y en cada inscripción, impulsa tanto el decaimiento de vitalidad de §9 como la compuerta de frescura del descubrimiento del Solver.

Fíjate en lo que la palabra no contiene: un precio. El Monoslot lleva todo lo necesario para clasificar un pool — actividad, clase de profundidad, comisión, tipo, flags, recencia — y nada que pueda confundirse con una cotización, así que la memoria del registro es estructuralmente incapaz de poner precio a una operación. Los precios se calculan de nuevo, a partir de reservas en vivo, dentro del marco que opera con ellos; la palabra empaquetada nunca hace más que decidir qué pools vale la pena consultar. El camino de actualización respeta la misma economía: un swap a través de un pool conocido reescribe la palabra una vez — contador, bucket, marcas de tiempo, un `SSTORE` sobre un `SLOAD` —, así que el costo marginal de mantener el registro al día viaja dentro de transacciones que los usuarios ya estaban enviando.

Por qué importa una sola palabra es una cuestión de multiplicación. El Solver sondea hasta `MAX_CANDIDATES = 8` venues por par en hasta tres topologías, así que una sola operación resuelta puede puntuar unas dos docenas de pools antes de mover un token. A 2,100 gas por slot en frío, una disposición de cinco slots por pool pone el precio de esa pasada de puntuación en cientos de miles de gas; el Monoslot la deja en una lectura por pool, y el `psisOf` del Hub devuelve la puntuación de cada candidato en una sola llamada en lote. El Monoslot no es una optimización aplicada al enrutamiento on-chain; es la razón misma de que el enrutamiento on-chain sea asequible.

## §9 El Registro Autorreparable y el Campo de Vitalidad Ψ

La mayoría de los agregadores conocen sus venues porque un keeper indexa pools off-chain y sube listas on-chain — un proceso en el que hay que confiar, una ventana de obsolescencia y una dependencia operativa, tres cosas que un protocolo que resuelve rutas on-chain no puede aceptar. BlazePhoenix adopta la postura opuesta: el registro aprende la liquidez operándola.

### §9.1 Aprender operando, y la prueba del par

En cada pata ejecutada, el Router llama a `recordSwap` del Hub (`onlyRouter`, `whenLive`) y le pasa el pool, su tipo, su comisión, el par, las cantidades y la profundidad que midió. El Hub rechaza primero cualquier tipo que el Router no podría haber ejecutado (`KINDS_EXECUTABLE`) — una defensa local que no depende de que el Router siga siendo lo que era en el despliegue — y después toma uno de dos caminos.

El Monoslot de un pool conocido se actualiza: el recuento de swaps sube desde su base *actualmente decaída*, se recalcula la clase de profundidad, y el bloque y la marca de tiempo de reloj avanzan — una palabra empaquetada reescrita, ninguna llamada externa. Un pool desconocido toma el camino frío. Primero debe superar la admisión (`_canInsert`, más abajo). Después debe probar la afirmación que está a punto de guardar: para cada tipo con forma de par (`KINDS_PAIR_PROOF` = V2, V3, Solidly, Algebra), el Hub lee los propios `token0()` y `token1()` del pool y exige que coincidan con el par — como mucho dos `staticcall`s, y solo en el camino frío. Todos los argumentos de `recordSwap` son calldata controlado por el llamador que viaja en la ruta del Router — pool, tipo y profundidad —, así que sin esta prueba un atacante registra un contrato que él mismo escribió, bajo un par que eligió, con una profundidad que eligió, sin tener ninguno de los dos tokens. Para los dos tipos V4, cuyo campo `pool` es un id truncado sin bytecode, la prueba es la re-derivación: se recupera el espaciado de ticks, se recalcula el id y se comprueba que coincide, solo para pools sin hook, así que un pool con hook nunca puede registrarse por esta puerta; un pool V4 nativo se compara en las dos orientaciones de `(address(0), T)`. Una discrepancia *omite* el registro y nunca revierte, porque el swap del usuario ya se ha liquidado y nunca debe fallar por una decisión del registro.

La prueba del par al registrar es nueva en septiembre de 2026, y la medición que la produjo es la de §15: se ejecutaron quince mutaciones del código fuente contra cada invariante con estado, y el mutante que eliminaba esta prueba sobrevivió a todas las campañas en la primera ejecución, porque ningún test había ofrecido nunca al Hub un pool que operara otros tokens. La campaña del Hub ahora lo hace en cada ejecución — el par invertido, un token compartido, dos ajenos — y `invariant_ActiveEntriesTradeThePair` lee el `token0`/`token1` de cada entrada activa; el mutante queda como una entrada de guarda que ese invariante mata. El descubrimiento lleva la misma prueba para los pools consultados (§7).

No hay ningún indexador que mantener en marcha ni ningún momento en que el protocolo quede ciego porque un servidor esté caído. Una puerta de operador `seedPool` precarga pares en el despliegue (nivel 0, curado) y una puerta sin permisos `claimV4` admite pools V4 sin hook bajo su propia prueba (§5.1); ambas son optimizaciones, no dependencias — un registro vacío se repara a sí mismo hasta quedar poblado mediante operaciones ordinarias.

### §9.2 La puntuación

El Campo de Vitalidad Ψ separa la calidad de la capacidad, y solo lee el Monoslot y el estado de puente en vivo del par (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 clase de profundidad $b = \min(15, \lfloor \log_{10}(d / 10^{15}) \rfloor)$ entra a través de su peso $2^{b}$: cada orden de magnitud de profundidad duplica el peso, así que la profundidad cuenta de forma sublineal — un pool profundo sale claramente favorecido, pero un pool simplemente sano no queda borrado por un gigante. Un pool de un par puente gana +25 %, porque los puentes son el tejido conectivo del grafo de liquidez; un pool concentrado gana un +5 % adicional. La bonificación de puente la lee en vivo del par un único productor (`_pairBridged`), y no un bit congelado al registrar, y dentro de un par es uniforme, así que nunca puede reordenar pools del mismo par.

**El decaimiento — un solo mecanismo.** La actividad decae con la inactividad según una única regla de tiempo de reloj (`_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

El paso es `VITALITY_DECAY_STEP_SECONDS = 24,576` — unas 6.8 horas — y treinta y dos reducciones a la mitad extinguen cualquier puntuación, así que un pool en silencio llega a cero y queda plenamente desalojable tras 786,432 s, unos 9.1 días. Un solo swap lo revive, y la reactivación es honesta: el recuento se reanuda desde lo que el decaimiento dejó en pie, nunca desde el total previo al decaimiento, así que un swap de dust tras un largo silencio no puede restaurar la posición acumulada de un pool. El puntuador mantiene separados dos ceros: un slot *muerto* — vacío, nunca actualizado o más allá del horizonte — puntúa un cero real y sale de la clasificación, mientras que un slot *vivo* cuyo recuento pequeño simplemente se perdió por redondeo bajo el desplazamiento se eleva a un mínimo de uno, así que un pool real y tocado recientemente nunca desaparece por un artefacto de redondeo. La ventana está en segundos de reloj a propósito: una ventana contada en bloques significa una duración real distinta en cada chain, y una implementación anterior que desplazaba cada 16 bloques decaía a cero unas 128× más rápido de lo documentado.

![FIG-5 — Decaimiento de la vitalidad: un recuento de 4,096 se reduce a la mitad cada 24,576 s y llega a cero tras 32 pasos; un swap el día 4 lo revive desde la base decaída, no desde el total histórico.](https://blazephoenix.xyz/whitepaper/figs/es/fig-5.png)

### §9.3 Desalojo

Cada par aloja como mucho `MAX_SLOTS = 16` pools. Cuando un decimoséptimo quiere entrar, el Hub puntúa a cada titular por su Ψ completo — profundidad, bonificaciones y todo lo demás, de modo que un recién llegado profundo puede desplazar a un ocupante poco profundo pero activo — y admite al recién llegado solo si su puntuación proyectada supera la del titular más débil por un margen (`_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

con el recién llegado puntuado de forma conservadora — vitalidad uno, su propia clase de profundidad, sin bonificación — y un recién llegado de profundidad cero rechazado de plano. El margen cierra un vector de sabotaje: con una simple regla de desplazar al más bajo, un adversario podría mantener dieciséis slots poco profundos apenas activos y dejar fuera a un competidor profundo; con la clasificación por Ψ y un margen, el pool realmente más profundo sigue ganando. El límite de la constante se declara donde se impone: el peso del recién llegado es una potencia de dos mientras que la puntuación del titular es continua, así que el margen efectivo va desde cerca del 100 % (un titular apenas por encima de uno) hasta cerca del 25 % (un titular en 51, recién llegado admisible más pequeño 64); el 25 % de la regla es también el suelo del comportamiento, y nunca se perfora. Un titular muy operado es difícil de desplazar hasta que el decaimiento hace su trabajo — dos días inactivo le cuestan siete reducciones a la mitad. Una reclamación V4 sin permisos supera el mismo margen con la liquidez medida del pool como su profundidad.

### §9.4 El descubrimiento, y su ajuste de gas

El descubrimiento es una view pública: para cualquier par, el Hub recorre su lista de factories, deriva candidatos según los modos de §7, aplica `hasCode` y la prueba del par, y el Solver fusiona el resultado con el conjunto registrado, sin duplicados por dirección, así que ninguna de las dos fuentes puede suprimir a la otra. Para acotar el gas, el Solver aplica una compuerta de frescura (`_registryFresh`): cuando al menos `MIN_FRESH_VENUES = 3` venues registrados *curados* del par se han tocado dentro de `DISCOVERY_TTL_SECONDS = 3,600`, confía en el registro y se salta el barrido de derivación. Solo las filas curadas (nivel 0) pueden avalar: una fila sin permisos la escribe quien hizo swap primero, y contarla permitía que tres pares de dust autorregistrados, mantenidos frescos con tres swaps de dust por hora, desactivaran el descubrimiento de un par y ocultaran todo venue honesto nunca registrado. La fila sin permisos sigue siendo candidata; ya no elige el camino. La compuerta es un ajuste de gas y de cobertura, nunca un parámetro de seguridad — el suelo Iron-Law y el mínimo del usuario protegen cada ejecución por obsoleto que esté el registro, porque ninguno de los dos lee el registro en absoluto.

### §9.5 La superficie de configuración

Las puertas de registro del Hub están custodiadas a la entrada. `addFactory` rechaza, antes de escribir ningún estado (`HubE(5)`): un tipo fuera de `KINDS_ROUTABLE`; un modo fuera de `MODES_VALID`; un modo CREATE2 con un init-code hash cero; un slot de salt V2 vinculado a cualquier tipo que no sea V2; un slot de clones vinculado a un tipo que no sea Solidly; un slot de salt V3 vinculado a cualquier tipo que no sea V3 o Algebra, con la exigencia de que un registro Algebra declare cada comisión como el centinela cero; y una fila V4 de derivación por escaneo vinculada a cualquier tipo que no sea V4 o con listas de comisiones y de espaciados mal emparejadas. Una factory conocida se refresca en su sitio y nunca recibe una segunda fila, así que una sola dirección no puede agotar los dieciséis puestos (`HubE(4)`); una vez renunciado el control, una factory cuyo código runtime cambió no puede rearmarse, y una fila de derivación no puede convertirse en una fila de consulta. `allowHook` fija el codehash del hook en la admisión, y un hook solo es enrutable mientras esté en la lista de permitidos *y* sin cambios (`isHookLive`); tras la renuncia, la retirada de la lista desaparece y un hook que cambió queda en pausa para siempre. Hay como mucho `MAX_BRIDGES = 3` puentes; `addBridge` es un poder de curador que sobrevive a la renuncia, `removeBridge` un poder de control que muere con ella.

### §9.6 El límite estructural

Una frase acota todo lo anterior: **Ψ puede ocultar un venue; nunca puede cotizarlo mal.** La puntuación clasifica y trunca el conjunto de candidatos — ninguna cotización, ningún suelo y ninguna asignación la leen, porque se calculan a partir de mediciones dentro de la transacción; la memoria llega a la selección de ruta solo por omisión. Crea lo que crea el registro, lo peor que puede hacer aflorar es un precio en vivo peor pero real, y el valor que ese precio puede dejar pasar está limitado por lo más estricto entre la banda de credibilidad, el suelo Iron-Law re-derivado dentro del marco y el mínimo que fijó el usuario. El principal queda fuera de alcance por construcción. El registro decide qué se ve; no tiene voto sobre qué se paga.

## §10 El enrutamiento y el Asignador de División

Un pool de producto constante pone precio con una sola regla: sus dos reservas, multiplicadas, nunca pueden disminuir. Vierte $\Delta$ con una retención de comisión $\gamma$ (una comisión de venue del 0.30 % significa $\gamma = 0.997$) y recibes $y\,\gamma\Delta / (x + \gamma\Delta)$, con tu propia entrada en el denominador: cuanto más empujas a través de un pool, peor paga cada token adicional. El slippage es aritmética, y su remedio también: dividir la misma orden entre pools del mismo par mueve menos cada pool.

**Ejemplo resuelto 2 — una división entre dos pools.** Una orden de 100 contra dos pools del mismo par, ambos al 0.30 %: el pool A tiene 1,000 por lado, el pool B tiene 4,000 por lado.

| Ruta | Aritmética | Recibes |
|---|---|---|
| Los 100 en A | $1{,}000 \times 99.7 / 1{,}099.7$ | 90.661 |
| Los 100 en B | $4{,}000 \times 99.7 / 4{,}099.7$ | 97.275 |
| 20 en A, 80 en B | $1{,}000 \times 19.94 / 1{,}019.94 \;+\; 4{,}000 \times 79.76 / 4{,}079.76$ | $19.550 + 78.201 = 97.751$ |

La división 20 / 80 es la elección del propio asignador, no una conjetura: los pesos son proporcionales a la profundidad medida respecto al candidato más profundo — A pesa $1{,}000/4{,}000 = 2{,}500$ de 10,000, B pesa 10,000 — así que A se lleva el $2{,}500 / 12{,}500 = 20$ % de la orden. La división supera al mejor pool único por 0.476 tokens, 48.9 bps de su resultado. La compuerta de división pregunta entonces si esa ganancia justifica su segunda pata: el umbral es $97.275 \times (1 + 25 / 10^{6}) = 97.278$, y 97.751 lo supera, así que la división se mantiene. Cada fila se puede comprobar con una calculadora, y el efecto se acumula: el impacto en el precio es la respuesta de cada pool a *su propia* porción, así que cada pata admisible que se añade a una ruta mueve menos cada pool.

![FIG-6 — Salida frente a la proporción enviada al pool profundo en el ejemplo de dos pools: una curva cóncava con su máximo cerca del 80 %, con el punto proporcional a la profundidad del asignador marcado.](https://blazephoenix.xyz/whitepaper/figs/es/fig-6.png)

### §10.1 Formas de ruta y presupuestos

El Solver construye tres topologías y devuelve la que maximiza (`findBestRoutePlan`, con la segunda mejor como respaldo): una ruta *directa* en un hop; una ruta *puente* a través de un intermediario; una ruta de *doble puente* a través de dos — tres hops, la forma más profunda que construye el Solver y el máximo que acepta el Router (`MAX_HOPS = 3`). Cada hop se divide en patas paralelas bajo dos presupuestos deliberadamente separados: `MAX_LEGS_PER_STAGE = 4` responde *cuántas patas caben en un hop*, `MAX_LEGS = 11` responde *cuántas caben en una ruta*, y juntos dan 4, 4 + 4 y 4 + 4 + 3 — el último hop de una ruta de tres hops recibe tres porque el techo global lo comprime, no porque alguien escribiera un caso especial. Los puentes salen de un conjunto, configurado por el operador, de como mucho tres tokens en el Hub; el Solver expande cada puente configurado y deja que el `totalOut` medido decida, operación por operación. Una ruta cuyo impacto medido supera `MAX_ROUTE_IMPACT_BPS = 9,000` no se devuelve en absoluto, y un par sin ruta admisible rechaza con `SolverE(5)` en lugar de fabricar una. El Router impone su propio tope de cinco patas por hop a cualquier ruta suministrada por el llamador (`MAX_LEGS_PER_HOP = 5`, `RouterE(3)`).

### §10.2 El embudo

Para cada par, el Solver reúne los pools registrados y, salvo que el registro esté fresco (§9.4), los descubiertos; elimina cualquier pool cuyo hook altere deltas (el proyector Ξ, aplicado aquí para que la puerta planificada nunca pueda ensamblar una ruta que el Router rechazaría); clasifica la unión por Ψ en una sola llamada agrupada y se queda con `MAX_CANDIDATES = 8` — deliberadamente más que el presupuesto de patas de cualquier hop, para que un pool profundo listado detrás de varios venues poco profundos se vea, se pondere y pueda desplazarlos en lugar de quedar sin asignación por el orden de la lista. Un empate exacto en Ψ se resuelve a favor de la comisión más baja, así que con un registro frío el embudo es función del precio y no de la historia.

Cada candidato se sondea entonces una vez, a una centésima de la orden (`amountIn / 100`, o la orden entera cuando eso se redondea hacia abajo a cero), y se capturan su tasa marginal y su profundidad en la misma llamada. Las tasas marginales separan limpiamente dos señales que las tasas a tamaño completo confunden: un pool con precio obsoleto tiene una tasa marginal errónea sea cual sea el tamaño, mientras que un pool pequeño pero sano tiene una tasa marginal correcta y solo una mala salida a tamaño completo. Para los tipos con forma de par, la profundidad declarada se limita a lo que el pool tiene físicamente del token de salida, así que un par sintético que declara $3 \times 10^{30}$ mientras tiene un token de cada lado se pondera por lo que tiene.

### §10.3 La banda de credibilidad

Antes de la asignación hay que creer a los candidatos, y la credibilidad debe anclarse en una cantidad que cueste algo falsificar. El centro de la banda es la **mediana ponderada por profundidad** de las tasas marginales de los supervivientes (`_depthWeightedMedian`): las tasas se ordenan con sus profundidades al lado, y la mediana es la tasa en la que se ha superado la mitad de la *masa de profundidad*. Su punto de ruptura es el 50 % de la masa — para moverla, un atacante debe superar en profundidad la mitad de la liquidez real del par — y las profundidades que pondera son `getReserves` para los pools de reservas, la liquidez activa para los pools concentrados y la liquidez medida para V4: cantidades que una simple donación a un pool no mueve. Esa última propiedad es la razón por la que el ancla no es el mayor saldo real: una transferencia a un pool infla `balanceOf` sin mover la reserva ni el precio, y el donante puede recuperarla, así que no es capital en riesgo — un investigador externo demostró que una donación capturaba el ancla anterior basada en el saldo, y el ancla se sustituyó. Los candidatos cuya tasa marginal se aparta del centro en más de `MEDIAN_FILTER_BPS = 500` (±5 %) se descartan antes de enviar nada; un conjunto de un solo candidato no se filtra en absoluto, y un conjunto cuyo centro se redondea a cero recurre a la mediana simple para que un hop por lo demás enrutable no muera por un artefacto de redondeo. La anchura es una elección calibrada: las curvas estables de Solidly que cotizan pares de liquid staking suelen quedar a un 20–40 % de la tasa real y se excluyen; un pool sano cuya lectura para este tamaño de operación difiere un poco del centro se admite. El carril metamórfico de §15 midió la banda desde fuera: con un pool cotizado a $10^{10}$ de su hermano, el plan paga un 35 % menos que el pool atípico por sí solo — la mediana se niega a creerlo.

### §10.4 El asignador y sus tres guardas

Los pesos son proporcionales a la profundidad medida, normalizados respecto a la mayor profundidad del conjunto (`_weights`) — una sola normalización, sin modos, porque la profundidad de cada familia está denominada en tokens (§4.2) y por tanto es comparable. La asignación proporcional es la elección robusta para un entorno adversarial: no necesita iteraciones ni un modelo de curvatura por venue, y evita el fallo catastrófico que produce una división uniforme — en un caso medido WETH/USDC, dividir 50 / 50 entre un pool de 790 WETH y un pool de dust de 0.34 WETH devolvió 1,302 USDC, mientras que concentrar en el pool profundo devolvió 1,638; la asignación ponderada por profundidad devolvió 1,638. Cuando el embudo tiene más supervivientes que el presupuesto del hop, se conservan los primeros supervivientes *por peso* que caben en el presupuesto, así que un pool profundo listado tarde desplaza a los poco profundos listados antes. Tres guardas se interponen entonces entre la asignación y la ruta en que se convierte.

- **El recorte de capacidad.** La fórmula de liquidez concentrada de un solo tick modela la liquidez del tick actual como si abarcara todos los precios, así que en un pool poco profundo puede prometer más de lo que el pool ha tenido nunca — 117× más en un caso observado. Por eso, una pata concentrada en un venue con forma de pool (tipos 1 y 6) se recorta a `MAX_CONC_DRAIN_BPS = 3,000` — el 30 % — del saldo real *medido* del token de salida del pool. El recorte no alcanza a las patas V4: el singleton guarda los tokens de todos los pools en un solo saldo, así que el Solver omite la lectura para los tipos 4 y 8 y una promesa V4 no lleva más cota física que el suelo de §11.3 — el suelo es su cota. El recorte tiene dos niveles: cuando la cotización supera todo lo que tiene el pool, la promesa es físicamente imposible, y el recorte reduce la *entrada comprometida* de la pata en la misma proporción, traslada en cascada la entrada liberada a las patas restantes y deja lo que no se pueda enrutar para devolverlo al llamador; cuando la ejecución es agresiva pero posible, solo se limita la promesa, porque se midió que recortar capital en ese caso fuerza ejecuciones parciales en pools que ejecutan bien. Las fórmulas acotadas por reservas no pueden prometer de más y no se tocan. Este recorte lee `balanceOf` deliberadamente donde la banda no lo hace: la banda hace una pregunta *relativa* que una donación puede sesgar, el recorte hace una *física* — ¿puede este pool pagar esto? — y una donación aumenta lo que el pool puede pagar realmente.
- **La compuerta de división.** Cada pata adicional cuesta gas real, y el asignador no contiene deliberadamente ningún término de precio del gas, porque comparar el gas en el token nativo con la salida en el token de la operación exigiría precisamente el oráculo de precios que el protocolo se niega a tener. El sustituto sin oráculo es un umbral: una división se mantiene solo si $\mathrm{out}_{\mathrm{split}} \ge \mathrm{out}_{\mathrm{single}} \cdot (1 + 25/10^{6})$ — `MIN_SPLIT_IMPROVEMENT_PPM = 25` —; si no, la asignación se reduce a una pata. El umbral es un número medido: el verdadero punto de equilibrio entre el gas de una pata y su mejora se midió en 3 ppm en Base (320 bytes de calldata por pata adicional a 105.5 gas por byte), y la compuerta se sitúa en unas ocho veces eso, denominada en partes por millón porque un cuarto de punto básico no existe en puntos básicos; su historia — 20 bps, luego 5, luego 0.25 — deja constancia de dos valores que nunca se habían calibrado contra una medición. Cuando la compuerta reduce una ruta, mide dos candidatos de una sola pata a tamaño completo en lugar de uno — el pool más profundo y la mejor tasa marginal — porque las tasas marginales hacen parecer mejores a los pools poco profundos, mientras que la profundidad es el indicador de la capacidad de absorber todo el tamaño; gana la mejor ejecución.
- **Seguro por medición, no por margen.** Un diseño anterior rebajaba 5 bps a cada pata de las rutas más largas en el momento de la planificación como seguro contra la deriva entre patas. Después se midió la deriva en nueve pares de Base en el mismo bloque: peor caso −2 bps, un orden de magnitud por debajo del seguro, que toda ruta honesta de varias patas pagaba en cada operación — 27× su riesgo medido. La rebaja se retiró; los suelos, impuestos sobre la salida realizada, asumen ese riesgo con coste de planificación cero. La medición se tomó en una ventana tranquila y el contraargumento consta en el código fuente: si alguna vez se restablece el conservadurismo, el lugar correcto es el suelo, que se comprueba, y no la estimación, que solo clasifica.

La ruta ensamblada lleva lo que el Router volverá a derivar: la entrada comprometida y la salida esperada de cada pata, el impacto de la ruta ponderado por participación y el suelo Φ que el Solver atesta con la misma aritmética que impone el Router (§11). El Solver también pone precio a la ejecución de cada pata según su tipo a partir de la escala `THETA_GAS` y publica el total como `estGas`, una cifra orientativa que la ruta lleva y que nada on-chain lee. La regla práctica del lector sobrevive intacta a toda esta maquinaria: compara la mejora que afirma el router con el gas que muestra tu wallet, antes de confirmar.

## §11 La capa de ejecución y el suelo Iron-Law Φ

El Solver propone; el Router dispone — de forma atómica, o no lo hace en absoluto. El Router es el único contrato que mueve fondos, no guarda ninguno entre transacciones y está construido sobre un principio: un mecanismo por cada aspecto, aplicado universalmente. No supone nada que pueda medir.

### §11.1 Cuatro puertas, un ejecutor

| Puerta | Firma (abreviada) | Autorización | Ruta |
|---|---|---|---|
| `swapExactIn` | `(route, amountIn, userMinOut, recipient, deadline)` | `approve` + `transferFrom` clásicos | desde calldata |
| `swapExactInWithPermit2` | `(route, amountIn, userMinOut, recipient, deadline, permit, signature)` | transferencia por firma Permit2 en un solo paso a través del contrato Permit2 canónico; ninguna autorización de gasto permanente al Router | desde calldata |
| `swapExactInNative` | `(route, userMinOut, recipient, deadline)` payable | valor nativo, envuelto una sola vez en la puerta; falla cerrado hasta que `weth` esté conectado (`RouterE(3)`) | desde calldata |
| `swapBestExactIn` | `(tokenIn, tokenOut, amountIn, userMinOut, recipient, deadline)` | clásica | resuelta dentro del marco por el Solver (`findBestRoutePlan`) y ejecutada después mediante una autollamada con el pagador transmitido explícitamente |

Solo `swapBestExactIn` invoca al Solver en la transacción; las otras tres toman la ruta que el llamador calculó off-chain, gratis, a través del Quoter (§12). Las cuatro convergen en un único ejecutor privado (`_execute`), así que cada comprobación descrita abajo se ejecuta en cada puerta. Una cuenta delegada EIP-7702 llama a las mismas puertas que llamaría un contrato; no existe ninguna entrada separada para ella.

Dos rechazos se ejecutan antes de que nada se mueva. Un mínimo cero se rechaza en todas las puertas — `RouterE(10)`, sin excepciones, sin indicador de compatibilidad — porque el mínimo es la única cota que el contrato no puede calcular en nombre del usuario, y una guarda que puede estar ausente no es una guarda. Y una ruta con más de tres hops, más de cinco patas en un hop o un hop cuyo token de entrada no sea la salida del hop anterior se rechaza con `RouterE(3)`: la continuidad entre hops es lo que hace conocido el saldo “ajeno” previo al swap de cada token intermedio, así que una ruta manipulada no puede nombrar como entrada un token varado y meter en el swap, escalado, todo el saldo que el Router tiene de él.

### §11.2 Medición, desde la primera instrucción

Sea cual sea la puerta usada, lo primero que hace el Router es medir lo que llegó: lee la diferencia de su propio saldo después de la extracción en lugar de fiarse de la cantidad nominal, así que un token que entrega de menos en la transferencia entra en la ruta con su tamaño real. Las líneas base nacen una sola vez: el saldo previo al swap del token de entrada (`baseIn`) y el del token de salida (`toutStart`) se toman a la entrada, y cada medición posterior es una diferencia respecto a ellas, así que los tokens que ya estaban en el contrato — dust, donaciones, restos varados de historias ajenas — quedan excluidos de lo que un swap puede pagar. Una ruta que consigue gastar por debajo de su propia línea base falla por aritmética comprobada — el resultado seguro, ya que esos fondos nunca le correspondió gastarlos a este swap.

Una ruta de varios hops es un plan, y los planes se encuentran con la realidad en cada frontera entre hops. El Router reescala las patas de cada hop respecto al saldo que *realmente* llegó — el hop 0 respecto a la entrada medida tras la extracción, limitada por lo que comprometió la ruta (para que el Router nunca meta a la fuerza una orden recortada de antemano en un pool poco profundo); cada hop posterior respecto al saldo medido del puente por encima de su línea base ajena —, y cada pata ejecuta $\mathrm{amt} = \mathrm{amountIn}_{\mathrm{leg}} \cdot \mathrm{real}/\mathrm{planned}$ (`_hopScaleImpactAndQuote`). Una pata escalada a cero se omite; la entrada no gastada y los residuos del puente se devuelven al pagador, nunca al Router. La misma pasada lee una vez el estado del pool de cada pata y produce, a partir de esa única lectura, la cantidad escalada de la pata, su impacto medido y su cotización dentro del marco — las tres cantidades que consumen los suelos —, así que las familias de reservas y concentradas no pagan ningún `staticcall` adicional por su medición.

### §11.3 Tres suelos bajo cada pata

**El suelo por pata.** La contribución medida de cada pata al saldo de salida del Router debe alcanzar `LEG_FLOOR_BPS = 8,000` — el 80 % — de su cota, o todo el swap revierte con `RouterE(5)` (`_execScaled`), así que un solo pool atacado con un sandwich o manipulado hace fallar la transacción de inmediato en lugar de esconder su pérdida dentro de un total por lo demás sano. La cota es la cotización atestada por el llamador, a prorrata de la cantidad realmente gastada — *elevada* a la cotización medida dentro del marco siempre que la atestación cubra menos de `MIN_QUOTE_COVERAGE_BPS = 5,000` de ella. Es un máximo, no un mínimo, a propósito: en un suelo, `min(claimed, measured)` con una afirmación reducida artificialmente devuelve la reducida y se relaja, que es el ataque que cierra la compuerta. La guarda actúa siempre que exista alguna base para el suelo — la atestación del llamador o la cotización dentro del marco —, así que escribir cero en una ruta ya no desactiva el propio suelo del llamador. Su límite: un venue sin cotización medible y sin atestación pasa a los suelos agregados y al mínimo del usuario, que lo acotan de todos modos.

**El suelo agregado por hop.** El suelo por pata es local mientras que la composición es global: un atacante que controle una de $L$ patas podría extraer cerca de $20\,\% \cdot (L-1)/L$ de un hop sin hacer fallar ningún suelo individual. Por eso cada hop puede perder como mucho lo que una pata atestada *media* podría perder legítimamente: $\sum \mathrm{got} + \lfloor (\mathrm{BPS} - \mathrm{LEG\_FLOOR\_BPS}) \cdot \lfloor \sum \mathrm{attested} / n \rfloor / \mathrm{BPS} \rfloor \ \ge\ \sum \mathrm{attested}$ — el margen es el 20 % de la pata atestada media; si no, `RouterE(5)`. La media y no el máximo, porque con un máximo el atacante infla su propia pata para inflar el presupuesto compartido. Con una sola pata, la regla coincide exactamente con el suelo por pata — cero rigidez nueva, por construcción.

**El suelo de ruta Φ** está en §11.5.

### §11.4 Un callback, un desbloqueo

Todo venue con forma de V3 — Uniswap V3, Algebra, los forks concentrados de Solidly, Pancake V3, Sushi V3 — usa el mismo patrón de contabilidad flash con un nombre de callback distinto: el pool llama de vuelta a mitad del swap con un delta para token0, un delta para token1 y datos arbitrarios, y exige el pago. En lugar de un callback con nombre por cada fork, el Router expone un único `fallback` que los responde a todos — la **Doctrina del Callback Único**: lee los dos deltas, identifica por sus signos qué token debe y paga exactamente esa cantidad, tomando el contexto de la pata en curso del transient storage (`TSLOT_POOL`, `TSLOT_TOKEN`, `TSLOT_AMT`). Como cualquiera puede llegar a un fallback, está protegido en tres frentes: solo paga al pool comprometido para la pata actual, se niega a actuar cuando no hay ninguna pata en curso (el contexto transitorio es cero entre swaps) y nunca libera más que el presupuesto registrado de la pata — una segunda exigencia dentro de una misma pata se rechaza con `RouterE(6)`. Un fork de V3 recién lanzado con un nombre de callback nuevo está soportado el mismo día en que se despliega.

El singleton V4 queda envuelto tras la misma interfaz de hop: `unlock`, luego el swap, `sync → settle → take`, y nuevo bloqueo, con las monedas llevadas en transient storage y la costura nativa de §5.3. Antes de que se ejecute cualquier pata V4, el Router lee la dirección del hook y rechaza la pata si sus bits declaran permisos que alteran deltas (`RouterE(9)`); el hook cribado es el hook ejecutado, porque el PoolManager canónico devuelve los datos de desbloqueo tal cual (`test/V4SievedHookIsTheExecutedHook.t.sol`). La regla de forma de ruta es el orden canónico, en toda la ruta: una vez que se ha ejecutado una pata con hook, ninguna pata sin hook se ejecuta después de ella en ese hop ni en ninguno posterior (`sawHooked`), así que un hook no puede observar una pata sin hook posterior de la misma ruta antes de que se liquide; una pata con hook puede estar en un hop anterior solo si todas las patas que vienen después también tienen hook.

**Comisión por transferencia, en las patas que pueden permitírsela.** Algunos tokens entregan de menos en cada transferencia. En las patas de producto constante, el Router lo gestiona con el patrón canónico — primero transferir, después leer las reservas del pool y su saldo en el mismo estado posterior a la transferencia, y poner precio a la salida sobre la diferencia que el pool realmente ve —, una cantidad correcta por construcción, porque es exactamente la cantidad que comprobará el propio invariante del pool. La rama se activa solo cuando un token mide de menos (`TSLOT_FOT`), y lo que cuesta activarla se declara junto con ella: el suelo de ruta derivado de la cotización se calculó sin ver el impuesto y se descarta para ese swap; el suelo del protocolo se reescala por la proporción neta medida (redondeada hacia arriba, la dirección conservadora); las cotas por pata se recotizan con la misma proporción; y el mínimo del usuario pasa a ser la restricción vinculante. Las patas concentradas no admiten comisión por transferencia por diseño — la contabilidad flash debe pagar exactamente lo que se debe —, así que un token gravado en una ruta solo V3 se rechaza de entrada con `RouterE(13)` en lugar de revertir dentro de un pool.

### §11.5 El suelo Iron-Law Φ, al completo

No toda ruta que puede ejecutarse debe hacerlo — una ruta que devuelve el 60 % del valor justo es peor que no operar —, así que bajo todo lo anterior hay un suelo que el protocolo deriva por sí mismo. Es una fracción de retención en puntos básicos (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

Tres constantes lo deciden: `FLOOR_BASE_BPS = 9,600`, `FLOOR_PER_LEG_BPS = 200`, `FLOOR_HARD_MAX_LOSS_BPS = 2,000`. El suelo empieza en el 96 % para un swap limpio de una sola pata, se afloja un punto básico por cada punto básico de impacto medido $\delta$ y 200 bps por cada pata adicional *efectiva*, y se recorta para que nunca caiga por debajo de 8,000 bps: el protocolo nunca admitirá a sabiendas una ruta que retenga menos del 80 % de su referencia, sean cuales sean las entradas. La rebaja por patas se calcula a partir de la *concentración* de la operación entre las patas — $(\sum a)^{2}/\sum a^{2}$, el número efectivo de patas, exactamente $N$ para $N$ participaciones iguales y exactamente 1 para una pata dominante —, así que una división gana aflojamiento en proporción a lo genuinamente dividida que esté, y una pata con una participación que tiende a cero gana una cantidad que tiende a cero, tanto si declara cero como un wei. Por Cauchy–Schwarz, el numerador nunca produce underflow. El término de impacto es la media ponderada por participación de los impactos medidos de las patas a lo largo de la ruta. Dos propiedades se hacen constar como parte de la definición: el término de volatilidad $\sigma_{\ln}$ existe en la firma y se pasa como cero literal en cada punto de llamada — un hook para un estimador futuro, inerte por ahora — y el impacto de la familia de reservas excluye la comisión mientras que el de la familia concentrada la incluye, cada uno en la forma que le da la especificación de su familia.

Lo que hace de Φ una imposición y no una heurística es dónde se ejecuta y sobre qué. La ruta entregada al Router lleva campos orientativos — un total cotizado, un suelo atestado — y el Router no confía en ninguno: dentro del marco de ejecución mide el impacto real de cada pata, calcula la rebaja por patas a partir de las cantidades que recibe, recalcula `floorBps` y lo aplica a la **cotización dentro del marco del último hop para la cantidad que realmente llegó a él** — una referencia denominada en el token de salida, derivada del estado del pool leído durante esta ejecución, imposible de falsificar mediante calldata. La elección de la referencia es lo que permite que la guarda llegue a dispararse: una fracción de la salida *realizada* nunca puede superar la salida realizada. Cuando el último hop no puede cotizarse dentro del marco — un hueco de liquidez en el tick actual, un centinela de comisión —, la referencia recurre a la cotización atestada del hop, que la compuerta de cobertura ya ha elevado y la medición de la comisión por transferencia ha recotizado, así que un hueco de liquidez just-in-time (JIT) en un pool poco profundo no puede desarmar el suelo; el ancla del suelo es siempre un hop que movió valor y produce la salida de la ruta. La comprobación se hace sobre la salida bruta, antes de cualquier comisión del lado de la salida. El mínimo efectivo es un máximo entre tres valores,

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

y su asimetría es lo esencial: los campos que suministra el llamador pueden endurecer la protección y nunca pueden aflojarla. El Solver atesta `singleOutFloor` con los mismos `legShaveBps` e `ironFloorBpsShv` sobre las mismas cantidades, así que, para una ruta construida por el Solver, el suelo atestado y el suelo impuesto coinciden por construcción (`test/FloorParitySolverRouter.t.sol`), y el arnés de regímenes de §15 fija los dos marcos en cada fila generada: con la comisión descontada de la entrada, el suelo impuesto queda en $[\mathrm{attested} \times (1 - \mathrm{fee}),\ \mathrm{attested}]$; con la comisión descontada de la salida, es igual al suelo atestado.

![FIG-7 — El suelo Iron-Law: retención frente al impacto medido para una sola pata, dos patas iguales y cuatro patas iguales; el recorte duro del 80 %; marcado el punto del Ejemplo resuelto 3.](https://blazephoenix.xyz/whitepaper/figs/es/fig-7.png)

**Ejemplo resuelto 3 — el suelo en una ruta de dos hops.** Una ruta A → WETH → B: el hop 0 divide 600 A hacia el pool P y 400 A hacia el pool Q; el hop 1 envía el WETH producido a través de un solo pool R. Medido en el marco: las patas del hop 0 muestran impactos de 100 y 60 bps, la pata del hop 1, 40 bps, y la media ponderada por participación a lo largo de la ruta es de 80 bps. La cotización dentro del marco del hop 1 para la cantidad de WETH que realmente llegó es de 1,000.000 B.

1. Concentración del hop 0: $(600 + 400)^{2} = 1{,}000{,}000$; $600^{2} + 400^{2} = 520{,}000$; $n_{\mathrm{eff}} = 1.923$; rebaja $= \lfloor 200 \times 480{,}000 / 520{,}000 \rfloor = \lfloor 184.6 \rfloor = 184$ bps.
2. El hop 1 tiene una sola pata: $(\sum a)^{2} - \sum a^{2} = 0$; rebaja 0. `legShv` total = 184.
3. $\mathtt{floorBps} = \max(9{,}600 - 184 - 80 - 0,\ 8{,}000) = 9{,}336$: la ruta debe retener el 93.36 % de su referencia.
4. $\mathtt{protocolFloorOut} = \lceil 1{,}000.000 \times 9{,}336 / 10{,}000 \rceil = 933.600$ B.
5. Con `userMinOut` = 990 B fijado por quien opera y `singleOutFloor` = 933.6 B atestado por el Solver, `effMin` = 990 B — la cota del propio usuario es la que obliga, como debe ser en una ruta honesta. Una entrega de 998.4 B se liquida; una entrega de 985 B se rechaza con `RouterE(5)`, y la transacción entera, con el swap de cada pool incluido, nunca ocurrió.

Si quien opera hubiera fijado `userMinOut` = 900 B, el suelo del protocolo obligaría en 933.6 B: una ejecución a 930 B, un 7 % por debajo de la referencia dentro del marco, se rechaza aunque supere la cifra del usuario.

### §11.6 La liquidación, y cómo leer una

La liquidación cierra el marco igual que se abrió: midiendo. El Router toma como salida bruta la diferencia de su saldo del token de salida respecto a la línea base de entrada (`RouterE(8)` si es cero), ejecuta la comprobación del suelo descrita arriba, cobra la comisión donde la pone el régimen (§13), transfiere el neto al destinatario y mide la diferencia de saldo *del destinatario*, impone `userMinOut` sobre esa cantidad entregada — para que un token de salida con comisión por transferencia no pueda dejar al usuario por debajo de su cota — y lee una vez el libro mayor de comisiones: una liquidación que no pagó nada al protocolo se rechaza (`RouterE(15)`), y en una ruta anclada se rechaza un segundo pago (`RouterE(16)`). Después, y solo en el camino de éxito, llama a `recordSwap` del Hub una vez por pata ejecutada (`_recordHits`) con las cantidades que el hop pudo gastar — la proporción entre lo medido y lo declarado aplicada a cada pata —, que es la única escritura externa que realiza un swap; no se escribe nada cuando el suelo rechaza.

Cada liquidación emite `ExecutionProof(user, tokenOut, quoted, realized, floorUsed, blockNumber)`: la cotización de referencia producida en el mismo marco que la ejecución, publicada como una serie on-chain que cualquiera puede volver a derivar con un `eth_call` a `findBestRoutePlan` en ese bloque. Sus campos, sin afirmar de más: `user` es el pagador, nunca `msg.sender`; `quoted` es la cotización del último hop — prueba el último hop, no la ruta; `realized` es lo que se entregó, medido en el destinatario; `floorUsed` es el suelo del protocolo que había que superar. Cada evento `Fee(token, amount, toT1, toT2)` indica el token y el reparto.

**Ejemplo resuelto 6 — leer una liquidación.** La ruta del Ejemplo 3 se liquida y emite `ExecutionProof(0xUSER, B, 1,000.000, 998.400, 933.600, N)` y un `Fee(WETH, 0.014, 0.0042, 0.0098)`.

| Cantidad | Valor | Lectura |
|---|---|---|
| `quoted` | 1,000.000 B | la cotización dentro del marco del último hop para el WETH que realmente llegó |
| `realized` | 998.400 B | entregado al destinatario y medido allí; 9,984 bps de la cotización |
| `floorUsed` | 933.600 B | `floorBps` 9,336 aplicado a la cotización; la ejecución quedó 64.8 B por encima |
| `Fee` | 0.014 WETH en un evento | el régimen anclado: 28 bps de los 5.000 WETH que produjo el hop 0, pagados en la entrada del hop 1, el 30 % a la primera tesorería; no se tomó nada de B |
| distancia a `userMinOut` | 8.4 B | la cota de 990 B del usuario dejó un 0.84 % de margen |

Lo entregado menos lo cotizado es la respuesta del mercado, no la del protocolo: la comisión salió antes, en WETH, y en esta ruta no existe ninguna detracción del lado de la salida. Un evento `Fee` denominado en el token de salida aparece solo en una ruta directa hacia una moneda puente (§13).

### §11.7 Los rechazos del Router

| Código | Significado | Dónde |
|---|---|---|
| `RouterE(1)` | llamada de control no autorizada | `onlyControl` |
| `RouterE(2)` | en pausa, o renuncia intentada estando en pausa | `whenLive`, `renounceControl` |
| `RouterE(3)` | entrada incorrecta — dirección cero, puerta nativa sin conectar, número de hops, patas por hop, discontinuidad entre hops | puntos de entrada, `_execute` |
| `RouterE(4)` | plazo vencido | puntos de entrada |
| `RouterE(5)` | salida por debajo de un suelo — por pata, agregado por hop, suelo de ruta o `userMinOut` | `_execScaled`, `_execute` |
| `RouterE(6)` | callback no autorizado — pool equivocado, ninguna pata en curso, segunda exigencia | `fallback` |
| `RouterE(7)` | reentrada | `nrEntrant` (bloqueo transitorio) |
| `RouterE(8)` | swap fallido — salida cero, tipo desconocido, comisión igual o superior a la cantidad | `_execute`, `_execScaled`, `_chargeHopFee` |
| `RouterE(9)` | el hook V4 altera deltas | precompromiso de la pata V4 |
| `RouterE(10)` | `userMinOut == 0` con `amountIn > 0` | todas las puertas |
| `RouterE(13)` | token con comisión por transferencia en una ruta solo concentrada | `_execute` |
| `RouterE(14)` | rescate no encolado o dentro de su retardo de 48 horas | `executeRescue` |
| `RouterE(15)` | liquidado sin pagar la comisión del protocolo | libro mayor de comisiones, final de `_execute` |
| `RouterE(16)` | comisión pagada dos veces en una ruta anclada | libro mayor de comisiones, final de `_execute` |

Los códigos 11 y 12 no están asignados. Una superficie de revert tan enumerable como la API es lo que permite a las matrices de regímenes de §15 juzgar cada fila con una sola regla — liquidar, o rechazar con un selector nuestro — y llamar a cualquier otra cosa una tercera vía.

## §12 Superficies de cotización, y cómo envejece una cotización

Un número llamado “tu cotización” puede venir de dos lugares, y son caminos de código distintos: el **Quoter**, un contrato de vista previa de solo lectura al que una interfaz llama antes de que firmes, y el **camino dentro del marco**, donde el Solver y el Router calculan cotizaciones dentro de la transacción en ejecución. La §4.4 dio la verdad por tipo; esta sección da las superficies y mide la brecha entre ellas.

### §12.1 Las superficies del Quoter

| Función | Devuelve | Notas |
|---|---|---|
| `previewPlan(tIn, tOut, amountIn)` | un `Preview` | el plan del Solver, empaquetado con la comisión y el margen de seguridad |
| `previewPlanWithMinOut(…, userMinOut)` | un `Preview` | lo mismo, con `canExecute` juzgado frente al mínimo del llamador |
| `previewAndEncode(…)` / `previewAndEncodeWithMinOut(…)` | un `Preview` y el calldata para `swapExactIn` | la forma en que un integrador toma una cotización — el estudio de §12.2 usa exactamente esto |
| `batchQuote(entries[])` | hasta `MAX_BATCH = 32` vistas previas | un par que no se puede planificar deja una entrada inicializada a cero |
| `previewPlanExact(tIn, tOut, amountIn)` | la ruta y una salida neta exacta | el Pase Exacto: cada pata concentrada se ejecuta en seco contra el propio swap del pool |

**Qué es una vista previa.** El struct `Preview` es orientativo por diseño — las comprobaciones vinculantes son las que el Router vuelve a derivar dentro del marco — y sus campos están documentados en el lugar de su definición: `grossOut` reproduce el total del plan; `protocolFee` es el efecto de la comisión sobre la salida, modelado por régimen exactamente como la cobra el Router (§13) — una vez en una ruta anclada, una vez por hop en una de agotamiento, redondeado hacia arriba como redondea el Router; `safetyBuffer` es el margen entre patas

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

cero con dos patas o menos, un punto básico por cada pata por encima de dos, más cinco por cada pata de producto constante cuyo precio se calcula con una comisión *supuesta* (una comisión declarada que el Core sustituyó bajo su techo), bajo un único tope compartido de diez, porque un margen sin techo deja de ser un margen y se convierte en un suelo; `netOut` es $\mathrm{grossOut} - \mathrm{protocolFee} - s$; `ironFloor` reproduce el suelo atestado; `topology` y `bridgeUsed` se derivan del número de hops y del ancla de la comisión que usará el Router — el mismo barrido del mismo productor, así que la vista previa y el ejecutor no pueden nombrar puentes distintos. La regla para el integrador cabe en una frase y es el consejo de este documento que más peso soporta: deriva tu mínimo de tu propia expectativa de precio, nunca de un campo de la vista previa.

**El Pase Exacto.** Para una vista previa exacta respecto al pool de un venue concentrado, el Quoter llama a la entrada de swap real del pool; el pool llama de vuelta exigiendo el pago; el callback del Quoter, en lugar de pagar, revierte — llevando los deltas calculados en el payload del revert. El revert deshace todo el estado, no se paga nada, y la salida decodificada es el propio swap del venue sobre el estado vivo, ejecutado y revertido; V4 se trata de forma idéntica mediante un unlock cuyo callback revierte con el delta empaquetado. Un pool que rechaza la ejecución en seco conserva la afirmación del propio plan, escalada por la cuerda que queda por debajo de ella — nunca por encima, porque la salida de un AMM es cóncava. El hop 0 se limita a lo que el plan comprometió, así que una ruta recortada por capacidad no se ejecuta en seco a un tamaño que el Router nunca gastará. Desde 2026-09-03 el escalar devuelto es neto de la comisión del protocolo, deducida una vez como la deduce la vista previa, así que el suelo respeta un mínimo derivado de él (`test/QuoterExactNetOut.t.sol`). Este es el instrumento del Quoter del lado de la vista previa; la superficie que vincula es la re-derivación dentro del marco.

### §12.2 Cómo envejece una cotización

Una cotización es una promesa sobre un bloque futuro. El estudio la toma como lo hace un integrador — `previewAndEncode` devuelve la vista previa y el calldata —, deja que el mundo se mueva (de cero a tres operaciones de otra persona a través de los mismos pools, cada una de hasta el 3 % de la reserva poco profunda, todas en la dirección del usuario, de modo que cada una de ellas le perjudica), deja pasar de cero a diez segundos y ejecuta el calldata sin cambios. 240 muestras en un universo de tres tokens con mocks de producto constante (A, B = moneda puente, C; directo al puente, dos hops a través de él, dos hops en el otro sentido), con cada resultado clasificado (`test/QuoteDelayStatistics.t.sol`):

| Deriva entre cotización y ejecución | Muestras | Liquidadas | Rechazadas por el suelo (`RouterE(5)`) | Entregado / previsto — media | mín. |
|---|---|---|---|---|---|
| ninguna | 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 |

| Retardo entre cotización y ejecución | Muestras | Liquidadas | Rechazadas (`RouterE(5)`) | Entregado / previsto — media | mín. |
|---|---|---|---|---|---|
| 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 — Cómo envejece una cotización: muestras liquidadas y rechazadas por tramo de deriva, con la razón entregado/previsto de cada muestra liquidada; las mismas 240 muestras agrupadas por retardo no muestran ninguna tendencia.](https://blazephoenix.xyz/whitepaper/figs/es/fig-8.png)

El tiempo no mueve una cotización; la deriva sí. Se afirma en cada muestra: una cotización sin deriva se liquida con cualquier retardo y entrega exactamente su previsión (64 de 64); una liquidación nunca entrega por debajo del suelo que atestó la vista previa; no hay un tercer resultado. Pasado el plazo (`deadline = now + 5`, ejecutadas en `+10`) se rechazan 20 de 20 con el código propio del plazo, `RouterE(4)`, y ninguna se liquida. Con hasta un 1 % de deriva adversa, toda cotización se sigue ejecutando, dentro del 2 % de su previsión; con 1–3 %, el suelo rechaza una de cada cuatro en lugar de ejecutarla por debajo de la salida atestada, y las ejecuciones quedan dentro del 3.5 % de la previsión — la curva del sandwich de §14 vista desde el lado de la cotización.

**Base en vivo** (`test/fork/QuoteDelayFork.t.sol`, 1,000 USDC → WETH, pools reales, el protocolo desplegado en un fork en el bloque actual): ejecutada 0, 3, 6 y 10 s después sin que nada más se moviera, la cotización entregó exactamente su previsión las cuatro veces. Con 10,000 · 50,000 · 200,000 · 1,000,000 · 5,000,000 USDC operados por delante de ella a través de la misma ruta y el calldata obsoleto ejecutado 10 s después, las cinco se liquidaron dentro del suelo, en 9,999 · 9,998 · 9,994 · 9,974 · 10,000 bps de la previsión: un millón de dólares por delante de mil le cuesta a los mil un cuarto de punto porcentual, y la operación de cinco millones se enrutó por pools que la ruta de mil dólares no toca. Las distribuciones de arriba son sobre mocks; Base en vivo da alcance en un par en un bloque, no una distribución.

### §12.3 Lo que cuesta una cotización a través de la ABI

Gas medido alrededor de la llamada externa, cuarenta tamaños en cada caso en el universo de tres tokens (`test/QuoterGasStatistics.t.sol`) y cinco tamaños en Base en vivo (`test/fork/QuoterGasFork.t.sol`):

| Mundo | media de `previewPlan` | mín. | máx. | σ | media de `previewAndEncode` | σ |
|---|---|---|---|---|---|---|
| mocks — descubrimiento: el par solo lo conoce la factory, un pool encontrado | 121,313 | 116,468 | 123,491 | 1,433 | 123,317 | 1,851 |
| mocks — registro nuevo: tres pools sembrados con precio calculado | 235,574 | 227,205 | 237,843 | 1,815 | 239,647 | 1,937 |
| Base en vivo — en frío: registro vacío, factories admitidas barridas | 1,524,221 | | | | 1,525,341 | |
| Base en vivo — en caliente: nuevo tras una ejecución, dos pools registrados | 1,414,633 | | | | 1,416,037 | |
| Base en vivo — de nuevo en frío tras el TTL de descubrimiento | 1,359,925 | | | | 1,361,582 | |

`batchQuote` de diez entradas sobre mocks: 2,055,390 gas, 205,539 por entrada. El mock con registro nuevo cuesta más que el descubrimiento porque calcula el precio de tres pools donde el descubrimiento encontró uno; la comparación honesta es la medida en vivo, donde el barrido de descubrimiento supone cerca del 7 % de una cotización — y una cotización es una llamada view que un integrador nunca paga on-chain.

## §13 Política de comisiones: los dos regímenes y la Regla del Excedente

*En palabras simples:* el protocolo cobra 0.28 % una vez, sobre una cantidad medida, en una moneda que la tesorería quiere conservar, y cuenta que lo hizo. *Con precisión:* `PROTOCOL_FEE_BPS = 28` es una constante de compilación del Core sin setter en ninguna parte del bytecode desplegado; la comisión es `mulDivUp(base, 28, 10,000)` — redondeada hacia arriba, así que una comisión cero es inalcanzable para cualquier base distinta de cero —, repartida en `TREASURY1_SHARE = 3,000` bps para la primera tesorería y el resto para la segunda (calculado como `fee − t1`, así que las dos siempre suman la comisión, sin dust), con las tesorerías fijadas en la construcción y congelables para siempre por la Puerta de un Solo Sentido. Ninguna clave puede subir la tasa en perjuicio de quien opera y ninguna clave puede bajarla para un integrador favorecido: la cuestión de la comisión se respondió en tiempo de compilación y se retiró de la lista de cosas que puede hacer una clave.

### §13.1 Dónde se cobra la comisión: dos regímenes, ambos con nombre

La regla desde 2026-08-22 es una frase: **cobrar una vez, sobre la primera moneda puente que tiene el Router.** El Router recorre la ruta buscando el primer hop cuyo token de entrada es un puente registrado (`hub.isBridgeToken`, en orden de hops) — ese hop es `feeHop` — y cobra 28 bps de la entrada *medida* de ese hop: en el hop 0, la extracción medida, limitada por lo que la ruta comprometió; en un hop posterior, el saldo de la moneda puente del Router por encima de su línea base de saldo ajeno, la cantidad que ya resiste a la comisión por transferencia y a los saldos extraviados. Después, el Router no cobra nada más en esa ruta. Una ruta directa cuyo destino es en sí una moneda puente (TOKEN → WETH) cobra en cambio sobre la salida, después de la comprobación del suelo — el suelo valida la calidad del swap, la parte del protocolo sale de la cantidad ya validada, y el mínimo del usuario se compara con lo que realmente recibe. Este es el **régimen anclado**. Por qué sobre el puente y no sobre el destino: los puentes son WETH y USDC, y la tesorería recibe una moneda líquida que quiere conservar en lugar de dust de cualquier token de cola larga que sea el destino. Por qué sobre la entrada del hop 1 y no sobre la salida del hop 0: son el mismo token y la misma cantidad, pero la entrada del hop 1 se mide con el saldo real, que es la medición que el Router ya hace.

La regla tiene un segundo régimen que la frase no nombró hasta 2026-09-05. Una ruta construida a mano a través de pools que el registro no guardaría, sin ninguna moneda puente como entrada de ningún hop, nunca encuentra un `feeHop`; el predicado que cobra es entonces verdadero para *cada* hop, y cada hop paga 28 bps sobre su propia entrada medida. Este es el **régimen de agotamiento**, y es una regla deliberada, no un descuido: cobrar una ruta así una sola vez, en el hop 0, se probó dentro de la suite en esa fecha y reabrió la vía de escape que la política de agotamiento existe para cerrar — un primer hop sin valor lleva el punto de cobro de la comisión hasta el dust y el hop real no paga nada (`test/FeeEscapeViaJunkPrefix.t.sol`); cinco tests fijados rechazaron el cambio dentro de la misma ejecución. No existe ningún índice en el que insertar dust que escape de todos los hops, que es lo que significa “inmunidad por agotamiento”. El Solver construye toda ruta multi-hop a través de puentes registrados, así que una ruta construida por el Solver siempre paga exactamente una vez; el régimen de agotamiento es lo que encuentra un llamador cuando enruta esquivando el registro, y el Quoter lo modela (`_pack` cobra `hops.length` veces cuando ninguna entrada de hop es un puente, y una vez en caso contrario), así que la vista previa y la entrega coinciden en ambos regímenes (`test/ExhaustionRegimePreviewParity.t.sol`).

![FIG-9 — Los dos regímenes de comisión tal como los decide el Router: recorrer los hops buscando la primera entrada en moneda puente; las rutas ancladas pagan una vez en ese hop (o sobre la salida de una ruta directa hacia una moneda puente); las rutas sin entrada en puente pagan una vez por hop; el libro mayor cuenta y rechaza cero, y rechaza dos en una ruta anclada.](https://blazephoenix.xyz/whitepaper/figs/es/fig-9.png)

### §13.2 El compromiso tiene un solo productor, y el contrato cuenta

Dos sellos acompañan a los regímenes. La suma de las entradas declaradas de las patas de un hop — la base de la comisión en el hop 0 y el denominador de la escala — la calcula una sola función, `_legSum`; antes de 2026-09-05 cada cálculo sumaba las patas en su propio bucle, dos productores de un mismo número. Y el libro mayor de la comisión: `_payFee`, la única función que transfiere la comisión, cuenta en un slot transitorio (`TSLOT_FEE`); cada liquidación lee el slot una vez, rechaza una entrega que no pagó nada (`RouterE(15)`) y, en una ruta anclada, un segundo pago (`RouterE(16)`), y después lo limpia. Las dos comprobaciones del libro mayor son *cinturones*: de forma aislada ningún test puede hacerlas saltar, porque el predicado que tienen delante no deja ningún camino que liquide sin pagar o que pague dos veces en una ruta anclada. Son el contrato rechazando en tiempo de ejecución lo que los tests rechazan en tiempo de revisión, en caminos que todavía no existen; figuran como cinturones, no se cuentan como cubiertos en ninguna parte y están ausentes de la guarda de mutaciones, que solo admite mutantes que elimina un test con nombre.

### §13.3 La Regla del Excedente

La **Regla del Excedente** es el argumento de alineación del diseño de la comisión, enunciado tal como lo implementa el código: la parte del protocolo es de 28 bps de una base medida, una vez en una ruta anclada y una vez por hop en una de agotamiento, y no hay ningún término en ninguna parte de la liquidación que escale con la diferencia entre lo que se cotizó y lo que se entregó. Donde la base es una entrada — toda ruta multi-hop y toda ruta directa cuyo destino no es una moneda puente —, la salida no se toca: todo lo que los pools entregan por encima de la cotización llega íntegro al destinatario, y la comisión no puede subir porque una ejecución resultara favorable. Donde la base es la salida — una ruta directa hacia una moneda puente —, la comisión es de 28 bps de la entrega bruta, excedente incluido, y nada más. El eje en el que un agregador siente más tentación de quedarse con una tajada es la brecha entre la cotización y la ejecución; este diseño no da a esa brecha ningún término de comisión, así que la tasa anunciada es la tasa verdadera y la única. La base nunca se lee del calldata: `route.totalOut` llega al Router como la afirmación de un extraño y nada lo lee (`test_FeeBase_IgnoresLiedAboutTotalOut`).

**Ejemplo resuelto 5 — los dos regímenes en una ruta.** Los puentes son {WETH, USDC}; quien opera vende 10,000 A.

*Anclada, dos hops.* A → WETH → B. El hop 0 no se cobra (su entrada, A, no es un puente). Los pools del hop 0 pagan 5.000 WETH, medidos como el saldo de WETH del Router por encima de su línea base de saldo ajeno. `feeHop` = 1: la comisión es $\lceil 5.000 \times 28 / 10{,}000 \rceil = 0.014$ WETH, repartida en 0.0042 para la primera tesorería y 0.0098 para la segunda, y el hop 1 gasta 4.986 WETH en B. Un evento `Fee`; el libro mayor marca 1 y queda satisfecho; la vista previa dedujo 28 bps una vez.

*Anclada, directa hacia un puente.* A → WETH en un hop. No se cobra nada sobre la entrada. Los pools entregan 5.000 WETH brutos; el suelo se comprueba sobre 5.000; después se paga la comisión del lado de la salida, $\lceil 5.000 \times 0.0028 \rceil = 0.014$ WETH, y se entregan 4.986 WETH; `userMinOut` se compara con 4.986. Un evento `Fee`, denominado en el token de salida.

*Agotamiento, dos hops.* A → C → B, donde ninguno de A, C o B es un puente — una ruta que ningún Solver construye. El hop 0 paga $\lceil 10{,}000 \times 0.0028 \rceil = 28$ A y envía 9,972 A a sus pools, que pagan 2,000 C; el hop 1 paga $\lceil 2{,}000 \times 0.0028 \rceil = 5.6$ C y envía 1,994.4 C a B. Dos eventos `Fee`; el libro mayor marca 2 sin `feeHop`, así que `RouterE(16)` no se aplica; la tasa efectiva es $1 - 0.9972^{2} = 55.9$ bps, y la vista previa lo dijo.

### §13.4 La comisión, medida desde fuera del Router

`test/FeeSeals.t.sol` no lee ninguno de los números del Router: el token de la comisión sale de la regla y de la lista de puentes; la base, de los deltas de saldo de los pools (lo que salió del Router hacia el hop de la comisión, o lo que pagaron los pools del hop anterior) o de los del destinatario; la comisión, de los deltas de las tesorerías; el recuento, de los eventos `Fee` — sobre cada forma que acepta el Router (de uno a tres hops; el puente en ninguna posición, o en la primera, la intermedia o la última; una o dos patas por hop), con cantidades sometidas a fuzz de $10^{12}$ a $10^{21}$ wei, 2,000 ejecuciones, ningún fallo, y el Router sin retener nada después de cada liquidación.

| Forma de la ruta | Régimen | Dónde cae la comisión | Medido |
|---|---|---|---|
| A → B (ninguno es un puente) | agotamiento, un hop | la entrada del hop 0, una vez | comisión = ⌈28 bps × entrada⌉, un evento |
| W → A (puente de entrada) | anclado | la entrada del hop 0 | igual |
| A → W (puente de salida, directa) | anclado, lado de la salida | la salida | comisión = ⌈28 bps × salida bruta⌉, entregado = bruto − comisión |
| A → C → B (sin puente) | agotamiento, dos hops | la entrada medida de cada hop | dos eventos |
| W → A → B | anclado en el hop 0 | la entrada del hop 0 | un evento |
| A → W → B | anclado en el hop 1 | la moneda puente que produjo el hop 0 | un evento, ⌈28 bps × lo que pagaron los pools del hop 0⌉ |
| A → C → W (puente solo como salida de dos hops) | agotamiento | la entrada de cada hop | dos eventos |
| A → C → D → B · A → W → C → B · A → C → W → B | como arriba | como arriba | 3 · 1 · 1 eventos |

**Con qué frecuencia los tests de la comisión detectan un defecto** se midió en lugar de suponerse (`docs/assurance/fee-seal-detection.json`): cada mutante de la comisión — el predicado del régimen desplazado en uno u otro sentido, el productor del compromiso, las dos comprobaciones del libro mayor y los tres mutantes de la comisión del estudio de invariantes — se ejecutó con veinte semillas de fuzz por cada test con fuzz y una vez por cada test determinista; la tasa de detección se informa con un intervalo de Wilson del 95 % y, donde no se escapó nada, con la cota de la regla del tres sobre la probabilidad de no detección.

| Mutante | Fuzz de FeeSeals | Campaña del Router (2 hops, 2 patas) | Matriz de cobertura t = 2 | Escape por prefijo basura | Paridad de la vista previa en agotamiento |
|---|---|---|---|---|---|
| el agotamiento cobra solo el hop 0 | 20/20 [0.84, 1.00] | 20/20 [0.84, 1.00] | no | sí | sí |
| el agotamiento omite el hop 0 | 20/20 [0.84, 1.00] | 20/20 [0.84, 1.00] | no | sí | sí |
| el compromiso solo cuenta la primera pata | 20/20 [0.84, 1.00] | 20/20 [0.84, 1.00] | no | no | no |
| comisión duplicada | 20/20 [0.84, 1.00] | 20/20 [0.84, 1.00] | sí | sí | sí |
| la comisión del lado de la entrada nunca se cobra | 20/20 [0.84, 1.00] | 20/20 [0.84, 1.00] | no | sí | sí |
| comisión cobrada en ambos lados | 20/20 [0.84, 1.00] | 20/20 [0.84, 1.00] | no | sí | sí |
| cinturón: la liquidación sin comisión ya no se rechaza | 0/20 [0.00, 0.16] | 0/20 [0.00, 0.16] | no | no | no |
| cinturón: el pago doble anclado ya no se rechaza | 0/20 [0.00, 0.16] | 0/20 [0.00, 0.16] | no | no | no |

![FIG-10 — Tasa de detección de cada mutante de la comisión por cada test, como mapa de calor: eliminaciones sobre ejecuciones, con el intervalo de Wilson; los dos cinturones se ven como las filas que nada ve.](https://blazephoenix.xyz/whitepaper/figs/es/fig-10.png)

Lee una fila de lado a lado: qué tests ven este defecto. Lee una columna de arriba abajo: qué puede ver un test y qué no. La primera medición encontró la campaña del Router ciega ante el mutante de agotamiento que exime al hop 0 y ante el productor del compromiso, porque solo construía rutas directas de una pata; la campaña se amplió ese mismo día para recorrer dos hops y dividir el hop 0 entre dos pools, con una moneda puente en el universo para que una sola campaña abarque todos los regímenes, y ahora ambos se ven 20 de 20. La matriz de cobertura solo ve la comisión duplicada, porque sus filas siempre cruzan un puente. Veinte semillas con 20 de 20 acotan la probabilidad de no detección de una sola campaña en el 15 %, y por eso la guarda de mutaciones ejecuta tanto el test con nombre como la campaña.

Resumiendo la sección en la forma que usa este documento — afirmación, punto de imposición, límite: nadie puede mover la tasa ni el reparto, porque ninguno de los dos tiene setter en el bytecode desplegado; un llamador no puede reducir la comisión declarando una cotización a la baja, porque la base es un saldo medido y nada lee `route.totalOut`; un llamador no puede esquivar la comisión con un prefijo sin valor, porque una ruta sin entrada en puente paga en cada hop; y una liquidación no puede quedar sin pagar ni pagar dos veces en una ruta anclada, porque el Router cuenta — con el límite de que las dos comprobaciones del propio contador son cinturones que ningún test puede hacer saltar.

## §14 Modelo de seguridad

*En palabras simples:* el Router no guarda nada entre transacciones, cada swap es todo o nada, y cada clase de ataque que este repositorio ha considerado tiene como respuesta una comprobación con nombre o se nombra como fuera de alcance. *Con precisión:* el catálogo de amenazas es `docs/assurance/threats.json`, 23 clases extraídas del historial público de exploits; cada una nombra el símbolo de guarda que la rechaza, y un script de CI hace fallar la compilación si el símbolo sale de `src/` o el test con nombre sale de `test/`. La cobertura de un catálogo es un suelo de lo que se ha considerado, nunca un techo de lo que existe.

### §14.1 Los ataques y las comprobaciones que los rechazan

| Clase (id) | La comprobación que la rechaza | Dónde se impone | Estado | El test que muestra que la comprobación puede fallar |
|---|---|---|---|---|
| Reentrada (DASP-1) | cerrojo transitorio `nrEntrant` en cada puerta que mueve valor; la superficie de callback autentica a su llamador | Router `nrEntrant` → `RouterE(7)`; `fallback` → `RouterE(6)` | bloqueada | `invariant_reentrancyBlocked` (`test/RouterAdversarialV4FromV1.t.sol`) |
| Control de acceso (DASP-2) | cuatro roles; toda puerta privilegiada tiene compuerta; el nivel de control, renunciable para siempre | Hub `onlyAdmin` / `onlyControl` → `HubE(1)`; Router `onlyControl` → `RouterE(1)` | bloqueada | `test/HubAllowHookAfterRenounce.t.sol`, `test/ControlEvents.t.sol` |
| Desbordamiento/subdesbordamiento aritmético (DASP-3) | aritmética comprobada en todo el código; cada bloque `unchecked` lleva una cota escrita; la representabilidad de las curvas difíciles, probada en el límite (`MAX_SQRT_PRICE_MINUS_ONE`) | Core | bloqueada | `test/BoundaryEquality.t.sol`, `test/CompilerPanicSurface.t.sol` |
| Denegación de servicio por iteración no acotada (DASP-5) | toda dimensión de la ruta acotada: `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` (slots) | bloqueada | `test/RouteHopCeiling.t.sol` |
| Dependencia del orden de las transacciones / ataques sandwich (DASP-7) | el suelo Iron-Law, recortado de forma rígida en `FLOOR_HARD_MAX_LOSS_BPS = 2,000`, impuesto con independencia del mínimo del llamador, que solo puede endurecerlo | Core `ironFloorBpsShv`; Router `_execute` → `RouterE(5)` | bloqueada (acotada, §14.3) | `test/regime/SandwichCurve.t.sol`; `invariant_DeliveredNeverBelowTheProtocolFloor` |
| Reentrada entre funciones a través de un venue (SWC-107) | el mismo cerrojo transitorio abarca todo el swap, callbacks de los pools incluidos | Router `nrEntrant` | bloqueada | `test/V4LockedRegionReentrancy.t.sol` |
| Manipulación del oráculo de precios (SOK-ORACLE) | no hay fuente de precios: la cotización de referencia se produce en el marco de ejecución a partir de los venues operados, y el suelo se deriva de ella (`ironFloorBps`) | Core; Router `_execute` | bloqueada | `test_Parity_SplitRoute_AttestedFloorEqualsEnforcedFloor` (`test/FloorParitySolverRouter.t.sol`) |
| Cantidad aportada por el llamador usada en lugar de una medida (SOK-FEE-BASE) | la base de la comisión es un saldo medido (`_chargeHopFee`); nada lee `route.totalOut` | Router `_chargeHopFee` | bloqueada | `test_FeeBase_IgnoresLiedAboutTotalOut` (`test/BlazePhoenixRouter.t.sol`) |
| Tokens con comisión por transferencia y rebasing (SOK-FOT) | toda cantidad que importa es un delta de saldo medido (`balanceOf`), nunca una cantidad de transferencia supuesta | Router, cada costura | bloqueada | `test/FotFloorReprice.t.sol`, `test/PartialFotAtPrePulledDoors.t.sol` |
| Venue hostil admitido en el registro (SOK-VENUE-ADMISSION) | la admisión es exclusiva del curador, una fila por dirección; el origen de la derivación se atesta en la admisión (`factoryDeployer`) y se congela tras la renuncia | Hub `addFactory`, `_probe` | bloqueada | `test_C4_FirstPin_AfterRenounce_MustNotAttestTheLiveAnswer` (`test/T19ReadmissionEdge.t.sol`) |
| Venue admitido cuya lógica cambia detrás de un proxy (SOK-PROXY-VENUE) | la readmisión tras la renuncia se rechaza cuando el runtime cambió (`factoryCodehash`); donde un proxy puede cambiar su respuesta sin cambiar su código, se fija en su lugar la respuesta | Hub `addFactory` → `HubE(1)` | bloqueada | `test/FactoryCodehashPin.t.sol`, `test/RenouncedFactoryRearm.t.sol` |
| Fila de venue convertida de derivada a consultada tras la osificación (SOK-MODE-FLIP) | una fila de derivación no puede convertirse en una fila de consulta una vez `controlRenounced`; endurecer en la otra dirección sigue permitido | Hub `addFactory` | bloqueada | `test_C4_S3_DeriveRowMustNotBecomeAnAskRowAfterRenounce` (`test/T19ReadmissionEdge.t.sol`) |
| Métrica publicada que refleja la declaración del llamador (SOK-VOLUME) | al registro se le entrega la cantidad que el hop pudo gastar, a partir de la razón entre lo medido y lo declarado (`hopScale`) | Router `_recordHits` | bloqueada | `test_INV_F4_VolumeInEqualsTheMeasuredPoolDelta` (`test/VolumeEventFidelity.t.sol`) |
| Vista previa que no predice la ejecución (SOK-QUOTE-EXEC) | vista previa y entrega afirmadas iguales en ambos regímenes de comisión (`previewRoute`, `_pack`) | Quoter; Router | bloqueada | `test_INV_F2_PreviewPredictsDeliveryWithNoBridge` (`test/ExhaustionRegimePreviewParity.t.sol`) |
| Derivación determinista desviada hacia la dirección de un atacante (SOK-V4-DERIVE) | la preimagen de CREATE2 tiene un solo productor (`create2Address`); el id de un pool se deriva de su propia clave; `hasCode` y la prueba de vida descartan lo que no se resuelve | Core; Hub `_probe`, `_scanV4` | bloqueada | `test/ConditionAdequacyCore.t.sol`, `test/KindIsDerivedNotDeclared.t.sol` |
| Hook malicioso en un venue con hook (SOK-HOOK) | tres capas, §14.2: bits inmutables de la dirección, lista de permitidos con fijación del codehash (`hookAllowed`, `isHookLive`), el proyector Ξ | Router → `RouterE(9)`; Hub `allowHook` → `HubE(8)`; Solver `_topKPools` | bloqueada | `test/V4SievedHookIsTheExecutedHook.t.sol`, `test/SeedPoolHookMustBeAllowed.t.sol`, `test/RenouncedHookRearm.t.sol` |
| Agotamiento del registro por admisión repetida (SOK-GRIEF-SEATS) | una fila por dirección; un alta repetida se refresca en su sitio en lugar de consumir una plaza (`MAX_FACTORIES`) | Hub `addFactory` → `HubE(4)` | bloqueada | `test/RenouncedFactoryRearm.t.sol` |
| Llamada externa arbitraria o sumidero de transfer-from (SOK-ARBITRARY-CALL) | el ejecutor nunca acepta un destino de llamada aportado por el llamador; la interacción con el venue se despacha desde un conjunto cerrado de formas seleccionadas por tipo (`_execScaled`), y un tipo desconocido revierte | Router `_execScaled` → `RouterE(8)` | bloqueada | `test/regime/HostileVenueMatrix.t.sol` |
| Repetición de firmas en la aprobación delegada (SOK-SIG-REPLAY) | la aprobación delegada la gestiona el Permit2 canónico (`permitTransferFrom`); el sistema no tiene ningún esquema de firma propio | Router `swapExactInWithPermit2` | bloqueada | `test/RouterPermit2OneStep.t.sol` |
| Captura de la gobernanza (SOK-GOV) | no hay gobernanza: ni voto con token, ni timelock, ni proxy; los únicos poderes privilegiados son la admisión de venues y la pausa, ambos renunciables | — | fuera de alcance | — |
| Actualización maliciosa (SOK-UPGRADE) | los contratos son inmutables; no hay proxy ni slot de implementación | — | fuera de alcance | — |
| Compromiso de un puente o venue externo (SOK-BRIDGE-EXT) | los venues de terceros se componen, no se controlan; su fallo queda acotado aquí por el suelo y el mínimo del llamador, y no se afirma nada sobre su seguridad interna | — | fuera de alcance | — |
| Compromiso de la clave privada de un usuario (SOK-KEY) | fuera del perímetro de confianza; se registra porque los datos publicados de incidentes le atribuyen una gran parte de las pérdidas | — | fuera de alcance | — |

Diecinueve de las veintitrés clases tienen como respuesta una guarda con nombre; cuatro están fuera de alcance, con el motivo registrado. El catálogo se contrasta desde la otra dirección con un cierre de activos (`assets.json`): seis cosas que el sistema guarda o decide — fondos en tránsito, asignación delegada, la comisión del protocolo, la integridad del registro, saldos varados, el gas del llamador — cruzadas con cinco formas en que algo se puede perder, treinta celdas, cada una de las cuales se resuelve en una clase de amenaza, una justificación de fuera de alcance o un *abierto* explícito. Diecinueve celdas están cubiertas, tres están fuera de alcance, cinco no son aplicables, y tres están abiertas y se publican como tales: el sabotaje sobre la denominación de la comisión, dejar dust varado para que la recuperación no sea rentable, y un llamador que se encuentra con uno de tres pánicos alcanzables del compilador en lugar de un rechazo con nombre. Ninguna de las celdas abiertas es un camino hacia los fondos de nadie más.

### §14.2 Hooks hostiles: tres capas, la más barata primero

Un hook es código arbitrario que se ejecuta dentro de la liquidación que está realizando el Router, y la defensa ingenua — preguntarle qué va a hacer — no es ninguna defensa, porque un hook hostil miente o reentra. En su lugar se alzan tres capas independientes, dispuestas de modo que la más barata y más certera se ejecute primero; acotan la alteración de los deltas, y el único residuo al que no llegan — un hook sin deltas que sobrescribe su comisión dinámica (§5.4) — queda acotado en su lugar por Φ y por el mínimo del llamador.

**Capa 1 — los bits de permiso viven en la dirección, y la dirección no puede mentir.** V4 codifica los permisos de un hook en los 14 bits bajos de su propia dirección de contrato; esos bits quedan fijados en el despliegue por el salt de CREATE2, y el propio PoolManager comprueba esos mismos bits antes de invocar el hook, así que un hook no puede tener un permiso que su dirección no declare. Exactamente dos bits permiten a un hook modificar la contabilidad del swap, y el Router los enmascara con un único AND antes de que se mueva ningún token (`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

Ninguna llamada a código no confiable, nada que engañar, y la evasión requeriría una dirección cuyos bits contradigan la propia imposición del gestor. Un proxy puede sustituir cada línea de su lógica y aun así no ganar ningún bit que su dirección nunca tuvo — por eso este rechazo está a distancia de proyección cero (§15.9), mientras que la fijación del codehash que hay debajo no lo está.

**Capa 2 — una lista de permitidos, cerrada por defecto, con el código fijado.** Un hook con bits limpios todavía puede revertir de forma maliciosa para sabotear o quemar gas, así que un pool V4 solo es enrutable si el curador ha incluido su hook en la lista de permitidos, y el Hub fija el codehash del hook en la admisión: un hook cuyo runtime cambia después queda pausado automáticamente sin desalojo (`isHookLive` en false) y solo se reanuda si se readmite — un poder que, tras la renuncia, se deniega para cualquier hook cuyo código haya cambiado. El límite de la fijación se declara en el propio sitio: vincula el código de runtime, así que detecta un redespliegue o una mutación directa y no detecta un proxy delegado cuya implementación se cambia mientras su propio runtime sigue idéntico byte a byte, porque la EVM no da a un contrato ninguna forma de leer el storage de otro. Admitir un hook actualizable es un juicio humano que esta fijación no puede sustituir. Las capas que no dependen de ella se mantienen igualmente.

**Capa 3 — la ruta ni siquiera se muestra.** El proyector Ξ elimina del conjunto de candidatos del Solver, antes de clasificar, cualquier pool cuyo hook altere los deltas (`_topKPools`), y el canal de lectura del Hub solo sirve hooks que están activos; una ruta que toca un hook inadmisible no participa en la maximización. Una regla sobre la forma de la ruta lo completa: orden canónico, en toda la ruta — ninguna pata sin hook se ejecuta nunca después de que se haya ejecutado un hook (§11.4).

![FIG-11 — Tres capas contra hooks hostiles, la más barata primero: la máscara de bits de la dirección (ninguna llamada a código no confiable), la lista de permitidos con la fijación del codehash (cerrada por defecto), el proyector Ξ (ruta no representable). Cada capa responde a una capacidad distinta; el residuo — un hook limpio y en la lista que revierte para malgastar gas — se nombra.](https://blazephoenix.xyz/whitepaper/figs/es/fig-11.png)

Derrotar la pila requiere un hook que no lleve bits de delta, siga en la lista de permitidos sin cambios y aun así subvierta la liquidación — una combinación que la apelación de la Capa 1 a la propia imposición inmutable de V4 descarta en lo que respecta a la alteración de deltas; el residuo de la sobrescritura de la comisión de §5.4 persiste, y el suelo lo acota. El residuo se declara en la tabla: un hook en la lista de permitidos y sin deltas todavía puede revertir a mitad del swap y malgastar el gas del intento. Y se nombra la única palanca del tamiz: el hook tamizado vincula el swap ejecutado porque el PoolManager canónico reproduce literalmente los datos del unlock y solo llama de vuelta a quien llamó al unlock; `setV4Manager` del Hub, un poder de control, es la única dirección en la que descansa esa suposición, y un gestor deshonesto hace colapsar la salida medida, de modo que la propia cota del llamador rechaza el swap (campaña `HostileV4Manager`) — acotado, no ausente.

### §14.3 Acotado, no eliminado: obsolescencia, orden y la curva del sandwich

Una cotización es una afirmación sobre el pasado, y la operación ocurre en el futuro. La puerta de resolución dentro del marco cierra por completo la mitad de esa brecha que corresponde al operador — la ruta se deriva dentro de la transacción que la ejecuta, a partir de las reservas tal como están en ese bloque. Lo que queda es la mitad propia de la chain: el pool puede moverse entre el momento en que firmas y el momento en que se incluye tu transacción. Nadie puede impedirlo desde dentro de un contrato, porque el orden se decide antes de que se ejecute el código; un protocolo que afirma haber eliminado el front-running o bien ha sacado tu operación de la cola pública, confiando en quien la tenga ahora, o bien describe algo más estrecho de lo que parece. Lo que hace este protocolo es acotarlo, dos veces y de forma independiente: tu mínimo limita el daño al número que elegiste, y el suelo Iron-Law — re-derivado por el Router a partir del impacto medido y de la cotización dentro del marco del último hop, no de nada que haya aportado el atacante o la ruta — lo limita de nuevo por cuenta propia del protocolo.

La cota se mide desde el lado del atacante (`test/regime/SandwichCurve.t.sol`): la ruta y el suelo de la víctima se fijan en el momento de la cotización, como en una transacción pendiente; el atacante opera una fracción de la profundidad del pool por delante de la víctima, la víctima ejecuta, el atacante opera en sentido inverso. El venue es un par de producto constante — la forma que usa todo modelo de sandwich —, así que la curva es una propiedad del suelo. En una operación del 1 % de la profundidad (10,000 frente a 1,000,000 por lado):

| El atacante mueve | Víctima | Pérdida de la víctima frente a la cotización | Ida y vuelta del atacante |
|---|---|---|---|
| 0.1 % de la profundidad | se liquida | 0.47 % | +13.9 |
| 0.5 % | se liquida | 1.26 % | +68.9 |
| 1 % | se liquida | 2.22 % | +136.7 |
| 2 % | se liquida | 4.12 % | +268.7 |
| 3 % y más allá | **rechazada** | 0 | −174.5 … −544.9 |

![FIG-12 — La curva del sandwich desde el lado del atacante: la pérdida de la víctima frente a la cotización y la ida y vuelta del atacante, por fracción de la profundidad movida por delante de la víctima; el borde de rechazo, cerca del 3 %, cerrado hacia arriba.](https://blazephoenix.xyz/whitepaper/figs/es/fig-12.png)

La garantía afirmada en cada punto: una víctima liquidada nunca recibe menos que el suelo atestado en el momento de la cotización, así que la pérdida está acotada por la distancia entre la cotización atestada y el suelo atestado; y la región de rechazo está cerrada hacia arriba — pasado el borde, toda manipulación mayor se rechaza y el atacante se queda con el precio que movió. La cifra que vale la pena citar es la última fila liquidada: el suelo limita lo que un sandwich puede sacar de una operación del 1 % a cerca del 2.7 % de ella, y convierte la operación del atacante en una pérdida en el momento en que sacaría más. La ruta dentro del marco añade un disuasivo más discreto: no hay ningún camino publicado de antemano que un searcher pueda estudiar, porque el camino no existe hasta el bloque que lo ejecuta. Un mínimo amplio es una invitación; uno ajustado es el control más eficaz de esta sección que está en tus manos.

### §14.4 Venues hostiles

Las patologías de los venues tienen su propia matriz (§15.5): diez comportamientos anómalos — un pool que no paga nada, que paga la mitad, que devuelve una bomba de returndata de 64 KiB, que quema todo el gas en la lectura o en el swap, un token cuyo `decimals()` nunca retorna, un callback de pago disparado dos veces, un pool que reentra en el Router antes de pagar, un `slot0()` que revierte, una factory que responde con un pool de otros tokens — cruzados con la puerta de calldata y la puerta de resolución, 20 de 20 celdas que liquidan con la cantidad entregada igual al delta de saldo y nada retenido en el Router, o que rechazan con un selector nuestro. Un swap que quema todo el gas reenviado es la única celda que ningún llamador puede decidir en nombre del contrato al que llama; la transacción revierte entera y el saldo del usuario queda intacto. Toda lectura protegida que el Core hace a un venue está limitada a `GAS_CAP = 100,000` de gas, así que un venue que quema gas en una *lectura* cuesta una cantidad acotada y se descarta, sin ser fatal.

### §14.5 El front end hostil

En todos los agregadores construidos hasta hoy, la interfaz propone el mínimo, y una descuidada lo propone mal — ningún contrato puede saber lo que tú habrías elegido. Lo que un contrato sí puede hacer es que la propuesta sea demostrable: cada swap emite `ExecutionProof` — la cotización usada, la cantidad entregada, el suelo aplicado — de forma permanente, on-chain, así que la calidad de ejecución de cualquier interfaz es un registro público, transacción a transacción, que cualquiera puede auditar, tú incluido. Una interfaz que propone sistemáticamente malos mínimos escribe su propia acusación en el registro de eventos. La defensa que completa el mecanismo es un hábito: mira el mínimo antes de firmar; es el único campo que es enteramente tuyo.

### §14.6 Lo que una clave hostil puede y no puede hacer

Antes de la renuncia existen claves administrativas, y la forma honesta de describirlas es por capacidad, suponiendo un titular hostil; la enumeración completa de los poderes está en §16.4.

**Lo que puede hacer una clave hostil del Router:** redirigir el destino de la comisión (`setTreasuries`); pausar los swaps (`setPaused`); reapuntar los contratos WETH y Permit2 a los que llaman la puerta nativa y la puerta Permit2 (`setWeth`, `setPermit2`), lo que hace que esas dos puertas sean solo tan fiables como las direcciones establecidas; encolar y, 48 horas después, ejecutar un rescate de tokens varados en el Router (`queueRescue`, `executeRescue`, `RESCUE_DELAY = 48 hours`, anunciado on-chain por `RescueQueued`). **Lo que no puede hacer:** mover la tasa de la comisión, el reparto ni los suelos, porque son constantes de compilación sin setters; tomar el principal, porque no hay proxy, ni ruta de actualización, ni `selfdestruct`, ni aprobación permanente de fondos de usuarios al Router, y el Router no guarda nada en reposo — las funciones que lo harían no están desactivadas, están ausentes.

**Lo que puede hacer una clave hostil del Hub:** listar un venue o un hook (`addFactory`, `allowHook`), sembrar pools (`seedPool`, a través de una plaza de operador), pausar el aprendizaje del registro (`setPaused`), reapuntar la vista que el Hub tiene del Router, el Solver y el Quoter (`setRoles`), y reapuntar el gestor de V4 (`setV4Manager`, la única palanca del tamiz). **Lo que no puede hacer:** poner un precio erróneo a una ejecución, porque el registro solo decide lo que se ve (§9.6) — lo más afilado que alcanza un listado hostil es la brecha entre una cotización y *tu mínimo*; el principal está fuera de su alcance, tu tolerancia de slippage no, lo cual es una razón más para que el mínimo lo fijes tú, y de forma ajustada.

### §14.7 La Puerta de un Solo Sentido

La autoridad administrativa termina de forma irreversible, en el bytecode, no por promesa. Tanto el Hub como el Router exponen `renounceControl`, y cada uno se niega a ser llamado mientras está en pausa (`RouterE(2)`, `HubE(2)`): un protocolo pausado y luego renunciado sería un estado terminal que nadie quiere y del que nadie puede salir, y pausar vale la pena precisamente porque se conserva el control para actuar. La bandera de renuncia se escribe a `true` en exactamente un punto de cada contrato y a `false` en ninguno, así que ninguna función del bytecode desplegado puede reabrir la puerta — la diferencia entre “no lo haremos” y “no podemos”.

Lo que se cierra y lo que queda abierto se enumera, no se resume. En el Router muere todo lo que está bajo `onlyControl`: `setAdmin`, `setTreasuries`, `setPermit2`, `setWeth`, `setPaused`, la vía de rescate — las tesorerías, el cableado y la bandera de pausa se congelan para siempre en sus valores actuales, y el Router sigue ejecutando swaps con esa configuración fija. En el Hub muere el **nivel de control**: `setRoles`, `setOperator`, `setPaused`, `setV4Manager`, `removeBridge` y la *retirada de la lista* de hooks. El **nivel de curador** sobrevive, y solo hace crecer el registro: `addFactory` (que rechaza, tras la renuncia, cualquier factory cuyo runtime haya cambiado o cualquier fila de derivación que pasaría a ser una fila de consulta), `addBridge` (solo añadir, como máximo tres, idempotente), `allowHook(h, true)` (que rechaza un hook listado cuyo código haya cambiado); las plazas de operador existentes conservan `seedPool` y `addV4`, y no se puede conceder ninguna plaza nueva. Un listado malicioso tras la renuncia no puede drenar nada: los pools se validan en la cotización y en la ejecución y quedan acotados por el suelo y el mínimo del llamador. El registro sigue aprendiendo al operar. Quien repita “renunciado” sobre este protocolo debería poder decir qué puertas siguen abiertas — admisión, puentes, listado de hooks, siembra curada — y cuáles se cierran: todo poder que redirige o congela.

### §14.8 Lo que este modelo te pide

El protocolo cierra lo que un contrato puede cerrar: falsificación de direcciones, suplantación de callbacks, falsificación de la contabilidad, hooks hostiles, aritmética. Acota lo que ningún contrato puede eliminar: la obsolescencia y el orden, mediante tu mínimo y el suelo. Y publica pruebas contra lo que ni siquiera puede acotar: un front end que propone malos mínimos en tu nombre. Todas las filas convergen en el mismo campo. Fija tu mínimo tú mismo, deliberadamente, cada vez — es la única protección de esta sección cuya calidad no depende de la competencia de nadie salvo la tuya.

## §15 El aparato de verificación, medido

*En palabras simples:* cada número de esta sección se publica junto al denominador contra el que se midió, se recalcula desde un checkout limpio mediante un script o un comando `forge` identificado (Apéndice C), y va seguido de lo que no establece. *Con precisión:* el árbol medido es `main` en `8949a9d` y la rama que se convirtió en él, en el perfil de release (`optimizer_runs = 300`, `via_ir`), 2026-09-05.

### §15.1 La suite en cifras

| Qué | Recuento | Cómo se cuenta |
|---|---|---|
| tests declarados | 1,609 funciones `test*` / `invariant*` / `check*` en 236 archivos `.t.sol` | `grep` sobre `test/` |
| en verde en el perfil de release, excluidas las suites de fork | 1,319 superados · 0 fallidos · 1 omitido, de 1,320 | `forge test --no-match-path 'test/fork/**'` |
| en verde sobre liquidez en vivo | 119 de 119 en las 39 suites del carril de fork, cinco chains; más los tres tests sobre Base en vivo de §12 | `forge test --match-path 'test/fork/**'` con un RPC de archivo |
| invariantes con estado | 43 funciones `invariant_*` en 14 campañas | `grep -rc 'function invariant_' test` |
| mutantes seleccionados, todos eliminados | 268 de 268 | `.github/scripts/mutants.py`; `check_targets.py` |
| matrices de cobertura de régimen | fuerza 2: 63 filas, todos los 258 pares · fuerza 3: 168 filas, todos los 1,636 tríos · de 5,184 combinaciones | `covering_array.py --check` |
| tamaños entregados (bytes de runtime; límite de EIP-170 24,576; compuerta del proyecto 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 cadena de evidencia y la guarda de mutantes seleccionados

Una propiedad vale tanto como el camino más corto desde una amenaza hasta algo que fallaría si la propiedad se rompiera: `threat → property → guard → test → mutant`. Una guarda se nombra por su símbolo, un test por su nombre, un mutante por la línea exacta que altera; si cualquiera de los tres sale del árbol, el build falla. Cada test de regresión de la suite se ha visto fallar una vez, por la razón que le da nombre — una corrección llega con el test que estaba en rojo contra el código sin ella.

La guarda de mutación contiene 268 mutantes escritos a mano, cada uno de los cuales empareja una línea exacta del código fuente con el único test que debe fallar en cuanto esa línea se altera — una guarda borrada, una comparación invertida en la cota que decide un rechazo, una autorización ampliada, un código de error intercambiado con el de su vecino. Tres propiedades la convierten en una guarda y no en una puntuación: el test emparejado se ejecuta primero en verde sobre el árbol sin mutar; una mutación que el optimizador suprime — bytecode de runtime idéntico — se informa como *inerte*, nunca como eliminada; y una comprobación estática de un segundo verifica que cada mutante sigue apuntando exactamente a una línea. 203 de 203 están eliminados. La cifra es adecuación frente a *este* registro: una puntuación saturada es un suelo, no un techo.

### §15.3 Mutantes dirigidos a los invariantes

Hasta el 2026-09-05, ninguno de los mutantes seleccionados nombraba un invariante con estado como el test que debe caer ante él. Las 43 funciones `invariant_*` de las 14 campañas estaban en verde, y nada había preguntado si alguna de ellas podía ponerse en rojo: una campaña cuyo handler nunca alcanza el estado que protege una propiedad certifica esa propiedad sobre un universo vacío, y desde fuera parece idéntica a una que sí lo alcanza. La medición es la de la guarda, apuntada a las campañas: alterar una guarda en el código fuente, ejecutar cada invariante con una semilla, registrar cuáles lo detectan (`docs/assurance/invariant-mutants.json`).

| Mutante | Qué quita | Detectado por |
|---|---|---|
| la transferencia final paga un wei menos | no retener nada | `RouterHoldsNothing`, `routerHoldsNothing`, `HoldsNothingBeyondTheSeed` |
| la comisión del protocolo se duplica | el techo de la comisión | `FeeNeverExceedsProtocolMax` |
| la comisión del lado de entrada nunca se cobra | el suelo de la comisión | `FeeNeverEscapes`, `DeliveredNeverBelowUserMinOut` |
| la comisión se cobra en ambos lados | una comisión, un lado | `FeeIsChargedOnExactlyOneSide` |
| el 30 % de cada comisión va a una dirección muerta | la conservación | `conservationA/B/C/H`, `TokenConservation`, `LedgerConservationA` |
| la guarda pata-par admite una pata en el par equivocado | hops homogéneos | `DivergentLegNeverSettles`, `HoldsNothingBeyondTheSeed`, `StrandedMoneyIsNeverSwept` |
| `whenLive` ya no comprueba `paused` | la pausa | `PausedRouterNeverSettles` |
| el bloqueo de reentrada ya no rechaza | el bloqueo | `reentrancyBlocked` |
| el suelo del protocolo se reduce a la mitad | el suelo | `DeliveredNeverBelowTheProtocolFloor` — cerrado el 2026-09-05 |
| `MAX_SLOTS` pasa a ser 17 | la cota del registro | `neverExceedsMaxSlots` |
| se quita la prueba del par al registrar | la autenticidad del par | `ActiveEntriesTradeThePair` — cerrado el 2026-09-05 |
| el barrido del residuo de entrada ignora su línea base | fondos varados | `StrandedMoneyIsNeverSwept`, `HoldsNothingBeyondTheSeed` |
| el barrido del residuo de puente ignora su línea base | fondos varados | el mismo par — cerrado el 2026-09-05 |
| la medición de la salida ignora su línea base | fondos varados | el mismo par |
| la comprobación de `userMinOut` posterior a la comisión se reduce a la mitad | — | sobrevive, por diseño: un centinela de regresión inalcanzable en universos de campaña sin token de salida con comisión por transferencia; dos mutantes unitarios lo vigilan |

![FIG-13 — Mutantes dirigidos a los invariantes: quince mutaciones del código fuente frente a cuarenta y tres invariantes con estado, los invariantes que se pusieron en rojo por mutante, los tres cerrados el 2026-09-05 dando a una campaña la acción que le faltaba, y el único centinela que sobrevive por diseño.](https://blazephoenix.xyz/whitepaper/figs/es/fig-13.png)

Once de quince se detectaron en la primera medición; veinte nombres distintos de invariantes se pusieron en rojo al menos una vez. Tres de los cuatro supervivientes se cerraron el mismo día dando a una campaña la acción que le faltaba — pools ajenos ofrecidos bajo el par, una ruta de dos hops sobre un saldo intermedio presembrado, una ballena que mueve el pool un 0–8 % en contra de una ruta cotizada antes de que se ejecute la ruta obsoleta — y cada uno se verificó en rojo sin su guarda; dos guardas sin vigilante de ningún tipo era el número que esta medición existía para publicar, y ahora es cero. Catorce de quince se detectan; los catorce son entradas de guarda emparejadas con el invariante que cae ante ellas. Una suite en verde con una guarda sin vigilar dentro fue la forma de las dos regresiones documentadas en esta base de código; la medición que encuentra una así se ejecuta ahora sobre cada guarda que las campañas reivindican.

### §15.4 Matrices de cobertura de régimen

Los criterios de cobertura indexan el código; la mutación indexa un fallo inyectado; ninguno indexa el estado que un *fixture* fija antes de la llamada. Se enumeran diez factores de ese tipo — familia de venue (V2, V3, Solidly), hops (1–3), patas por hop (1–2), si el token de entrada es un puente registrado, si lo es el intermedio, forma de la comisión por transferencia (ninguna, solo al retirar, en cada transferencia), los decimales del token de entrada (18, 6), la puerta (ruta en calldata, resolución en la transacción, Permit2), el control (activo, renunciado) y si el par está lleno — 5,184 combinaciones. `covering_array.py` genera matrices en las que cada par (fuerza 2) o cada trío (fuerza 3) de valores de factores aparece en al menos una fila, cada fila un fixture a través de un único arnés (`test/regime/RegimeHarness.sol`) con una única aserción: el swap se liquida, con la cantidad entregada igual a la variación del saldo del destinatario, como mínimo el suelo que emitió el Router y nada restante en el Router — o se rechaza con un selector nuestro. Un panic, un selector ajeno, una entrega insuficiente o un saldo varado es una tercera vía y hace fallar la fila.

| Fuerza | Filas | Tuplas contenidas | Liquidadas | Rechazadas, nuestras | No construibles | Tercera vía |
|---|---|---|---|---|---|---|
| 2 | 63 | 258 pares | 53 | 4 | 6 | 0 |
| 3 | 168 | 1,636 tríos | 158 | 10 — `SolverE(5)` ×7, `RouterE(13)` ×3 | 0 | 0 |

![FIG-14 — Matrices de cobertura de régimen: resultados de cada fila generada con fuerza 2 (63 filas) y fuerza 3 (168 filas) — liquidada, rechazada con un selector nuestro, no construible, tercera vía — sobre 5,184 combinaciones de diez factores.](https://blazephoenix.xyz/whitepaper/figs/es/fig-14.png)

Los rechazos son el planificador sin ninguna ruta con puente que construir y el ejecutor que declina un token gravado en una ruta solo concentrada; las seis filas de fuerza 2 que no se pueden construir (el token gravado solo al retirar no tiene forma de seis decimales) se publican por nombre y cuentan en el denominador. Las familias que no están en la matriz — V4, V4 nativo, Algebra, la puerta nativa, hooks — se declaran en `regimes-covering.json`. La primera ejecución de la matriz hizo explícito un marco que los tests de paridad solo habían fijado por un lado: el suelo que impone el Router es igual al suelo atestado cuando la comisión se descuenta de la salida, y queda dentro de $[\mathrm{attested} \times (1 - \mathrm{fee}), \mathrm{attested}]$ cuando se descuenta de la entrada — ambos marcos se afirman ahora en cada fila. El archivo generado se comprueba contra su generador en CI, así que la matriz no puede derivar respecto de los factores que dice cubrir.

### §15.5 La matriz de venues hostiles

Las patologías de token tenían sus tests; las patologías de venue no tenían ninguno en una matriz. `test/regime/HostileVenues.sol` contiene un mal comportamiento por venue y `HostileVenueMatrix.t.sol` cruza cada uno con la puerta de calldata y la puerta de resolución bajo la regla de la matriz de cobertura:

| Venue | Puerta de calldata | Puerta de resolución |
|---|---|---|
| no paga nada / paga la mitad | rechazado `RouterE(5)` | rechazado `RouterE(5)` |
| bomba de returndata en la lectura de reservas | se liquida | se liquida |
| la lectura de reservas quema todo el gas | rechazado `RouterE(8)` | el planificador nunca lo selecciona |
| el swap quema todo el gas | toda la transacción revierte, saldo intacto | toda la transacción revierte, saldo intacto |
| `decimals()` quema todo el gas | se liquida | se liquida |
| callback de pago disparado dos veces | rechazado `RouterE(6)` | rechazado `RouterE(6)` |
| reentra en el Router antes de pagar | se liquida; el swap anidado nunca se ejecutó | se liquida; el swap anidado nunca se ejecutó |
| `slot0()` revierte | se liquida con la cotización atestada | el planificador nunca lo selecciona |
| la factory responde con un pool de otros tokens | nunca se lista | nunca se lista |

Veinte de veinte celdas: se liquidan o rechazan con un selector nuestro, nunca una tercera vía. La última fila es lo que cambió la primera ejecución de la matriz — el descubrimiento listaba un pool con el que respondía una factory admitida por un curador, y el ejecutor lo rechazaba en la costura que paga; ahora un pool consultado prueba su propio par antes de que el descubrimiento lo liste (§7).

### §15.6 Oráculos canónicos

Cada mock de la suite cotiza con las propias fórmulas del Core, así que un defecto en una fórmula es invisible para todos los tests de paridad que las usan: el oráculo es el objeto. `test/regime/CanonicalOracles.t.sol` contiene tres implementaciones escritas a partir de los invariantes publicados de los venues — producto constante con la comisión en la entrada, el paso de precio en raíz cuadrada de un solo tick de V3 con el propio redondeo del pool en contra de quien opera, la curva estable de Solidly resuelta por el método de Newton — y somete el Core a fuzzing contra ellas, 5,000 ejecuciones cada una, afirmando primero la dirección (el Core nunca promete más de lo que entrega la matemática del venue) y después el ajuste.

| Familia | Dirección | Ajuste medido |
|---|---|---|
| Uniswap V2 | el Core nunca supera la especificación | exacto al wei |
| Solidly estable | el Core nunca supera la curva en más que el último paso del propio solver | dentro de 4 wei |
| Uniswap V3 | el Core nunca supera la especificación en más de una unidad en el último lugar (ulp) del precio en raíz cuadrada, que vale $L / 2^{96}$ wei | por debajo de un wei para todo pool con $L < 2^{96}$ |

La cota de V3 se expresa en la cantidad que la causa — el pool redondea su nuevo precio en contra de quien opera y el Core lo redondea una vez, así que ambos pueden diferir en una unidad de $\sqrt{P}$ — y la aserción es la cota, no la muestra (13 wei en una salida de $6.5 \times 10^{34}$ con $L = 10^{30}$). Los oráculos están escritos a partir de las especificaciones, no del bytecode de los venues; ese es su límite.

### §15.7 Relaciones metamórficas

Un oráculo escrito a partir de la misma fórmula no puede atrapar la fórmula. Una relación metamórfica pregunta en cambio cómo debe *moverse* la salida cuando se mueve la entrada, y la propia curva del venue responde a eso sin una implementación de referencia. `test/CoreMetamorphicRelations.t.sol` contiene catorce relaciones sobre la matemática de cotización del Core y `test/RouteMetamorphicRelations.t.sol` cinco sobre el plan del Solver en un universo de dos pools con un Hub real y un Solver real:

| Nivel | Relación | Qué dice |
|---|---|---|
| Core, V2 · V3 · estable | MR1 monótona | más entrada, nunca menos salida |
| Core, V2 · V3 · estable | MR2 subaditiva | dividir una orden en el mismo pool nunca gana |
| Core, V2 · estable | MR3 sin ida y vuelta | ida, y luego vuelta sobre las reservas actualizadas, nunca devuelve más de lo que entró |
| Core, V2 · V3 · estable | MR4 equivarianza de escala | escalar juntos la orden y el pool escala la salida, salvo redondeo |
| Core, V2 · V3 | MR5 monótona en la comisión | una comisión más alta nunca paga más |
| Core, V3 | MR6 simetría de dirección | a precio 1.0 las dos ramas de `outV3` coinciden dentro de dos ulps |
| Core, Solidly | MR7 identidad | la rama volátil *es* `outV2`, un solo productor |
| plan | MR-R1 | la división nunca paga menos de lo que pagaría el mejor pool único |
| plan | MR-R2 | monótona en `amountIn` |
| plan | MR-R3 | añadir un pool dentro de la banda de precios nunca rebaja el plan |
| plan | MR-R4 | el orden de registro mueve el plan como mucho una unidad de peso de la división |
| plan | MR-R5 | el suelo atestado nunca supera la salida esperada |

Tres de las cotas se midieron antes de escribirse, y el número de la aserción es el mecanismo, no la muestra. MR6: con $L = 8.5 \times 10^{37}$ y 1,374 wei de entrada, una rama cotizó 1,073,741,823 wei — exactamente $L / 2^{96}$, un ulp — mientras la otra cotizó cero, con su paso redondeado a nada, fallando cerrado; la relación establece dos ulps, uno por rama. MR-R1 y MR-R3: con un pool cuyo precio estaba a $10^{10}$ del otro, el plan pagó un 35 % menos que el pool atípico por sí solo — la mediana ponderada por profundidad negándose a creerlo — así que las relaciones se cumplen sobre los pools que admite la banda, y el dominio mantiene los dos precios dentro de ±4 %. MR-R4: un pool cuyo peso de profundidad se redondea a la baja hasta el mínimo se conserva como pata de dust cuando se registra primero y se descarta cuando se registra segundo, lo que mueve el plan en $3 \times 10^{-7}$ de la salida — que la división conserve o descarte un pool de peso mínimo depende de su posición, y eso queda ahora escrito donde se encontró.

### §15.8 N-versión: el mismo código fuente bajo otra generación de código

La suite ejecuta un binario — el que emiten los ajustes de release, y `profile_parity.py` mantiene la suite en esos ajustes. Cualquier otro ajuste del optimizador es un programa distinto compilado a partir del mismo código fuente, y el compilador es un componente. `test/nversion/` compila la matemática de cotización del Core con dos perfiles más — `optimizer_runs` 1 y 20,000, todo lo demás idéntico — y `NVersionCore.t.sol` despliega los tres binarios uno junto a otro y afirma la igualdad sobre entradas generadas por fuzzing: `outV2`, `outV3`, `outSolidly`, `outSolidlyStable`, `mulDiv`, `mulDivUp`, 3,000 ejecuciones cada una sobre todo el dominio de entrada. El carril afirma primero que el bytecode alternativo *difiere* del entregado, de modo que no puede pasar comparando un programa consigo mismo, y un artefacto ausente es un fallo, no una omisión.

**Los tres binarios coinciden.** El hallazgo no fue el compilador. La primera ejecución del carril sobre todo el dominio hizo revertir la *referencia*: la comprobación de ajuste de la curva estable dividía `max · WAD / b`, y esa misma división se desbordaba con un pool que tenía menos de una unidad de un token; un segundo escape — una reserva de entrada de ballena frente a una reserva de salida de dust — superaba la comprobación del invariante y desbordaba la derivada. Ninguno era un número erróneo; ambos eran reverts dentro de una cotización, y `universalQuote` es un `DELEGATECALL` de biblioteca, así que un pool registrado que tenía un wei del token de salida deshacía el plan del Solver para su par. La reparación (§6) hace la pregunta en cuyo lugar estaba la división y entró como test escrito primero en rojo; una guarda adicional — un tope al iterado de Newton — se escribió, resultó inobservable para cualquier test una vez que se cumplen las dos comprobaciones, y se eliminó en lugar de arrastrarla. Su clase es la que este repositorio nombra primero — fallar abierto — encontrada por un instrumento que no la buscaba.

### §15.9 Distancia de proyección

Cada defecto confirmado en este repositorio, interno o reportado, tiene una sola forma: una comprobación observa un objeto mientras un objeto *distinto* decide el comportamiento. Eso hace que la propiedad interesante de un rechazo no sea su complejidad sino su distancia — lo lejos que está lo que lee de lo que decide. `projection_distance.py` clasifica el predicado que protege cada `revert` de los cinco contratos: una constante, un immutable, una variación de saldo o una cantidad calculada dentro del marco a partir de uno de ellos está a distancia 0; un campo de la ruta del llamador, o una consulta al storage mutable de otro contrato, está a distancia 1; una propiedad de un contrato que puede delegar su comportamiento no está acotada. Sobre 99 puntos de rechazo: **86 a distancia 0, 11 a distancia 1, 2 no acotados.** Los dos puntos no acotados son ambos fijaciones `EXTCODEHASH` — el código de un hook admitido y el de una factory admitida — y no son simétricos: para los hooks, el residuo lo cierran los bits de dirección que la fijación no lleva (§14.2); para las factories no hay equivalente, y el residuo queda acotado por la admisión de pools, el suelo por pata y el mínimo del usuario, así que el daño es degradación de la ruta y no drenaje, documentado en el propio punto. El límite del cribado: lee un predicado y no puede ver que un valor se restringió tres marcos más arriba; un campo declarado por el llamador que también es el selector del pool está a distancia 0 solo porque una asignación los vincula, y esa asignación la fija `test/V4SievedHookIsTheExecutedHook.t.sol`.

### §15.10 Independencia de la evidencia, el registro de cantidades compartidas y el artefacto

Para cada guarda portante, el aparato registra cuántas confirmaciones *independientes* existen — por fuente, oráculo, herramienta, entorno y metodología — y reporta el mínimo, porque una cadena de evidencia falla por su eslabón más débil; las propiedades portantes se confirman por al menos dos vías que no comparten ninguna hipótesis. El registro de cantidades compartidas (`SHARED_QUANTITIES.md`) enumera cada cantidad con más de un productor o consumidor, establece la *pregunta* que responde y califica lo que vincula las copias — `SINGLE`, `PINNED`, `WEAK`, `OPEN`, `UNVERIFIED` — con una regla de CI que degrada una fila cuyo test no alcanza lo que dice fijar; cada cantidad con dos productores del registro relacional (7 de 7) está atada por un test con nombre. Y se lee el artefacto compilado, no solo el código fuente: opcodes que nunca deben aparecer (`SELFDESTRUCT`, `CALLCODE`, `ORIGIN`); los opcodes transitorios aún presentes en el ejecutor, con lo que el bloqueo de reentrada no se ha vuelto, en silencio, storage persistente; cada función declarada presente en el despachador; tres de los cinco contratos sin emitir ninguna escritura de storage; el 88.3 % del flujo de instrucciones con la forma entregada demostrado como ejecutado por una cota inferior sólida, verificada contra un contrato de verdad fundamental que el instrumento debe clasificar correctamente antes de imprimir; y, para un swap V2 honesto grabado y reproducido en el Router de release con el opcode comprobado en cada paso, se ejecutan 4,382 instrucciones del Router y 407 del Hub, cero discrepancias — el 28.2 % y el 2.5 % de las dos secciones de código, siendo el complemento código que ningún escenario grabado ejecutó, no código muerto.

### §15.11 Lo que nada de esto establece

- **Ninguna probabilidad de corrección.** La literatura sobre la validación de una fiabilidad ultraalta es explícita en que las pruebas no pueden producir una; aquí no se declara ninguna.
- **La adecuación de mutación es adecuación frente a este registro.** Está seleccionado a mano; una puntuación saturada es un suelo.
- **La cobertura de amenazas y la de regímenes son suelos de lo que se ha considerado.** Las clases y los factores que nadie ha nombrado quedan fuera del denominador por construcción.
- **Las distribuciones de §12 son sobre mocks.** Base en vivo da alcance, no una distribución.
- **Las tasas de detección son por test, con 256 ejecuciones de fuzz por semilla.** Un test que no detecta con ese presupuesto puede detectar con uno mayor.
- **Este repositorio es V2, y V2 no está desplegado** (§19).

Cada instrumento de arriba se calibró antes de publicar su número: se ejecutó primero contra un defecto ya conocido por otra vía y se corrigió donde no lo detectó — un comparador de texto que leía documentación como comportamiento porque buscaba coincidencias de texto dentro de comentarios, una regex de cobertura que no contemplaba un bloque de opciones de llamada, una búsqueda de contrapartes cuya lista de patrones estaba vacía. Un instrumento no vale nada hasta que encuentra la instancia ya conocida; un veredicto sin búsqueda es vacuidad; y todo lo que decide el compilador se mide, nunca se estima.

No conocemos ninguna otra base de código DeFi pública que publique una guarda de mutación seleccionada y con nombre, con tests que eliminan cada mutante y reporte de mutaciones inertes; un registro de cantidades compartidas con una regla de CI que lo degrada; mutantes dirigidos a sus invariantes con estado con el superviviente publicado; una matriz de cobertura de fuerza 3 sobre sus factores de régimen bajo una sola aserción; pruebas N-versión de su matemática de cotización; una curva medida de cómo envejecen sus cotizaciones; o la tasa de detección de sus propios mutantes de comisión con un intervalo de confianza. Los artefactos están en el repositorio; si encuentras una instancia previa, la afirmación es falsa y nos gustaría saberlo.

## §16 Arquitectura y tamaños

*En palabras simples:* cinco contratos, uno de los cuales es matemática pura importada por los otros cuatro, y ninguna pieza de matemática se define dos veces. *Con precisión:* el Core es una biblioteca sin estado, sin storage, sin propietario y sin ruta de actualización; Hub, Solver, Router y Quoter lo importan y ninguno redefine una primitiva, así que una sola auditoría de la matemática cubre a todos los consumidores.

### §16.1 Cinco contratos: qué contiene cada uno, a qué puede llamar cada uno

| Contrato | Contiene | Puede llamar a | Escribe estado persistente | Tamaño (bytes de runtime) |
|---|---|---|---|---|
| **Core** (biblioteca) | nada — funciones pure y view: Ω, 𝒟, Φ, Ψ, el códec del Monoslot, transferencias seguras, `mulDiv` | pools y tokens, solo lectura, cada `staticcall` protegida con un tope de `GAS_CAP = 100,000` | nunca — ni `tstore` ni `sstore` | 6,442 |
| **Hub** | el registro (un Monoslot por pool, ≤ 16 por par), filas de factory (≤ 16), puentes (≤ 3), lista de permitidos de hooks con fijaciones de codehash, entradas V4, roles; con espacio de nombres ERC-7201 | pools y factories, solo lectura, a través del Core; el PoolManager de V4 mediante `extsload` | en `recordSwap` (solo el Router), en la admisión y en la configuración | 23,648 |
| **Solver** | nada — solo `view` | el Hub (lecturas del registro, descubrimiento), pools a través del Core | nunca | 19,686 |
| **Router** | admin, tesorerías, `permit2`, `weth`, `paused`, `controlRenounced`, cola de rescate; **ningún saldo de tokens en reposo** | el token de entrada, los pools de la ruta, el token de salida, el Hub (`isBridgeToken`, `isHookLive`, `recordSwap`), el Solver (solo `swapBestExactIn`, `view`), WETH y el PoolManager para V4, Permit2 para su puerta, las tesorerías, el destinatario — nunca un destino aportado por el llamador | su propia configuración; la única escritura externa por swap es `Hub.recordSwap` en el camino de éxito | 23,781 |
| **Quoter** | nada | el Solver (`view`), el Hub (`isBridgeToken`, solo lectura), pools para la simulación del Pase Exacto (llamados y revertidos) | nunca | 11,429 |

![FIG-15 — Topología de cinco contratos: el Core (matemática pura: Ω · 𝒟 · Φ · Ψ) importado por el Hub (registro, estado Ψ, puentes, fijaciones de hooks), el Solver (optimizador de rutas, view), el Router (ejecución atómica, el único contrato que mueve fondos) y el Quoter (vista previa, view y simulación). Las flechas son llamadas; la única flecha sólida hacia el storage por swap es Router → Hub.recordSwap; todas las demás flechas que salen del Solver y del Quoter son solo view.](https://blazephoenix.xyz/whitepaper/figs/es/fig-15.png)

La confianza va en un solo sentido. El Hub confía en el Router para llamar a `recordSwap` (`onlyRouter`) y en nadie más; el Router confía en el Hub para tres hechos que una ruta necesita — si este token es un puente, si este hook está activo, y la escritura de registro — y para nada que ponga precio a una operación. El Solver confía en el registro del Hub solo como una lista de coordenadas que medir. El Quoter confía en el plan del Solver a título orientativo y vuelve a poner precio a las patas concentradas frente a los propios pools. Nada confía en el calldata: una ruta llega al Router como la afirmación de un desconocido.

### §16.2 La vida de un swap

![FIG-16 — La vida de un swap: `previewAndEncode` (view) → calldata → una puerta → `_execute` → venues → liquidación, con la comprobación de cada paso nombrada por su código de rechazo: mínimo y plazo en la puerta; forma y continuidad de los hops; llegada medida; por hop, reescalado, comisión si este es el hop de comisión, suelo por pata y compuerta de cobertura, agregado del hop; barridos; salida bruta; el suelo de ruta Φ; la comisión del lado de salida, si la hay; entrega medida en el destinatario; `userMinOut`; el libro mayor de comisiones; `recordSwap` por pata ejecutada; `ExecutionProof`.](https://blazephoenix.xyz/whitepaper/figs/es/fig-16.png)

1. **Vista previa** (off-chain, gratis): `previewAndEncode` ejecuta `findBestRoutePlan` — registro y, si no está fresco, descubrimiento; Ξ; clasificación Ψ; sondeos; la banda; la asignación; el recorte; la compuerta de división; el suelo atestado — y devuelve el `Preview` y el calldata. Quien opera fija `userMinOut` a partir de su propia expectativa de precio.
2. **La puerta**: `swapExactIn` (o una hermana) comprueba el plazo (`RouterE(4)`), rechaza un mínimo cero (`RouterE(10)`), retira la entrada y mide la diferencia.
3. **`_execute`**: número de hops, patas por hop y continuidad de los hops (`RouterE(3)`); el escaneo de la comisión; líneas base que nacen una sola vez.
4. **Por hop**: reescalar frente a la llegada medida; cobrar la comisión si este es el hop de comisión (o en cada hop en agotamiento); leer cada pool una vez para cantidad, impacto y cotización; ejecutar cada pata por la forma que selecciona su tipo (`RouterE(8)` para un tipo desconocido, `RouterE(9)` para un hook que altera deltas); el suelo por pata con la compuerta de cobertura y el agregado del hop (`RouterE(5)`); el callback paga solo al pool comprometido (`RouterE(6)`).
5. **Barridos**: la entrada no gastada y los residuos de puente por encima de sus líneas base vuelven al pagador.
6. **Liquidación**: salida bruta como diferencia de saldo (`RouterE(8)` si es cero); `floorBps` a partir del impacto medido y la concentración; el suelo de ruta frente a la cotización dentro del marco del último hop y el máximo de tres vías (`RouterE(5)`); la comisión del lado de salida en una ruta directa hacia una moneda puente; transferencia, entrega medida en el destinatario, `userMinOut` sobre la cantidad entregada (`RouterE(5)`); el libro mayor de comisiones (`RouterE(15)`, `RouterE(16)`).
7. **La única escritura**: `Hub.recordSwap` por pata ejecutada, con cantidades medidas; después, los eventos `ExecutionProof`, `Swap` y `Fee`. No se escribe nada cuando alguna comprobación rechaza; la transacción, incluido el swap de cada pool, nunca ocurrió.

### §16.3 Tamaños frente a EIP-170

| Contrato | Bytes de runtime | Por debajo de la compuerta de 24,000 bytes | Por debajo de 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 — Tamaños de runtime de los cinco contratos frente a la compuerta de 24,000 bytes del proyecto y el límite de EIP-170 de 24,576 bytes, medidos en el perfil de release; el router V1 desplegado (14,624 bytes) como escala.](https://blazephoenix.xyz/whitepaper/figs/es/fig-17.png)

La compuerta se afirma dentro de la propia suite (`DeployedSizeGate`) en el mismo perfil con el que se ejecuta la suite, que `profile_parity.py` mantiene idéntico al perfil de release — una suite que probara un binario que la chain no pudiera aceptar estaría probando el objeto equivocado. El tamaño se mide, nunca se estima: una guarda estimada en decenas de bytes llegó a medir varios cientos porque la forma elegida alargaba el intervalo de vida de una variable. El Router y el Hub están a pocos cientos de bytes del muro, y por eso la prueba del par de §9 cuesta 179 bytes y la reparación de la curva estable de §6 es 35 bytes *más pequeña* que el código que sustituyó: cada byte añadido se paga con un byte eliminado.

### §16.4 La superficie de ataque, contada

Una superficie de ataque es lo que un adversario puede escribir, lo que un contrato contiene, a quién llama y quién tiene poder sobre él. Aquí, cada una de esas cosas es una cantidad contable.

**Qué puede escribir un llamador.** Una ruta es una estructura tipada — como mucho 3 hops × 5 patas, cada pata con diez campos (`pool`, `hooks`, `kind`, `fee`, `tickSpacing`, `zeroForOne`, `stable`, `amountIn`, `expectedOut`, `auxId`) — y ningún campo nombra un destino de llamada ni lleva un blob de calldata. `docs/assurance/fields.json` clasifica cada campo escribible por el llamador que llega al estado, a un evento o a una guarda, 23 en total:

| Clase | Número | Significado | Ejemplos |
|---|---|---|---|
| **confirmado** | 14 | la ejecución lee el campo *y* una comprobación lo compara con una observación | `leg.kind` re-derivado de la forma del pool (Hub `recordSwap`); `leg.fee` en pools concentrados, donde responde el pool; `leg.amountIn`, reescalado por la llegada medida; `leg.expectedOut`, elevado por la compuerta de cobertura; `hop.tokenIn`/`tokenOut`, sometidos a compuerta frente a los `token0`/`token1` del propio pool; `leg.hooks` y `leg.auxId` en V4, que entran en el id del pool ejecutado |
| **direccional** | 5 | el campo dirige un movimiento de tokens, así que un valor falso castiga al llamador y no necesita una comprobación aparte | `leg.pool` en venues de par y concentrados (los tokens se envían a él); `leg.zeroForOne` en V4 (una dirección errónea falla cerrado en la contabilidad del gestor); `route.singleOutFloor` (monótono — solo puede endurecerse); los campos consultivos de la ruta (`totalOut`, `estGas`, …), que nadie lee |
| **declarado** | 4 | llega al estado, a un evento o a una guarda, y nada lo observa — cada uno con su razón registrada | `leg.fee` en pares V2 (ningún par expone `fee()`; acotado por el techo de 100 bps y usado igual por la cotización y la ejecución, así que no pueden divergir entre sí); `leg.hooks` en patas no V4 (solo alimenta el orden que pone al final los pools con hook; una mentira le cuesta al llamador su propia ruta); `leg.expectedOut` tal como se publica en el evento `Volume` (acotado cerca de +25 % por el suelo por pata que consume el mismo campo; sin consumidor on-chain); `leg.stable` (el Hub mide `stable()` al registrar y ambos consumidores leen la misma declaración) |

La clasificación del registro, el suelo del protocolo y la base de la comisión se calculan solo a partir de cantidades confirmadas. El Hub confirma `kind`; el Router confirma las cantidades, los tokens y las comisiones de los pools concentrados; la derivación confirma los campos V4 por construcción.

**Qué contiene el Router entre transacciones.** Saldo de tokens cero — `INV-1`, afirmado por `invariant_RouterHoldsNothing`, `invariant_routerHoldsNothing` e `invariant_HoldsNothingBeyondTheSeed` en las campañas, y vigilado por cuatro mutantes de guarda (una transferencia corta en un wei, y la eliminación de cada una de las tres líneas base). Las comisiones fluyen a las tesorerías dentro del swap; los residuos vuelven al pagador dentro del swap; lo único que puede quedarse en el Router es un envío erróneo, que el rescate de 48 horas existe para devolver y que las líneas base de cada swap excluyen del pago. Tres de los cinco contratos — Core, Solver, Quoter — no emiten ninguna escritura de storage en absoluto.

**A quién llama un swap.** Las contrapartes de un swap forman un conjunto cerrado de ocho clases: el token de entrada; el token de salida; los pools que nombra la ruta (como mucho 15); el Hub; el Solver, solo a través de `swapBestExactIn` y solo como view; WETH y el PoolManager, solo para patas V4 y nativas; Permit2, solo a través de su puerta; las tesorerías y el destinatario. No hay novena: el ejecutor no acepta ningún destino de llamada aportado por el llamador (SOK-ARBITRARY-CALL) y despacha la interacción con el venue desde un conjunto cerrado de formas seleccionadas por un tipo guardado en el registro. Cada lectura protegida tiene un tope de 100,000 gas. El número exacto de instrucciones de un swap se registra en lugar de afirmarse: 4,382 instrucciones del Router para un swap V2 honesto en el binario de release, con el opcode comprobado en cada paso (§15.10).

**Quién tiene el poder.** Nueve funciones de control en el Router y seis en el Hub, cada una enumerada en §14.6–§14.7 con lo que puede y lo que no puede alcanzar; ninguna puede mover el principal, la comisión, el reparto ni los suelos; todas terminan en `renounceControl`, y lo que sobrevive es el nivel de curador de cuatro funciones, que solo puede crecer, más los puestos de operador existentes. No hay `selfdestruct`, ni proxy, ni inicializador después del `initialize` único del Hub, y las únicas `delegatecall` del artefacto son las del propio compilador hacia la biblioteca pública Core.

**Qué debe existir para que una operación se liquide.** El presupuesto de §3.1: ni servicio de enrutamiento, ni oráculo, ni red de solvers, ni keeper, ni proxy — cinco ceros donde el diseño establecido tiene “requerido”, “habitual” o “varía”.

**La diferencia, contada.** Frente a un diseño que obtiene su ruta off-chain y ejecuta una lista genérica de llamadas, o que mantiene inventario:

| Superficie | Este diseño | Ejecutor resuelto off-chain | Venue o router que mantiene inventario |
|---|---|---|---|
| campos escribibles por el llamador que llegan a una guarda | 23, de los cuales 14 confirmados frente a una observación y 0 nombran un destino de llamada | una lista de pares `(target, calldata)`: no acotada, y cada destino es una llamada que el contrato hace fiándose de la palabra del llamador | — |
| saldo de tokens en reposo en el ejecutor | 0 (`INV-1`) | normalmente 0 | el propio inventario |
| contratos que escriben storage durante un swap | 1 (`Hub.recordSwap`, una escritura por pata ejecutada, solo en el camino de éxito) | varía | la contabilidad del venue |
| quién calcula la ruta | la chain (`findBestRoutePlan`, `view`) o el llamador, re-medida dentro del marco en ambos casos | un servidor; el contrato comprueba, no elige | — |
| oráculos, keepers, proxies, rutas de actualización | 0 | habitualmente ≥ 1 de cada | habitualmente un proxy |
| funciones privilegiadas que pueden alcanzar el principal | 0 | un admin del proxy puede sustituir la lógica | varía |
| bytes desplegados que un auditor debe leer | 84,986, cinco contratos, todos por debajo de EIP-170 | el contrato de liquidación, más un servidor cuyo tamaño en líneas auditables es imposible de conocer | — |

La comparación se cuenta, no se argumenta: la diferencia es una ruta acotada, tipada y confirmada frente a una lista no acotada; cero frente a inventario; una escritura frente a muchas; y una ruta que derivó la chain — o que derivó el llamador y la chain volvió a medir — frente a una ruta que derivó un servidor y que la chain se limitó a comprobar.

## §17 Rendimiento medido

*En sencillo:* todo lo de esta sección se midió en forks de chains reales — venues reales, reservas reales depositadas por desconocidos, un solo bytecode — y se etiqueta exactamente así. *Con precisión:* un número aparece aquí solo si un arnés público del repositorio lo reproduce; el arnés, la chain y la fecha se imprimen a su lado. Una medición en fork es evidencia sobre el comportamiento, no un registro de despliegue; el registro de despliegue está en §19. Las mediciones de §12 y §15 son del 2026-09-05; la campaña de esta sección se ejecutó en agosto de 2026 y sus arneses están en `test/fork/`.

### §17.1 Identidad entre chains

El mismo bytecode se ejercitó en forks de Ethereum, Arbitrum, Optimism y Base, adaptándose a la combinación de venues de cada chain — Aerodrome y Pancake junto a V4 en Base, Camelot en Arbitrum, Velodrome en Optimism — sin ningún cambio de código por chain. Las cuatro chains se eligieron porque entre ellas ejercitan todas las familias de venues que admite el registro, de modo que un caso especial por chain, de haber existido, tuvo cuatro oportunidades de aflorar. En cada chain se midieron dos identidades contra la verdad de referencia: para la derivación CREATE2, cada dirección de pool calculada por 𝒟 frente a la dirección que la propia factory de la chain reporta para el mismo par y los mismos parámetros, con `hasCode` confirmando bytecode en cada dirección derivada; para el Pase Exacto, el propio swap del venue llamado sobre el estado en vivo y sus deltas extraídos en el payload del revert, por chain, superando las convenciones de callback de cada familia, incluidas las de Algebra. Ambas identidades se cumplen en las cuatro chains, y la columna de diferencias que la tabla necesitaría está vacía.

### §17.2 Vista previa → entrega en cuatro chains

La tesis admite una medición directa: cotizar un swap, ejecutarlo, comparar. El arnés de fork `FidelityMatrix` cotiza, ejecuta y liquida pares reales en Arbitrum, Base, Optimism y Robinhood Chain en bloques fijados e imprime la vista previa frente a la entrega de cada par que toca; lleva una alarma permanente en 50 bps, de modo que una regresión de fidelidad se anuncia sola. Medido el 2026-08-24:

| Chain | Vista previa → entrega | Observado además |
|---|---|---|
| Arbitrum | 0–1 bps en cada par ejecutado | la ruta del Solver coincide con una rejilla exhaustiva de división en dos vías con pasos de 10 % |
| Base | 0–1 bps | a menos de 0.13 bps del máximo de la rejilla; ejecuciones reales de 222,000–326,000 gas, todo incluido |
| Optimism | 0–1 bps | la primera ejecución enrutada por V4 de este motor medida en fork en Optimism |
| Robinhood Chain | 0–1 bps | exacta respecto a la rejilla; las primeras ejecuciones medidas en la chain |

La matriz ejecuta de extremo a extremo patas de producto constante, concentradas, V4 — nativo incluido — y Solidly. La banda es de un punto básico en cada par ejecutado, con una alarma de 50 bps detrás; la comparación Solver frente a rejilla de §17.1 es el único resultado que coincide a la unidad, y se declara como tal. Junto a esto figuran dos resultados de comportamiento: los intermediarios puente se seleccionan de forma adaptativa por par como el maximizador de la Meta-Ecuación, y un par deliberadamente aislado no devuelve ninguna ruta en lugar de una fabricada.

### §17.3 El censo de factories

Registrar un venue es una afirmación, y la afirmación tiene su propio instrumento. El arnés del censo (`FactoryCensus`) hereda el cableado de despliegue de producción — despliega mediante el mismo script que reciben las chains, tras una compuerta de igualdad que se niega a ejecutarse si el arnés y el despliegue se desvían entre sí. Vive con ese script en el árbol privado de despliegue y no está en el repositorio público, así que la tabla de abajo es reportada, no reproducible desde este checkout; el barrido público que comparte sus fixtures y su compuerta antivacuidad es `test/fork/TokenSweep.t.sol`. Reporta, para cada factory registrada en cada chain sobre un corpus fijo de tokens, tres columnas: registrada, tiene un pool para el par, ganó el par (aportó una pata de la ruta ganadora). Las columnas comparten un denominador, los pares de hop, con una categoría explícita de no resueltos que midió cero en las cuatro chains: cada pata que enrutó el motor se atribuye a una factory con nombre.

| Arbitrum, 88 pares de hop | Tiene un pool | Ganó el par |
|---|---|---|
| Uniswap V4 (nativo + envuelto) | 67 | 48 |
| Uniswap V3 | 86 | 33 |
| Pancake V3 | 77 | 13 |
| Camelot V3 | 66 | 10 |

Un par acredita a cada venue que aportó una pata de su ruta ganadora, así que la columna puede superar el número de pares — atribución, no partición. En Arbitrum la familia V4 gana más pares que cualquier otro venue registrado, incluida la factory canónica de V3 — una confirmación desde fuera del orden de sondeo nativo primero de §5.1. El censo es también un instrumento de poda: una factory que se registra, tiene pools y no gana nada a lo largo de cientos de pares medidos es candidata a la retirada, tasada por atribución y no por reputación, y cada barrido lleva una compuerta antivacuidad — un par reportado como con profundidad también debe cotizar distinto de cero.

### §17.4 Gas

Cada cifra puede rastrearse hasta un arnés con nombre, con frío y caliente reportados por separado.

| Medición | Valor | Fuente |
|---|---|---|
| resolución en frío (primer descubrimiento, 4 factories) | 169,093 gas | `test/fork/DiscoveryColdWarm.t.sol`; `REPORTS.md` |
| resolución en caliente (Monoslots en caché, descubrimiento omitido) | 132,691 gas | misma ejecución |
| ahorro en caliente | −36,402 gas (−21 %) | diferencia |
| costo marginal por pata adicional | ≈ 28,700 gas | `LifecycleMetrics` (agosto de 2026) |
| segundo hop vía puente | ≈ 59,000 gas | `LifecycleMetrics` (agosto de 2026) |
| `previewPlan` en Base en vivo, frío / caliente / frío tras el TTL | 1,524,221 / 1,414,633 / 1,359,925 gas | `test/fork/QuoterGasFork.t.sol` (§12.3) |

![FIG-18 — Lo que cuesta una cotización a través de la ABI: `previewPlan` y `previewAndEncode` sobre mocks (descubrimiento frente a registro fresco, media y dispersión sobre cuarenta tamaños) y en Base en vivo (frío, caliente, frío tras el TTL).](https://blazephoenix.xyz/whitepaper/figs/es/fig-18.png)

Tres lecturas estructurales se sostienen sin ninguna medición, porque se siguen de la arquitectura. El ahorro en caliente escala con la amplitud del registro, no con el tamaño de la operación: lo que paga el camino en frío es barrer factories, y la lectura en caché que sustituye al barrido es un `SLOAD` en cualquier caso. Las patas son la unidad de costo marginal, y por eso existe la compuerta de división (§10.4). Y los hops son la dimensión cara: una ruta vía puente debe ganar lo suficiente en los pools para pagarse a sí misma en el surtidor. Los llamadores eligen su punto en esta superficie en cada operación — resolver dentro del marco y pagar la búsqueda, o aportar una ruta y pagar solo la ejecución — y la compuerta de frescura de §9.4 reserva el barrido para los pares que el protocolo aún no conoce. El costo de una cotización en vivo es de 1.4–1.5 millones de gas de cómputo *view*, que nadie paga on-chain.

### §17.5 El ejecutor frente a la competencia

Una tabla de gas sin una referencia externa es autoelogio, así que la capa de ejecución se comparó en el mismo fork, en el mismo bloque, con calldata real de producción: el `MetaAggregationRouterV2` de KyberSwap (`0x6131B5fae19EA4f9D964eAc0408E4408b66337b5`), en un fork de Base fijado al bloque en el que se construyó cada ruta, el 2026-08-22, con Paraswap cotizado al lado como segundo comparador: las propias rutas codificadas de Kyber, solicitadas a su API pública y reproducidas contra su contrato desplegado, con nuestras rutas ejecutadas junto a ellas y las entregas de ambos lados medidas como diferencias de saldo. El arnés se valida primero a sí mismo: la entrega medida del competidor coincide hasta el wei con la promesa de su API, de modo que un revert en cualquiera de los dos lados no puede hacerse pasar por un resultado. Por pata, el ejecutor midió entre 2.4× y 10.9× más eficiente en gas que la referencia — en el par más claro, 322,172 gas para cuatro patas frente a 879,553 para una, 2.7× por transacción — con un arnés (`BenchExec.t.sol`) que vive con el calldata capturado en el árbol privado, así que los números son reportados, no reproducibles desde este checkout, que se compila con `optimizer_runs = 999999` — de modo que la diferencia es de diseño, no de ajustes del compilador; su router interpreta una lista genérica de llamadas que decidió un servidor, el nuestro ejecuta una estructura tipada, e interpretar cuesta. En el par más claro su ruta de una sola pata costó 879,553 gas; cuatro de nuestras patas costaron 322,172. En nueve pares medidos, la puerta que resuelve por completo y la puerta de calldata entregaron una salida idéntica. Las barras de error del método forman parte del resultado: las cotizaciones se fijaron al bloque para el que se construyeron, y las estimaciones de gas del lado de la API se descartaron después de que la ejecución real midiera una de ellas un 148 % por debajo.

### §17.6 El tipo de cambio byte–gas ρ*

¿Qué puerta debe usar un llamador — llevar una ruta en calldata, o dejar que la chain resuelva? El costo de una transacción es afín y separable — $21{,}000 + \mathrm{bytes} \cdot p_{\mathrm{byte}} + \mathrm{gas} \cdot p_{\mathrm{gas}}$ — así que todo el compromiso entre calldata y ejecución se reduce a un escalar: **ρ\***, el gas de ejecución que compra un byte de calldata. Medido con `test/EquationBench.t.sol` en el perfil de release, ρ* va de 66.9 gas por byte en rutas de una pata a 105.5 en rutas de cuatro y cinco patas, transacción completa. Frente a él está el precio por byte en vivo de cada chain, leído de los propios predeploys de comisiones de la chain — el oráculo de precio del gas del OP-Stack, el `ArbGasInfo` de Arbitrum — sin clave y sin feed de terceros.

| Chain, precios leídos en vivo 2026-08 | $p_{\mathrm{byte}}$ (gas/byte) | Peso de los datos en un swap | Puerta barata |
|---|---|---|---|
| Base | 7.2 | 0.9 % | ruta en calldata |
| Arbitrum | 12.8 | 3.4 % | ruta en calldata |
| Optimism | 33.9 | 14.0 % | ruta en calldata |
| ZK rollup que paga la disponibilidad de datos a precio de calldata | 243,256 | 99.9 % | resolución dentro del marco — 14× más barata de extremo a extremo |

Donde $p_{\mathrm{byte}} < \rho^{*}$ la puerta de calldata es la barata; donde $p_{\mathrm{byte}} > \rho^{*}$ lo es la resolución dentro del marco. En las chains OP-Stack y Arbitrum el término de datos es el 0.9–14 % del costo de un swap, así que la ejecución es donde va el dinero — y es ahí donde se potencia la eficiencia del ejecutor descrita arriba. Las cuatro puertas del Router no son una función de conveniencia; son el modelo de costo afín, convertido en interfaz.

## §18 El DEX, reducido a lo esencial

Diecisiete secciones de mecanismo se reducen a una página. Si no lees nada más, lee esto.

**Un solo trabajo.** Recibir dos tokens, una cantidad, tu mínimo, un destinatario y un plazo. Devolver la mayor salida que la liquidez pública de la chain puede entregar — o rechazar, con un código que dice por qué. No hay tercer resultado: en la matriz de venues hostiles, veinte de veinte fixtures de patología de venue y puerta liquidan dentro de los suelos o rechazan con un selector nuestro (§15.5).

**Cinco contratos, una sola matemática.**

| Contrato | Qué es, en una línea |
|---|---|
| **Core** | La biblioteca sin estado: la forma cerrada de cada venue, la derivación 𝒟, el suelo Φ, la puntuación de vitalidad Ψ. Definida una vez, importada por los otros cuatro. |
| **Hub** | El registro que aprende la liquidez operándola — y prueba cada par antes de guardarlo. No pone precio a nada. |
| **Solver** | Construye la ruta y la división a partir de la salida marginal y la profundidad *medidas*. Solo `view`. |
| **Router** | El único contrato que mueve fondos. Mide cada diferencia de saldo, impone el suelo, no guarda nada en reposo. |
| **Quoter** | La vista previa gratuita — el mismo evaluador con el que liquida el Router, de modo que vista previa y entrega coinciden. |

**Seis tipos de venue, cuatro familias.** Uniswap V2, V3 y V4 (ETH nativo incluido), pools de comisión dinámica de Algebra y pares Solidly estables y volátiles — cada uno con su precio dado por una forma cerrada en el Core, un solo despachador Ω para todos ellos (§4). Curve y Balancer se extirparon, y sus números de tipo se retiraron para siempre (§4.1).

**Cuatro puertas, un ejecutor.** `swapExactIn`, `swapExactInWithPermit2` y `swapExactInNative` reciben una ruta en calldata; `swapBestExactIn` resuelve y ejecuta de forma atómica on-chain. Las cuatro llegan al mismo `_execute`, a los mismos suelos y a la misma comisión (§11.1).

**Tres suelos bajo cada operación.** Un suelo por pata, un agregado por hop y el suelo de ruta Φ re-derivado a partir del impacto medido en el marco de ejecución — y después *tu* `userMinOut` sobre lo que realmente llega al destinatario. La entrega es el mayor entre el suelo del protocolo y el tuyo (§11.5).

**Una comisión, compilada.** 0.28 %, cobrada una vez sobre la primera moneda puente que tiene el Router, redondeada hacia arriba para que nunca sea cero, y nada que crezca con la diferencia entre cotización y entrega (§13).

**En qué se niega a confiar.** La liquidez anunciada. Las comisiones declaradas. La profundidad en calldata. Lo que un pool dice sobre qué tokens opera. Cada una de estas cosas se mide desde el pool o se deriva por aritmética antes de que pueda llegar al estado compartido (§7, §9, §16.4).

**Lo que cuesta.** Gas: derivar la ruta dentro del marco es más caro que liquidar una respuesta calculada en otro lugar, y §17.6 te dice por chain qué puerta es la más barata. Inmutabilidad: un defecto se divulga y se recompensa con un bounty, nunca se parchea (§21).

## §19 Estado del despliegue

Este documento describe el repositorio, y el repositorio es canónico: donde este texto y el código fuente discrepen, prevalece el código fuente. Existen dos generaciones y nunca se confunden.

**V1 es lo que está en vivo on-chain.** Es una base de código distinta y anterior, archivada en su propio repositorio — no un commit anterior de este. Medido por RPC el 2026-09-04, el router V1 desplegado es el mismo contrato de 14,624 bytes en Base, Ethereum, Optimism y Arbitrum. `test/fork/DeployedParity.t.sol` y `test/fork/DeployedCodehashPin.t.sol` fijan lo que es V1, en cada red que nombra el SDK — el codehash de cada contrato desplegado, registrado en `test/fork/deployed-codehash.json` — de modo que un cambio en lo desplegado se detecta, y afirman las propiedades que se cumplen sea cual sea la generación: sin `SELFDESTRUCT`, sin `CALLCODE`, sin autoridad por `tx.origin`, un bloqueo de reentrada transitorio, un tamaño de runtime fijo, cableado activo y el control aún no renunciado.

**Este repositorio es V2, y V2 no está desplegado.** El árbol medido en este documento compila un Router de 23,781 bytes y tiene funciones externas que V1 no tiene — entre ellas la puerta de valor nativo, la costura de wrap/unwrap, la vía de rescate y la puerta Permit2. Los contratos V2 se despliegan sobre forks de liquidez en vivo y se ejercitan de extremo a extremo en cada ejecución de fork (§15.1, §17). Nada en §15 ni en §17 protege los contratos V1 desplegados; cada afirmación de este documento es una afirmación sobre V2 tal como está en `8949a9d`.

En consecuencia, dos políticas protegen a los lectores. Este documento no contiene direcciones de contratos — las direcciones sobreviven a los documentos que las citan, y la superficie de direcciones en vivo se mantiene donde puede actualizarse y verificarse por máquina: los registros del repositorio y la página de verificación y el manifiesto del sitio. Y un registro de despliegue tiene un formato especificado — chain, dirección, commit de despliegue y hash del bytecode de runtime, escrito en el momento del broadcast, con un paso de verificación que re-deriva el hash a partir de una compilación limpia — para que, cuando V2 se despliegue, cada afirmación sobre direcciones sea aritmética, no confianza.

## §20 Token — BZPX

Mil millones de BZPX, fijos para siempre, como un ERC-20 simple: 18 decimales, sin vía de acuñación, sin comisión por transferencia, sin rebasing, sin hooks de transferencia, sin pausa ni lista negra — seis invariantes de despliegue, verificados contra el bytecode del token en el momento del cableado. El suministro totalmente diluido se conoce desde la génesis; la escasez es una propiedad del contrato, no una política.

| Asignación | Tokens | Parte | Nota |
|---|---|---|---|
| Liquidez de mercado | 520,000,000 | 52.0 % | el flotante a través del cual enruta el agregador |
| Emisión de staking | 180,000,000 | 18.0 % | reservada; el diseño de emisión V4 está en §25 |
| Airdrop y Bounties | 130,000,000 | 13.0 % | un solo pool: el antiguo airdrop de 50,000,000, el antiguo fondo de investigación de 50,000,000 de §21, y 30,000,000 trasladados desde la liquidez de mercado |
| Equipo y operaciones | 103,000,000 | 10.3 % | el único tramo bloqueado de insiders; vesting lineal de 30 meses |
| Socios y ecosistema | 60,000,000 | 6.0 % | integraciones, alianzas con venues |
| Marketing | 7,000,000 | 0.7 % | difusión del lanzamiento |

**Qué cambió en septiembre de 2026.** El airdrop y el fondo de investigación de seguridad eran dos líneas que pagaban a las mismas personas por lo mismo — leer el protocolo a fondo y usarlo de verdad. Ahora son una sola línea de 130,000,000 BZPX: 50,000,000 del airdrop, 50,000,000 del fondo de investigación y 30,000,000 tomados de la liquidez de mercado, que pasa de 550,000,000 a 520,000,000. No se tomó nada de la línea de nadie más, y el suministro sigue siendo exactamente mil millones.

El registro on-chain del contrato del token, una vez publicado, es la versión canónica de esta tabla. En el momento de escribir esto, el token no tiene liquidez aprovisionada ni evento de generación de tokens; un volumen on-chain de cero es el estado esperado de esta etapa, no una señal sobre el código.

## §21 Programa de investigación e historial de seguridad

Antes de que este proyecto tuviera un programa de investigación digno de ese nombre, investigadores independientes leyeron el código fuente con ojos hostiles — sin que nadie se lo pidiera, sin nada prometido — y divulgaron en privado lo que vieron. El código que describe este documento es el código que sobrevivió a su lectura. Lo que reportó cada uno de ellos, y lo que fue de ello, vive donde corresponden esos registros: en `SECURITY.md` y `SECURITY_HALL_OF_FAME.md` del repositorio, junto al código, donde cada entrada puede comprobarse en ambas direcciones. Esta sección registra quiénes son, lo que paga la lectura hostil y la forma de lo que encontró.

**Los investigadores.** 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 · y un investigador que eligió permanecer en el anonimato. Se ha acreditado a veinticinco investigadores; ningún reporte ha alcanzado la clase Crítica. La atribución nunca se redondea a la baja: los nombres se publican exactamente como se dieron.

**El pool: Airdrop y Bounties, 130,000,000 BZPX — 13 % del suministro fijo** (§20). Las recompensas de seguridad se toman de él según las bandas de abajo, que se mantienen sin cambios.

| Severidad | Recompensa |
|---|---|
| Crítica — robo directo o congelación permanente de fondos de usuarios; un estado insolvente alcanzable | 2,500,000 – 7,500,000 BZPX |
| Alta — robo bajo condiciones específicas; una redistribución que paga sistemáticamente a la parte equivocada | 625,000 – 2,500,000 BZPX |
| Media — griefing, denegación de servicio temporal, residuo recuperable | 125,000 – 625,000 BZPX |
| Baja — impacto demostrado por debajo de lo anterior | hasta 125,000 BZPX |

Las recompensas se pagan en BZPX — la cantidad y el calendario están prometidos, el valor de mercado no — con pagos que comienzan después de octubre de 2026 y reportes que se clasifican ya. Una cláusula es deliberada: un reporte no necesita romper una ley de conservación para ser Crítico — una redistribución que paga sistemáticamente a la parte equivocada mientras las cuentas cuadran se valora como un robo, porque esa es la clase que solo detectan los lectores hostiles.

**Qué contiene el historial, por clase.** Cada hallazgo confirmado se convirtió en una propiedad con nombre, un test de regresión que falla contra el código previo a la corrección y un mutante que el test debe eliminar; las clases, una frase cada una, con referencia al registro:

- *Dos productores de una misma cantidad* — un planificador y un ejecutor, una vista previa y una entrega, que responden a una sola pregunta en dos marcos (la agregación de impacto del suelo, el puente nombrado por la vista previa frente al hop en el que se ancla la comisión, el presupuesto de patas entre dos contratos, el bit de puente congelado frente al predicado vivo): se cierra colapsando a un solo productor o con un test de paridad con nombre, y se enumera como un registro con una regla de CI.
- *Una cantidad aportada por el llamador donde existía una medición* — la base de la comisión leída de `route.totalOut`, la comisión y los hooks del registro escritos desde calldata, la profundidad declarada en lugar de medida, un volumen publicado que reportaba la declaración: se cierra midiendo, y con la clasificación de campos de §16.4.
- *Ausencia leída como permiso* — un hop final que no podía cotizarse dentro del marco y dejaba el suelo inerte, una compuerta de cobertura que un llamador podía desactivar escribiendo cero, una lista de comisiones vacía que cotizaba una familia entera a cero: se cierra recurriendo a la cifra atestada o medida, nunca a nada.
- *Una fijación sobre el objeto equivocado* — el origen de derivación de una factory que un proxy podía mover mientras su codehash permanecía, el runtime de un hook fijado donde su implementación podía moverse: se cierra atestando la respuesta donde el código no puede fijarse, y declarando el residuo en el lugar donde no puede cerrarse.
- *Fallar abierto en una cotización* — la guarda de dominio de la curva estable que ella misma desbordaba con dust, encontrada por el carril N-versión: se cierra escribiendo primero el test en rojo, con una reparación más pequeña que el código que sustituyó.
- *Una corrección aplicada a uno de dos canales simétricos* — la firma de defecto que esta base de código nombra primero, encontrada repetidamente; cada cierre busca ahora el canal hermano antes de fusionarse.

La invitación es el sentido de esta sección, y es permanente: se invita a lectores independientes, en público, y se les paga, en una moneda cuya cantidad es fija. Reporta en privado, a través del canal de divulgación de `SECURITY.md` del repositorio.

## §22 En qué debes confiar, y en qué no necesitas hacerlo

Todo protocolo afirma no requerir confianza; casi ninguno puede enumerar sus excepciones. Esta es la lista completa, cada poder enunciado con la línea que lo acota.

- **El registro de venues, la lista de puentes y la lista de permitidos de hooks V4** — curados; el peor caso de un listado hostil está acotado por el más estricto entre tu mínimo y el suelo Iron-Law Φ; al principal no puede llegar (§9.6, §14.6).
- **La clave de control del Router, antes de la renuncia** — puede redirigir el destino de la comisión, pausar swaps, reapuntar los contratos WETH y Permit2 a los que llaman sus puertas nativa y Permit2, y recuperar tokens enviados por error tras un retraso público de 48 horas; no puede mover la tasa de la comisión, el reparto ni los suelos (constantes de compilación, sin setters) ni llegar al principal (sin proxy, sin ruta de actualización, sin `selfdestruct`, nada guardado en reposo).
- **La clave de control del Hub, antes de la renuncia** — puede reapuntar la vista que el Hub tiene del Router, el Solver y el Quoter, reapuntar el manager de V4, pausar el aprendizaje del registro y otorgar puestos de operador; no puede cotizar mal una ejecución, porque nada que ponga precio a una operación lee el registro.
- **Las tesorerías** — fijadas en la construcción, y el reparto 30 / 70 no tiene setter.
- **Los contratos canónicos de los que dependen las puertas** — el Permit2 de Uniswap para la puerta Permit2, el WETH de la chain para la puerta nativa, el PoolManager para las patas V4: cada uno se llama solo a través de su puerta o de su pata, y el Router pone precio a cada pata a partir de su propia diferencia de saldo medida, de modo que una contraparte deshonesta hunde la salida y tu cota rechaza el swap.
- **El token y la correspondencia** — que BZPX cumple sus seis invariantes de despliegue, y que las direcciones a las que llamas corresponden a este texto: verificable en el momento en que se publiquen los registros de §19, mediante el procedimiento que especifica §19.

En qué no necesitas confiar: en nadie que ponga precio a tu operación — la ruta se deriva en el marco de ejecución a partir del estado público, o se vuelve a medir allí si la aportaste tú; en nadie que la enrute con justicia — el suelo se re-deriva on-chain a partir de la salida medida y la comisión no tiene ningún término que crezca con la diferencia entre cotización y entrega; en nadie que cuente la comisión — el Router se niega a liquidar sin pagarla; y en nadie que siga dándote servicio — si todos los servidores que opera este proyecto desaparecieran esta noche, los contratos cotizarían, enrutarían y liquidarían mañana exactamente igual que hoy.

Y si te quedas con un solo hábito de este documento, que sea el mínimo que fijas en tus propias operaciones. Cada cota del agregador recurre en última instancia a ese número; es la única protección del sistema cuya calidad no depende de la competencia de nadie salvo la tuya.

## §23 Trabajo relacionado

**Enrutamiento.** Angeris, Evans, Chitra y Boyd plantean el enrutamiento a través de creadores de mercado de función constante como un programa convexo cuyo óptimo iguala los precios marginales entre venues; Diamandis, Resnick, Chitra y Angeris dan para él un algoritmo eficiente de descomposición dual. Este diseño se queda deliberadamente por debajo de ese óptimo on-chain: el Asignador de División es proporcional a la profundidad en una sola pasada (§10.4), lo que no requiere iteración ni modelo de curvatura por venue y es robusto allí donde un solver iterativo es una factura de gas y una superficie de ataque; el carril metamórfico mide la distancia que cuesta esa elección — la división nunca paga menos que el mejor pool único (MR-R1) y la matriz de fidelidad sitúa la ruta del Solver a menos de 0.13 bps de una rejilla exhaustiva en Base. La forma cerrada exacta que iguala los rendimientos marginales para la familia de producto constante son dos sumas y una raíz cuadrada por venue, y es candidata para una versión posterior; el objetivo es plano cerca de su óptimo, y por eso la regla robusta se publica primero. Los diseños de solver off-chain trasladan la optimización a un servidor y reducen el contrato a un verificador; el Presupuesto de Dependencias de §3.1 es la formulación de este documento de lo que eso cuesta. Trabajos recientes amplían el modelo con el gas: *Optimal Routing across Constant Function Market Makers with Gas Fees* muestra que el gas ensancha las regiones sin operación e interactúa de forma no trivial con la fragmentación, y *Routing Frictions and Executable Liquidity in Fragmented Markets* separa la liquidez anunciada de la ejecutable — la distinción que este diseño mide dentro del marco en lugar de modelarla.

**Agregación robusta.** El centro de credibilidad de §10.3 es una mediana ponderada por profundidad, cuyo punto de ruptura es la mitad del peso. Müller, Moumeni y Messaoudi, *Cost of Manipulation in AMM-Based Oracles* (FC 2026), demuestran la cota correspondiente para oráculos que agregan precios de AMM: con pesos iguales, el ataque más barato mueve los pools menos profundos, mientras que con pesos por liquidez controlar la mitad del peso requiere al menos la mitad de la profundidad total de cotización. Ormer propone un oráculo on-chain que estima la mediana en lugar de la media TWAP, y *From Means to Medians* estudia qué benchmarks son los más difíciles de manipular. E25 aplica el mismo principio al enrutamiento: a un pool solo se le cree en la medida en que lo respalda capital en riesgo.

**Venues.** Uniswap v2 aporta la referencia de producto constante y la convención de 30 bps que la rama V2 usa por defecto; Uniswap v3, el modelo de liquidez concentrada, `slot0` y el punto fijo Q64.96 que sigue la forma cerrada de §4.2; Uniswap v4, el singleton, la contabilidad flash y la codificación de los permisos de hook en la dirección, sobre los que se apoyan §5 y §14.2 — el diseño aquí es, hasta donde sabemos, el primero en derivar, probar, poner precio, filtrar y liquidar pools V4 enteramente dentro del marco. El StableSwap de Egorov define el invariante amplificado de la familia Curve, que este protocolo extirpó en lugar de darle soporte sobre un supuesto de confianza no medido (§4.1); la curva estable Solidly de §6 no tiene un artículo revisado por pares y se cita por sus implementaciones desplegadas.

**MEV.** Daian et al. nombraron al adversario de ordenamiento y mostraron su escala; Zhou et al. midieron los ataques sandwich en exchanges descentralizados y formalizaron su rentabilidad. Este documento no afirma ninguna eliminación — el orden se decide antes de que se ejecute el código del contrato — y en su lugar mide la cota: la curva de sandwich de §14.3, desde el lado del atacante, con el borde de rechazo y el viaje de ida y vuelta negativo del atacante más allá de él.

**Verificación.** Las hiperpropiedades de Clarkson y Schneider nombran la clase a la que pertenecen los hallazgos de redistribución de §21 — propiedades de conjuntos de trazas, no de una traza — y el porqué de que una ley de conservación pueda cumplirse mientras se paga a la parte equivocada. La interpretación abstracta de Cousot y Cousot es el marco para la forma cerrada de un solo tick: una abstracción de la matemática del venue que es exacta dentro de un rango y correcta en una dirección fuera de él, con el suelo como comprobación de concreción. Chen, Cheung y Yiu introdujeron el testing metamórfico, el segundo juez de §15.7 allí donde no existe un oráculo independiente de la fórmula. DeMillo, Lipton y Sayward introdujeron el análisis de mutación, en cuya hipótesis de acoplamiento se apoya la guarda curada de §15.2. La programación N-versión de Avizienis es el antecedente de §15.8, que la aplica a la generación de código en lugar de a equipos independientes. El testing combinatorio de Kuhn, Kacker y Lei da a los arreglos de cobertura de §15.4 su construcción y su afirmación: cada interacción de orden t entre factores de régimen ejercitada por construcción, con el denominador impreso. El resultado de Littlewood y Strigini sobre la validación de una confiabilidad ultraalta es la razón por la que en ningún lugar de este documento se declara una probabilidad de corrección.

**Estándares.** El transient storage de EIP-1153 lleva el contexto de pata de la Doctrina del Callback Único, el libro mayor de comisiones y el bloqueo de reentrada; EIP-170 es el muro de 24,576 bytes del que el Router y el Hub quedan a unos pocos cientos de bytes; EIP-1014 es la aritmética CREATE2 de 𝒟; EIP-2612 y el Permit2 de Uniswap definen la transferencia basada en firma de la segunda puerta; ERC-7201 da espacio de nombres al storage del Hub; las cuentas delegadas de EIP-7702 llaman a las mismas puertas que cualquier contrato.

## §24 Conclusión

Nos propusimos construir un agregador sobre una sola disciplina — medir, no creer — y mantenerla en cada punto donde un número podría haberse aceptado, en cambio, bajo palabra de alguien: la ruta, el precio, el suelo, la base de la comisión, el conjunto de venues, el par que un pool dice operar, incluso el propio modelo de costos del protocolo.

El resultado es un sistema pequeño con una forma fuerte. La fragmentación de la liquidez resultó ser un problema de lenguaje, no de matemáticas: bajo la proliferación de venues hay una pequeña familia de invariantes, y un protocolo que expresa cada uno una sola vez — un despachador, una derivación, un suelo, un campo de vitalidad — puede poner precio al panorama sin casos especiales por venue, derivar la ruta dentro de la transacción que la ejecuta y volver a comprobar su propia promesa contra la salida medida antes de que un token te llegue. El registro medido es el resumen más breve de lo que eso compra: una cotización sin deriva que se liquida exactamente según su predicción 64 veces de 64, sobre mocks y en Base en vivo; un suelo que limita un sandwich sobre una operación del 1 % a cerca del 2.7 % de ella y rechaza más allá; una vista previa y una entrega separadas por 0–1 puntos básicos en cuatro chains; y un aparato que publica, con denominadores, cómo podría manipularse y lo que no puede ver.

Lo que cuesta es igual de claro. Gas: derivar una ruta dentro del marco es más caro que liquidar una respuesta calculada en otro lugar — el precio de una garantía que puedes verificar en lugar de una que debes aceptar bajo palabra de alguien — y el tipo de cambio byte–gas te dice, por chain, qué puerta hace ese precio más pequeño. Inmutabilidad: los defectos se divulgan y se recompensan con bounties, no se parchean — el precio de un contrato que auditas una vez, y §21 es lo que compra ese precio.

La tesis, reformulada: lo que elige es lo que opera. Una cotización y su ejecución son el mismo cálculo, así que la brecha donde antes vivía la honestidad de un operador se cierra con aritmética; un registro que aprende la liquidez operándola, y prueba cada par que guarda; una comisión, un suelo y una superficie de entrada que nadie — los autores incluidos — puede mover, porque están compilados. El protocolo no pide confianza. Pide ser verificado, y trabaja para que la verificación sea barata, pública y permanente.

## §25 Staking V4: diseño

Staking V4 es la base de código actual; la dirección se publica en el lanzamiento.

Este capítulo describe el motor de staking tal como está su código fuente: `src/BlazePhoenixStaking.sol` en el repositorio `Blaze-Phoenix-Staking`, `main` en `cc614f9` (2026-10-14), que declara `VERSION = "4.0.0"` (L169). Cada número de línea de este capítulo (L*n*) se refiere a ese archivo en ese commit; donde este texto y el código fuente discrepen, prevalece el código fuente. El capítulo no contiene direcciones, ni fechas de despliegue, ni rendimientos: cada número que aparece en él es una constante compilada en el contrato, o se deriva de una, y la derivación se imprime a su lado.

El motor se somete a la disciplina del resto de este documento. Mantiene un solo token, BZPX, y cada derecho sobre ese BZPX es un término del libro mayor cuya suma el contrato vuelve a comprobar en torno a cada cambio de estado (E35). Un staker bloquea BZPX durante el número de días que elige y obtiene dos flujos — una emisión programada (E27–E29) y los intereses que pagan los prestatarios (E31–E34) — cada uno ponderado por un boost que crece con el compromiso (E30). Un staker puede tomar prestado BZPX con su stake como garantía (E41). Cuando se produce una pérdida, sigue un único orden fijo (E37), y el contrato informa, mediante una vista pública, de si mantiene lo que debe (E36).

### §25.1 Arquitectura y roles

Un solo contrato, `BlazePhoenixStaking`, que hereda de `AccessControl`, `Pausable` y `ReentrancyGuard` (L167). El token, la tesorería y la ventana de emisión (`emissionStart`, `emissionEnd`) son inmutables (L266–269); el constructor rechaza una dirección de token que no contiene código (L477). No hay oráculo, ni ruta de actualización, ni función para modificar parámetros: cada constante de abajo está compilada en el contrato.

| Rol | En manos de | Puede | Línea |
|---|---|---|---|
| `DEFAULT_ADMIN_ROLE` | el desplegador, en la construcción | conceder y revocar roles (no existe ninguna llamada a `_setRoleAdmin`, así que administra todos los roles) | L485 |
| `ADMIN_ROLE` | el desplegador, en la construcción | `fundEmission` (financiación acumulada ≤ `TOTAL_REWARDS`); `withdrawReserve` (solo hacia la tesorería inmutable, como máximo `protocolReserve`, cerrado bajo un incumplimiento grave); `sweepUndistributedEmission` (solo después de `emissionEnd`); `unpause`; `cancelEmergency` (solo con un balance solvente) | L486, L516, L533, L563, L580, L618 |
| `GUARDIAN_ROLE` | nadie en la construcción; lo concede un administrador de roles | `pause`; `declareEmergency` — ninguno de los dos mueve fondos | L171, L578, L608 |
| cualquiera | — | `liquidate`; `tripBreaker` (revierte salvo que el balance esté en incumplimiento grave); `absorbBadDebt`; `lockStep` | L1066, L597, L639, L1291 |

Ningún rol puede mover el principal de un staker, y el único destino al que un rol puede enviar ingresos es la tesorería inmutable.

| Constante | Valor | Significado | Línea |
|---|---|---|---|
| `TOTAL_REWARDS` | 180,000,000e18 | presupuesto de emisión, y tope de la `fundEmission` acumulada | L192 |
| `HALVING_PERIOD` | 2 × 365 días | duración de un periodo de emisión | L193 |
| `EMISSION_PERIODS` | 8 | periodos antes de que se cierre el programa (`EMISSION_LENGTH` = 16 años) | L194–195 |
| `INITIAL_REWARD_PER_SEC` | (`TOTAL_REWARDS` / 2) / `HALVING_PERIOD` | tasa del periodo 0, $R_{0}$ | L196 |
| `MAX_STAKE_PER_WALLET` | 30,000,000e18 | tope de stake por posición | L198 |
| `MIN_EMISSION_WEIGHT` | 1,000e18 | por debajo de este peso total, ambos flujos se limitan a prorrata | L208 |
| `MAX_LTV` | 50 | tope de préstamo, % del stake neto | L210 |
| `LIQ_THRESHOLD` | 95 | liquidable con deuda ≥ 95 % del stake | L211 |
| `LIQ_BONUS_BPS` | 500 | excedente de liquidación para el llamador, en bps de la deuda | L212 |
| `RESERVE_FACTOR_BPS` | 300 | parte de cada tramo de intereses que se queda en `protocolReserve` | L213 |
| `MIN_LOCK_DAYS`, `MAX_LOCK_DAYS` | 90, 2,555 | límites del bloqueo, en días | L221–222 |
| `BOOST_BASE`, `BOOST_LINEAR`, `BOOST_QUAD` | 10,000, 750, 250 | polinomio de boost, bps | L224–226 |
| `MIN_DEPOSIT_BLOCKS` | 10 | bloques que debe tener de antigüedad un depósito antes de un retiro, una reclamación de rendimiento puro o un cambio de bloqueo | L228 |
| `RATE_R0`, `RATE_RK`, `RATE_UK`, `RATE_S1`, `RATE_S2` | 100, 500, 0.8e18, 500, 72,500 | curva de intereses con codo, bps anuales | L231–235 |
| `MAX_PROTOCOL_UTIL_WAD` | 0.75e18 | tope de utilización del balance para un nuevo préstamo | L242 |
| `CONSERVATION_DUST` | 1e10 | tolerancia de la guarda de conservación, en wei | L247 |
| `MAINT_BASE`, `MAINT_DENSITY`, `MAINT_GAP_UNIT`, `MAINT_MAX_SCAN`, `MAINT_MAX_LOCK_ACTIONS` | 1, 50, 15 minutos, 10, 4 | presupuesto de mantenimiento autónomo (E42) | L252–255, L263 |

### §25.2 El flujo: stake, unstake, reclamación, préstamo

Cada punto de entrada de usuario ejecuta un mismo pipeline, en el orden que fija `deposit`: tomar una instantánea de la identidad de conservación; rechazar si el balance está en incumplimiento grave; hacer avanzar el índice de intereses y la ventana de emisión (`_updateGlobal`, L1428); liquidar ambos flujos al peso que tenía la posición; atribuir los intereses que debe la posición (`_accrueInterestFor`, L1627); levantar un bloqueo vencido; realizar la acción; recalcular los pesos de la posición mediante el escritor único (`_resync` → `_applyBoost` → `_checkpoint`, L1726–1756); ejecutar la ventana de mantenimiento autónomo (E42); y volver a comprobar la identidad de conservación (E35). Las puertas solo difieren en las compuertas que añaden.

| Punto de entrada | En pausa | Emergencia | Incumplimiento grave | Condiciones adicionales |
|---|---|---|---|---|
| `deposit(amount, lockDays)` — stake | cerrado | cerrado | cerrado (L760) | $90 \le$ `lockDays` $\le \min(2{,}555,\ \text{days left to } \mathtt{emissionEnd})$; stake de la posición ≤ `MAX_STAKE_PER_WALLET`; antes de `emissionEnd` (L743–760) |
| `lock(lockDays)` | cerrado | cerrado | — | `MIN_DEPOSIT_BLOCKS` desde el último depósito (L964–971) |
| `withdraw(amount)` — unstake | abierto | cerrado | cerrado (L902) | sin deuda; bloqueo vencido; `MIN_DEPOSIT_BLOCKS` desde el último depósito (L890–895) |
| `claimRewards()` | abierto | cerrado | cerrado (L932) | liquida la emisión y el rendimiento puro (L930) |
| `claimPureYield()` | abierto | cerrado | cerrado (L948) | sin deuda; `MIN_DEPOSIT_BLOCKS` desde el último depósito (L944–948) |
| `borrow(amount)` | cerrado | cerrado | cerrado (L826) | E41 (L820–840) |
| `repay(amount)` | abierto | cerrado | abierto | — (L858) |
| `liquidate(user)` | cerrado | cerrado | abierto | E39 (L1066) |
| `emergencyWithdraw()` — salida | — | solo | — | E40 (L658) |
| `absorbBadDebt(amount)` | abierto | abierto | abierto | E38 (L639) |

**Stake.** `deposit` ingresa BZPX en la posición e inicia o prolonga su bloqueo; el bloqueo nunca puede extenderse más allá del final del programa de emisión, y el boost que compra (E30) solo se paga mientras el bloqueo está vigente. **Unstake.** `withdraw` devuelve el principal en cuanto la posición no debe nada y su bloqueo ha vencido; sigue abierto bajo una pausa del guardián, de modo que una pausa detiene el riesgo nuevo sin atrapar a nadie. En una emergencia la única salida es `emergencyWithdraw`, que paga el patrimonio neto, reducido a prorrata si el contrato tiene menos de lo que se debe a las posiciones (E40), y hace que la posición pierda sus flujos no reclamados. **Reclamación.** La cosecha son las dos reclamaciones: `claimRewards` paga ambos flujos, `claimPureYield` hace lo mismo para una posición sin deuda; no existe una función de cosecha aparte. **Préstamo, devolución, liquidación.** Un staker puede tomar prestado BZPX con su propio stake como garantía dentro de dos topes (E41); los intereses se cargan contra el stake, no se suman a la deuda (E34); `repay` sigue abierto bajo una pausa y bajo un incumplimiento grave, porque cerrar deuda solo hace más seguro el balance. Una posición cuya deuda alcanza el 95 % de su stake puede ser cerrada por cualquiera, y quien la cierra recibe el excedente de E39.

![FIG-19 — Los puntos de entrada del staking y su único pipeline: las compuertas que añade cada puerta, después la instantánea de conservación, la compuerta de incumplimiento, la actualización global, la liquidación, la atribución de intereses, el vencimiento del bloqueo, la acción, la resincronización por el escritor único, el mantenimiento autónomo y la comprobación de conservación; la salida de emergencia queda aparte.](https://blazephoenix.xyz/whitepaper/figs/es/fig-19.png)

### §25.3 Contabilidad de recompensas

**Emisión.** Sean $T$ = `TOTAL_REWARDS`, $P$ = `HALVING_PERIOD`, $R_{0}$ = `INITIAL_REWARD_PER_SEC`, $s$ = `emissionStart`, y para un instante $t$ sean $\tau = t - s$ y $p = \lfloor \tau / P \rfloor$. La emisión programada acumulada es una forma cerrada (`_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

*Derivación.* En aritmética exacta la tasa del periodo $q$ es $T/(2^{q+1}P)$, así que un periodo $q$ completo emite $T/2^{q+1}$ y la emisión anterior al periodo $p$ es $\sum_{q<p} T/2^{q+1} = T(1 - 2^{-p})$. $T = 1.8\times 10^{26}$ es divisible por $2^{8}$, así que $T \gg p = T/2^{p}$ exactamente para $p \le 8$, y el primer término es esa suma sin redondeo. El término de la tasa se redondea hacia abajo: $R_{0} \gg p = \lfloor\lfloor T/(2P)\rfloor / 2^{p}\rfloor = \lfloor T/(2^{p+1}P)\rfloor$, por debajo de la tasa exacta en menos de un wei por segundo. Dentro de un periodo, $E$ crece por tanto linealmente y se mantiene por debajo de la curva exacta; en cada cambio de periodo sube un escalón de $T/2^{p+1} - (R_{0} \gg p)\,P$, un salto no negativo y menor que $P$ wei, que la devuelve a la serie exacta. $E$ es monótona no decreciente, y se cierra en $T - T/2^{8} = \tfrac{255}{256}\,T$: el último $T/256$ nunca se programa y, junto con lo que dejen las ventanas vacías o sin financiar, solo se puede recuperar hacia la tesorería mediante `sweepUndistributedEmission` después del cierre (L563). Una evaluación con enteros exactos sobre los ocho cambios de periodo confirma que cada salto es positivo y menor que $P$.

![FIG-20 — Emisión programada E27 frente al tiempo: la tasa se reduce a la mitad cada dos años y la curva acumulada se cierra en 255/256 de TOTAL_REWARDS al cabo de dieciséis años; programada, no pagada — una ventana sin peso no emite nada.](https://blazephoenix.xyz/whitepaper/figs/es/fig-20.png)

**La ventana.** En cada punto de entrada, con $W$ = `totalBoostedEffective`, $W_{\min}$ = `MIN_EMISSION_WEIGHT`, $t_{\ell}$ = `lastRewardTime` y $t' = \min(t, \mathtt{emissionEnd})$, la ventana transcurrida se paga a un acumulador por unidad de peso $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` baja en $r$ y `totalRewardDistributed` sube en $r$. Con $W = 0$ el reloj avanza y no se captura nada, así que nadie hereda un atraso; por debajo de $W_{\min}$ la ventana se limita a prorrata, así que una posición de dust no puede llevarse sola el calendario; y como cada ventana tiene como tope la reserva, `fundEmission` liquida el reloj antes de que llegue la nueva financiación (L523), de modo que una recarga nunca paga una ventana con carácter retroactivo.

**Liquidación de recompensas.** Una posición con peso de emisión $w_{u}$ y punto de control $c_{u}$ (`rewardDebt`) tiene derecho a (`_settlePendingRewards`, L1487–1497; el gemelo de rendimiento puro, L1499–1517, usa $w^{\mathrm{p}}_{u}$, $A^{\mathrm{p}}$ y `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

y después de cada cambio de peso el escritor único vuelve a fijar el punto de control $c_{u} = \lfloor w_{u}A/10^{18}\rfloor$ (`_checkpoint`, L1736), de modo que una posición solo gana en las ventanas que se cierran después de que su peso esté establecido.

**Boost.** El peso es el stake de la posición multiplicado por un boost que es un polinomio del bloqueo por el que se le paga *actualmente* (`boostByDays`, L496–505; `_effectiveLockDays`, L1687; `_computeBoost`, L1695–1700). Con $\ell$ el bloqueo en días — cero una vez pasado el momento de desbloqueo, se haya normalizado o no la posición — con tope en 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

con $s_{u}$ el stake de la posición y $d_{u}$ su deuda. Valores exactos: $b(90) = 10{,}199$, $b(365) = 11{,}000$, $b(730) = 12{,}500$, $b(1{,}825) = 20{,}000$, $b(2{,}555) = 27{,}500$ bps. El peso de emisión se aplica al stake neto; el peso de rendimiento puro solo existe para una posición sin deuda — un prestatario no gana los intereses que paga.

### §25.4 Intereses: la curva y el índice

**La curva.** La utilización es $U = \min(\lfloor D \cdot 10^{18} / S \rfloor, 10^{18})$, con $D$ = `totalDebt` y $S$ = `totalStaked` ($U = 10^{18}$ si $S = 0 < D$; la tasa base si ambos son cero), y la tasa anual en bps tiene un codo en el 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

Las dos ramas se unen en el codo — $100 + 0.8 \times 500 = 500$ — y la curva alcanza $500 + 0.2 \times 72{,}500 = 15{,}000$ bps con utilización plena. Un préstamo no puede llevar el balance por encima del 75 % (E41), así que ningún préstamo entra por sí solo en la rama empinada.

**El índice.** Cada tramo transcurrido $\Delta t$ se sella con la tasa vigente a lo largo de él, antes de cualquier escritura en $D$ o $S$ (`_interestSlice`, L1589–1619; `_updateInterestIndex`, L1539–1583), con $Y$ = 365 días:

$$
\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$ fuera a superar $S$, el avance del índice se vuelve a derivar como $\delta = \lfloor S \cdot 10^{18}/D \rfloor$ y $\sigma$ se recalcula a partir de él, de modo que los cargos por posición nunca suman más que el débito global. La aritmética se satura a cero en lugar de revertir, así que los puntos de entrada que se ejecutan sobre ella no pueden congelarse, y bajo una emergencia el reloj avanza sin cobrar (L1547). Una sola función calcula el tramo para la ejecución y para cada cotización, así que ambas no pueden redondear de forma distinta.

**La división.** El tramo sale del stake y se reparte en el mismo paso (L1560–1580; `_dampPure`, L1623–1625), con $W_{\mathrm{p}}$ = `totalBoostedPure` y $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

así que $\sigma = \kappa_{r} + \eta + (\pi - \eta)$ exactamente; sin peso puro, todo $\pi$ va a la reserva. La limitación es la misma que la de la emisión (E28), con la misma constante.

**Atribución.** El lado global se liquidó cuando se devengaron los intereses; la parte propia de una posición se atribuye la próxima vez que se la toca (`_accrueInterestFor`, L1627–1671), con $I_{u}$ su punto de control del índice:

$$
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

Los intereses se pagan con cargo al stake; la deuda en sí no crece. Una posición sin deuda solo vuelve a fijar su punto de control, así que nunca hereda el crecimiento del índice de un periodo en el que no debía nada. Los intereses que una posición no puede pagar ya se distribuyeron de todos modos por E33; sumarlos de nuevo a $S$ mantiene $S = \sum_{u} s_{u}$, y la pérdida se registra por E37 en el momento en que se conoce.

### §25.5 La reserva, la solvencia y la cascada de pérdidas

**Conservación.** Sean $B$ el balance de BZPX del contrato, $D$ = `totalDebt`, $\mathit{BD}$ = `totalBadDebt`, $S$ = `totalStaked`, $\mathit{RR}$ = `rewardReserve`, $\mathit{PR}$ = `protocolReserve` y $\Pi$ = `totalRewardDistributed − totalRewardsPaid` (distribuido, aún no pagado); toda función que lleva el modificador `conserves` toma una instantánea de ambos lados antes de su cuerpo y revierte después de él salvo que se cumpla (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

Cada término es una única variable de estado no negativa, sumada sin suelo, así que la identidad es lineal en todos los regímenes — incluida la angustia terminal, en la que las herramientas de recuperación (`repay`, `liquidate`, `absorbBadDebt`) deben seguir funcionando — y solo se compara la variación a lo largo de una llamada, de modo que el historial no puede acumularse hasta producir una falsa alarma.

**Solvencia.** Lo que el contrato debe, y la prueba pública de si lo mantiene (`_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

*Derivación del vínculo con la deuda incobrable.* Donde $S + \mathit{RR} + \mathit{PR} \ge D$, E35 da $\Delta(B - \mathrm{owed} + \mathit{BD}) = 0$ (salvo $\varepsilon$) a lo largo de cada llamada protegida, de modo que $B - \mathrm{owed} = c - \mathit{BD}$, con $c$ el balance mantenido más allá del libro mayor. La deuda incobrable registrada es, por tanto, exactamente el término que empuja el balance hacia un incumplimiento grave, y el balance se lee insolvente en cuanto $\mathit{BD} > c + \varepsilon$. Bajo un incumplimiento grave, las puertas que pagarían a la par o añadirían dinero nuevo a un fondo corto revierten — `deposit`, `borrow`, `withdraw`, `claimRewards`, `claimPureYield` y la liquidación del rendimiento puro que hay dentro de ellos, `withdrawReserve`, `cancelEmergency` (L760, L826, L902, L932, L948, L1510, L539, L620) — mientras que `repay`, `liquidate` y `absorbBadDebt` siguen abiertas, y cualquiera puede llamar a `tripBreaker` para llevar el contrato a emergencia (L597).

**La cascada.** Una pérdida $\lambda$ surge solo en tres lugares: una liquidación cuya deuda supera el stake (L1101–1106, L1128), una salida de emergencia con patrimonio negativo (L704–709) e intereses que una posición no puede pagar (L1657–1663). Los tres aplican el mismo orden:

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

La reserva — formada con el factor de reserva de cada tramo de intereses (E33) y con los bonos de autoliquidación — absorbe primero; solo el resto no cubierto se convierte en deuda incobrable registrada. La recapitalización es una puerta pública que toma primero la reserva, luego como máximo lo que ofrezca el llamador y nunca más de lo que aún está sin cubrir (`absorbBadDebt`, L639–649), con $a$ el importe del llamador:

$$
\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

Revierte cuando $\rho + \gamma = 0$, no saca nada y no paga nada al llamador; E35 se cumple por construcción ($\Delta \mathrm{LHS} = \gamma - \rho - \gamma = -\rho = \Delta \mathrm{RHS}$). Cuando $\mathit{BD}$ vuelve a estar dentro del dust, `isSolvent()` se lee verdadero y el administrador puede cancelar la emergencia.

**Liquidación.** Una posición es liquidable si y solo si $d_{u} > 0$ y ($s_{u} = 0$ o $100\,d_{u} \ge 95\,s_{u}$) (`_isLiquidatable`, L1133–1137). Su cierre (`_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

El bono va al llamador, salvo cuando un prestatario cierra su propia posición, en cuyo caso se queda en `protocolReserve`. Los flujos ganados por la posición se pagan antes de cerrarla, y cualquier stake sobrante permanece como una posición sin deuda que conserva su bloqueo.

**Salida de emergencia.** Con $\mathrm{pot} = B$ y $E_{+}$ = `totalPositiveEquity` $= \sum_{u} \max(s_{u} - d_{u}, 0)$ tal como se registra, la salida paga (`emergencyWithdraw`, L658–725; el escalado, 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

donde $e_{u}$ es el término registrado de quien sale, sustituido por su patrimonio exacto tras el devengo. Con un balance solvente el escalado nunca se activa y el patrimonio neto se paga íntegro; con uno corto, cada salida recibe la misma fracción de su derecho, y una salida con patrimonio negativo registra su déficit mediante E37 (L704–709).

![FIG-21 — La cascada de pérdidas E37–E38: protocolReserve absorbe primero, el resto se registra como totalBadDebt y lo lee isSolvent(), y absorbBadDebt recapitaliza desde la reserva, luego desde el llamador, nunca más allá de lo que está sin cubrir; proporciones ilustrativas, sin importes de tokens.](https://blazephoenix.xyz/whitepaper/figs/es/fig-21.png)

**Topes de préstamo.** Un préstamo de $a$ debe cumplir ambos (`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

el primero sobre el stake neto de la posición, el segundo sobre todo el balance, mantenido por debajo del codo del 80 % de E31.

**Mantenimiento.** Cada transacción de usuario, salvo que el contrato esté en pausa o en emergencia, escanea además una ventana del registro de prestatarios (liquidando lo que tiene patrimonio negativo) y del registro de bloqueos (normalizando los bloqueos vencidos), cada una de tamaño (`_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

con $n$ el tamaño del registro y $t_{m}$ el último barrido; se ejecutan como máximo cuatro normalizaciones de bloqueo por transacción (L263). El calendario es una función pura del estado on-chain — sin keeper, sin gobernanza — y su gas está acotado en todos los casos.

**Invariantes.** El diseño añade nueve invariantes, INV-23 a INV-31 (Apéndice B): la identidad de conservación, el techo de emisión, el orden de pérdidas, las puertas cerradas bajo un incumplimiento, el límite de recapitalización, el escritor único de los pesos, los topes de préstamo, la salida de emergencia acotada y la regla de antigüedad del depósito.

### §25.6 Endurecimiento en V4

Tres cambios fusionados dieron forma al código fuente de V4 después del diseño que está desplegado en Base; cada uno se describe aquí al nivel del diseño que produjo.

- **#22 (`b44e238`, 2026-10-11).** Los intereses se sellan por tramo mediante una sola función compartida por la ejecución y por cada cotización (E32), de modo que una vista previa y una transacción calculan el mismo cargo, a la tasa vigente a lo largo del tramo. El canal de rendimiento puro se limita con el mismo piso de peso que la emisión (E33), de modo que un denominador residual no pueda capturar el flujo. Un prestatario que cierra su propia posición no recibe bono de liquidación. Se añadieron vistas de riesgo de solo lectura (`healthFactor`, `daysToLiquidation`, `maxBorrowOf`).
- **#26 (`7fc0c2a`, 2026-10-13).** Un único orden de pérdidas para los tres lugares donde se producen: primero la reserva, luego la deuda incobrable registrada (E37); una puerta pública de recapitalización (E38); las puertas que mueven valor cerradas bajo un incumplimiento grave (E36); un constructor que rechaza una dirección de token sin código; la comprobación de conservación emitida una sola vez como dos auxiliares internos en lugar de insertarse en línea en cada lugar protegido, con la identidad sin cambios; y una batería de pruebas de aseguramiento.
- **#25 (`cc614f9`, 2026-10-14).** Solo documentación: líneas de resumen breves en las guías y en las cabeceras de las pruebas. No cambió ningún código fuente del contrato.

### §25.7 Evidencia de pruebas

Contados en `origin/main` en `cc614f9`, a partir del árbol de código fuente, no de una ejecución: 37 archivos `*.t.sol` (`git ls-tree -r --name-only origin/main | grep -c '\.t\.sol$'`), que declaran 256 funciones `test*` y 14 `invariant*` (`git grep -E '^\s*function (test|invariant)' origin/main -- '*.t.sol'`, separadas por prefijo). El README del repositorio (línea 84) declara además un harness en Node de nueve suites. Son recuentos de lo que está declarado; este documento no informa de una tasa de aprobación de la suite de staking.

---

## Historial de revisiones

### Versión 2.3.0 (2026-10-14)

2.3.0 — añade el capítulo de diseño de Staking V4 (§25), con las ecuaciones E27–E42, los invariantes INV-23–INV-31 y las figuras FIG-19–FIG-21; deja sin efecto la afirmación, más abajo, de que el motor de staking ya no se describe en este documento. En lo demás solo cambian las etiquetas de versión, los rangos de las enumeraciones, las introducciones de los apéndices y las líneas del DOI; el resto es el texto de la 2.3.

### Versión 2.3 (2026-09-24)

La versión 2.3 continúa la línea 2.2; se retira la numeración 3.0 usada brevemente para un borrador de este texto, como lo fue antes la numeración 3.0 del 2026-09-01. Qué cambió respecto a la 2.2: las dieciocho innovaciones reciben nombre, se numeran de I-1 a I-18 y se enuncian con su matemática al principio del documento, con un marcador del historial medido; cada cifra se vuelve a medir contra `main` en `8916545`; el motor de staking ya no se describe aquí (lo recoge su propia especificación); la sección del token registra el pool único de Airdrop y Bounties de 130,000,000 BZPX; y la fe de erratas de la 2.2, más abajo, se incorpora al texto que corregía.

### Fe de erratas de la 2.2 (2026-09-07)

Aplicada tras una lectura hostil del texto del 2026-09-06 contra `main 8949a9d`; la línea de versión siguió siendo 2.2. Cada punto indica qué decía el texto y qué dice el checkout.

1. §4.2 — el recorte de frontera `sqrtBoundary` está definido en el Core pero aún no está conectado a la clasificación ni a la promesa en el marco; el texto decía que se aplicaba del lado de la promesa. Punto abierto: conectarlo (`Router._v4LegQuote`, rama de clasificación del Core), con un test escrito primero en rojo que entregue por debajo de la cotización neta más allá del búfer de E21 cuando una pata V4 sale de su rango.
2. §10.4 — el recorte de capacidad excluye las patas V4 (el Solver omite la lectura del saldo para los tipos 4 y 8); el texto decía que se recortaba toda pata concentrada. Punto abierto: una cota física para el singleton.
3. §5.4 — una clave V4 de comisión estática ignora la comisión de protocolo del manager (cero en todas las chains atendidas hoy); el texto llamaba inequívoca a la comisión de la clave.
4. §9.3 — el margen de desalojo baja hasta aproximadamente el 25 % (ocupante 51, recién llegado 64), no el 3 %; la cifra anterior violaba E13 y se copió de un comentario del código fuente.
5. §15 y el resumen — 43 funciones `invariant_*` (el comando impreso devuelve 43), 217 archivos `.t.sol`.
6. §14.1 — `HubE(6)` no existe; el agotamiento de factories es `HubE(4)` y el agotamiento de slots es `_canInsert`.
7. §14.2 — se retira “basta con una sola capa”: las capas acotan la alteración de deltas; el residuo de la sobrescritura de comisión de §5.4 queda acotado por el suelo.
8. §11.4, INV-21 — la regla de forma de la ruta es el orden canónico en toda la ruta (`sawHooked`), no “con hook solo en el último hop”.
9. §11.3, E24 — la holgura del suelo agregado del hop se divide por BPS: el 20 % de la pata atestada media.
10. §17.3 y Apéndice C — el arnés `FactoryCensus` vive en el árbol de despliegue privado; la tabla es reportada, no reproducible desde este checkout.
11. §17.5 — se nombra la referencia de gas (KyberSwap `MetaAggregationRouterV2`, fork de Base, 2026-08-22, Paraswap como segundo comparador) y la razón por transacción (2.7×) se imprime junto a la razón por pata; el arnés y el calldata capturado son privados, así que las cifras son reportadas, no reproducibles desde este checkout.
12. §17.2 — se retira “la primera agregación enrutada por V4 de la chain”; “exacta” se sustituye por la banda medida (0–1 bps).
13. INV-8, INV-10, INV-15, INV-20, INV-22 — las citas de dónde se imponen se corrigen a los tests y los códigos que las sustentan; INV-22 nombra los cuatro contratos desplegados.
14. §1 — el estudio de los agregadores se limita a los siete nombrados, según lo documentado el 2026-09-07.
15. Referencias 16 y 20 — EIP-1153 es de A. Akhunov y M. Salem; EIP-7702, de V. Buterin, S. Wilson, A. Dietrichs y M. Garnett.
16. §2.3, §15.11, §21 — los recuentos de calibración de instrumentos y de patrones de defecto se sustituyen por la propia regla de calibración.
- §14.2, Capa 2 (2026-09-07, admisión de hooks): la lista de permitidos cura la puerta automática, y la fijación del codehash pausa, en todas las puertas, un hook listado cuyo código se movió. Un hook cuyos bits de dirección no llevan ningún permiso de swap nunca se ejecuta en un swap y no necesita ni lista ni fijación. Un hook que se ejecuta en el swap y que nadie listó se acepta en la puerta explícita como elección firmada del llamador, acotado, como todo venue, por la promesa en el marco, la compuerta de atestación y `userMinOut`; la puerta automática nunca lo propone. Registrar un pool con hook admite y fija su hook, así que un pool con hook es un solo paso del operador. Las tres capas se mantienen; “cerrado por defecto” describe la puerta automática.

### Fe de erratas de la 2.2 (2026-09-24)

Aplicada tras la novena oleada de bounties; la línea de versión siguió siendo 2.2.

1. §4.2, y el punto 1 de la fe de erratas del 2026-09-07 — cerrado. Un pool V4 se cotiza recorriendo su propio libro: su precio, su liquidez, su bitmap de ticks y sus ticks, leídos mediante `extsload`, con la propia aritmética de pasos del PoolManager, tanto para la clasificación como para la promesa (`Core.v4WalkOut`). El recorte de frontera `sqrtBoundary` queda retirado. La afirmación de que el despachador no replica la aritmética de cruce de ticks ahora solo vale para la familia V3.
2. §10.4, y el punto 2 de la fe de erratas del 2026-09-07 — cerrado para el propósito al que servía: el recorrido cuenta solo la liquidez que el libro tiene en los ticks que cruza, así que una pata V4 queda acotada por lo que tiene su propio pool (`test_AOneTickRangeWithNothingBeyondPaysWhatItHolds`).
3. Punto 3 de la fe de erratas del 2026-09-07 — cerrado: una clave de comisión estática compone la comisión de protocolo del manager con la comisión LP de la clave en la dirección del swap (`Core.effV4Fee`; `test_StaticKey_ProtocolFee_ReachesThePromise`).
4. §4 — toda lectura que el camino de cotización hace de un pool tiene el gas limitado y copia un número fijo de palabras; las dos que eran llamadas de alto nivel (el `getAmountOut` del par Solidly, y `factory()` seguido de `getFee`) preguntan a través de `Core._askWord` (`test/APoolsAnswerIsBoundedByTheReader.t.sol`).

## Apéndice A · Las ecuaciones primitivas

Toda afirmación cuantitativa de este documento se reduce a una de las siguientes. Cada una se implementa una sola vez, en el contrato indicado, y se importa en todos los demás. La numeración E1–E22 es estable respecto a la edición anterior; E23–E26 son nuevas y se añaden al final. E27–E42 (2.3.0) son las del motor de staking, definidas en `src/BlazePhoenixStaking.sol` del repositorio de staking en `cc614f9` (§25); “Staking” en la cuarta columna nombra ese contrato, con sus números de línea.

| # | Nombre | Ecuación | Definido en | § |
|---|---|---|---|---|
| E1 | Meta-Ecuación | $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 del Solver | §3 |
| E2 | Salida de producto constante | $y = \lfloor x(\mathrm{BPS}-f)\,r_{\mathrm{out}} / (r_{\mathrm{in}}\,\mathrm{BPS} + x(\mathrm{BPS}-f)) \rfloor$ | Core `outV2` | §4.2 |
| E3 | Salida de liquidez concentrada (dentro del tick) | $\sqrt{P}' = L\sqrt{P}/(L + x_{f}\sqrt{P}/Q_{96})$, $y = L(\sqrt{P}-\sqrt{P}')/Q_{96}$; forma dual para la otra dirección | Core `outV3` | §4.2 |
| E4 | Slot base del storage de V4 | $\mathrm{slot} = \mathrm{keccak256}(\mathrm{abi.encode}(\mathrm{poolId}, 6))$; $\sqrt{P}$ en los bits [0,160), comisión LP en [208,232) de la palabra 0; $L$ en la palabra 3 | Core `v4SqrtAndLiq` | §5 |
| E5 | Invariante estable de Solidly | $k(x,y) = x^{3}y + xy^{3}$ | Core `_solK` | §6 |
| E6 | Paso de Newton de Solidly | semilla $y_{0} = Y$; paso ascendente $\max(1, \lfloor (K-k)/f' \rfloor)$; paso descendente $\max(1, \lfloor (k-K)/f' \rfloor)$, sustituido por $\lfloor y/2 \rfloor$ cuando cruzaría cero; salida en $\lvert y_{n+1}-y_{n}\rvert \le 1$ con un ajuste de una unidad en contra del tomador; tope 64 → semilla → cotización 0 | Core `_solY` | §6 |
| E7 | Normalización decimal | $X = r_{\mathrm{in}}10^{18-d_{\mathrm{in}}}$, $Y = r_{\mathrm{out}}10^{18-d_{\mathrm{out}}}$, desescalado tras la resolución | Core `_solidlyStable` | §6 |
| E8 | Derivación CREATE2 | $\mathrm{pool} = \mathrm{addr}_{20}(\mathrm{keccak256}(\mathtt{0xff} \Vert \mathrm{origin} \Vert \mathrm{salt} \Vert \mathrm{initCodeHash}))$ | Core `create2Address`, `deriveAddress` | §7 |
| E9 | Empaquetado del 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 | Campo de Vitalidad | $\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 | Decaimiento de la actividad | $V = \mathrm{swapCount} \gg \lfloor (t - t_{\mathrm{last}})/24{,}576 \rfloor$; 0 una vez que el desplazamiento supera 31 | Core `_decayedSwapCount` | §9.2 |
| E12 | Cubo de profundidad | $b = \min(15, \lfloor \log_{10}(d/10^{15}) \rfloor)$ | Core `depthBucket` | §9.2 |
| E13 | Umbral de desalojo | $2^{b(d_{\mathrm{new}})} > \Psi_{\mathrm{worst}} + \lfloor \Psi_{\mathrm{worst}}/4 \rfloor$ | Hub `_canInsert` | §9.3 |
| E14 | Compuerta de división | conservar la división si y solo si $\mathrm{out}_{\mathrm{split}} \ge \mathrm{out}_{\mathrm{single}}\cdot(1 + 25/10^{6})$, $\mathrm{out}_{\mathrm{single}}$ el mejor entre el candidato más profundo y el de mejor tasa a tamaño completo | Solver `_buildHop` | §10.4 |
| E15 | Suelo Iron-Law | $\mathtt{floorBps} = \max(9{,}600 - \mathtt{legShv} - \min(\delta, 10^{4}) - \sigma_{\ln}/10^{14},\ 8{,}000)$ | Core `ironFloorBpsShv` | §11.5 |
| E16 | Mínimo efectivo | $\mathtt{effMin} = \max(\mathtt{userMinOut},\ \mathtt{route.singleOutFloor},\ \lceil \mathtt{finalHopQuote}\cdot\mathtt{floorBps}/10^{4}\rceil)$; cuando se detecta fee-on-transfer, el segundo término se descarta y el tercero se reescala por la razón neta medida | Router `_execute` | §11.5 |
| E17 | Comisión de protocolo | $\mathrm{fee} = \lceil 28 \cdot \mathrm{base} / 10^{4} \rceil$, base = la entrada medida del hop de comisión (anclado), la salida bruta (ruta directa hacia una moneda puente) o la entrada medida de cada hop (agotamiento); $t_{1} = \lfloor 3{,}000\cdot\mathrm{fee}/10^{4} \rfloor$, $t_{2} = \mathrm{fee} - t_{1}$ | Router `_chargeHopFee`, `_payFee` | §13 |
| E18 | Compuerta de hooks | $(\mathrm{uint160}(\mathrm{hook}) \,\&\, \mathtt{0x3FFF}) \,\&\, ((1\ll 3)\mid(1\ll 2)) \ne 0 \Rightarrow$ pata rechazada, sin ninguna llamada | Core `hookAltersDeltas`; Router | §14.2 |
| E19 | Compuerta de comisión dinámica | $f_{\mathrm{eff}} = f_{\mathrm{key}}$ si es estática; no cotizable si el centinela viaja con una comisión de protocolo distinta de cero; en otro caso, la comisión LP vigente de `slot0` / `globalState` / el `fee()` del pool | Core `effV4Fee`, `quoteV3Fee` | §5.4 |
| E20 | Techo de comisión V2 | $f_{\mathrm{eff}} = f_{\mathrm{decl}}$ si $0 < f_{\mathrm{decl}} \le 100$ bps, si no 30 | Core `effV2Fee`; la misma regla para el fallback declarado de Solidly | §4.2 |
| E21 | Margen de seguridad de la vista previa | $s = \min(\max(0, n-2) + 5a,\ 10)$ bps | Quoter `_pack` | §12.1 |
| E22 | Elección de superficie | llevar la ruta en calldata si y solo si $p_{\mathrm{byte}} < \rho^{*}$; $\rho^{*}$ medido en 66.9–105.5 gas/byte según la forma de la ruta | `test/EquationBench.t.sol` | §17.6 |
| E23 | Descuento de pata por concentración | $\mathtt{legShv} = \sum_{\mathrm{hops}} \lfloor 200\,((\sum a)^{2} - \sum a^{2})/\sum a^{2} \rfloor$ | Core `legShaveBps` | §11.5 |
| E24 | Suelo agregado del hop | $\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 | Centro de credibilidad | la mediana ponderada por profundidad: ordenar $(r_{i}, d_{i})$ por $r_{i}$, devolver el primer $r_{i}$ en el que $\sum_{j \le i} d_{j} \ge \lceil \sum d / 2 \rceil$; banda $[0.95, 1.05] \times$ centro | Solver `_depthWeightedMedian` | §10.3 |
| E26 | Predicado del régimen de comisión | $\mathrm{feeHop} = \min\{h : \mathrm{isBridge}(\mathrm{hops}[h].\mathrm{tokenIn})\}$; cobrar en $h$ si y solo si $\neg\mathrm{feeOnOut} \wedge (\mathrm{feeHop} = \infty \vee h = \mathrm{feeHop})$; $\mathrm{feeOnOut} \iff$ un solo hop y $\mathrm{isBridge}(\mathrm{tokenOut})$ | Router `_execute` | §13.1 |
| E27 | Forma cerrada de la emisión | $E(t) = (T - (T \gg p)) + (R_{0} \gg p)(t - s - pP)$, $p = \lfloor (t - s)/P \rfloor$; $0$ hasta $s$; $T - (T \gg 8)$ desde $s + 8P$ | Staking `_emittedAt` (L1458–1467) | §25.3 |
| E28 | Ventana de emisión | $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 | Liquidación de recompensas | $\mathrm{due}_{u} = (\lfloor w_{u}A/10^{18} \rfloor - c_{u})^{+}$, luego $c_{u} \leftarrow \lfloor w_{u}A/10^{18} \rfloor$ | Staking `_settlePendingRewards`, `_settlePendingPureYield` (L1487–1517) | §25.3 |
| E30 | Boost por bloqueo y pesos | $b(\ell) = 10^{4} + \lfloor 750\ell/365 \rfloor + \lfloor 250\ell^{2}/365^{2} \rfloor$ bps, $\ell$ el bloqueo vigente; $w_{u} = \lfloor (s_{u} - d_{u})^{+} b/10^{4} \rfloor$; $w^{\mathrm{p}}_{u} = \lfloor s_{u} b/10^{4} \rfloor$ si y solo si $d_{u} = 0$ | Staking `boostByDays`, `_computeBoost` (L496–505, L1695–1700) | §25.3 |
| E31 | Curva de intereses | $r = 100 + \lfloor 500U/10^{18} \rfloor$ para $U \le 0.8 \cdot 10^{18}$, en otro caso $500 + \lfloor 72{,}500(U - 0.8 \cdot 10^{18})/10^{18} \rfloor$ bps al año | Staking `_interestRate` (L1768–1782) | §25.4 |
| E32 | Índice de intereses y porción | $\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$ y $\sigma$ se recalcula | Staking `_interestSlice`, `_updateInterestIndex` (L1539–1619) | §25.4 |
| E33 | Reparto de la porción | $\kappa_{r} = \lfloor 300\sigma/10^{4} \rfloor$ a la reserva; $\eta = \lfloor (\sigma - \kappa_{r})\min(W_{\mathrm{p}}, W_{\min})/W_{\min} \rfloor$ a los stakers puros; el resto a la reserva | Staking `_updateInterestIndex`, `_dampPure` (L1560–1580, L1623–1625) | §25.4 |
| E34 | Atribución de intereses | $i_{u} = \lfloor d_{u}(I - I_{u})/10^{18} \rfloor$, cargada a $s_{u}$; todo exceso sobre $s_{u}$ es una pérdida para E37 | Staking `_accrueInterestFor` (L1627–1671) | §25.4 |
| E35 | Guarda de conservación | $\lvert \Delta(B + D + \mathit{BD}) - \Delta(S + \mathit{RR} + \mathit{PR} + \Pi) \rvert \le 10^{10}$ | Staking `conserves` (L419–457) | §25.5 |
| E36 | Adeudado y solvencia | $\mathrm{owed} = (S + \mathit{RR} + \mathit{PR} - D)^{+} + \Pi$; violación dura si y solo si $B + 10^{10} < \mathrm{owed}$ | Staking `_owed`, `_hardBreach`, `isSolvent` (L1794–1824) | §25.5 |
| E37 | Cascada de pérdidas | $\kappa = \min(\lambda, \mathit{PR})$; $\mathit{PR} \mathrel{-}= \kappa$; $\mathit{BD} \mathrel{+}= \lambda - \kappa$ | Staking L1101–1106, L704–709, L1657–1663 | §25.5 |
| E38 | Recapitalización | $\rho = \min(\mathit{BD}, \mathit{PR})$, $\gamma = \min(a, \mathit{BD} - \rho)$; $\mathit{BD} \mathrel{-}= \rho + \gamma$ | Staking `absorbBadDebt` (L639–649) | §25.5 |
| E39 | Liquidación forzosa | liquidable si y solo si $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})$; pérdida $(d_{u} - s_{u})^{+}$ | Staking `_executeLiquidation`, `_isLiquidatable` (L1079–1137) | §25.5 |
| E40 | Quita de emergencia | $x_{0} = (s_{u} - d_{u})^{+}$, $E' = E_{+} - e_{u} + x_{0}$; $x = \lfloor x_{0}\,\mathrm{pot}/E' \rfloor$ si $\mathrm{pot} < E'$, en otro caso $x_{0}$; $x \le \mathrm{pot}$ | Staking `emergencyWithdraw` (L689–696) | §25.5 |
| E41 | Techos de préstamo | $d_{u} + a \le \lfloor 50(s_{u} - d_{u})^{+}/100 \rfloor$ y $\lfloor (D + a) 10^{18}/S \rfloor \le 0.75 \cdot 10^{18}$ | Staking `borrow`, `_ltvCap` (L834–840, L1908–1909) | §25.5 |
| E42 | Presupuesto de mantenimiento | $c(n) = \min(1 + \lfloor n/50 \rfloor + \lfloor (t - t_{m})/900\ \mathrm{s} \rfloor,\ 10,\ n)$ por registro | Staking `_windowBudget` (L1148–1157) | §25.5 |

## Apéndice B · Los invariantes

Estas son las propiedades que se cumplen en todo estado alcanzable: cada una se impone por la forma del código en el sitio indicado, y una transacción que violaría alguna no se ejecuta. INV-1–INV-18 conservan su numeración de la edición anterior, con la columna de imposición llevada al código; INV-19–INV-22 son nuevos. INV-23–INV-31 (2.3.0) son los del motor de staking (§25), impuestos en `src/BlazePhoenixStaking.sol` del repositorio de staking en `cc614f9`.

| # | Invariante | Imposición |
|---|---|---|
| INV-1 | El Router no retiene fondos entre transacciones; su saldo en reposo es cero. | Barridos y pago con alcance de línea base (§11); `invariant_RouterHoldsNothing`, `HoldsNothingBeyondTheSeed` |
| INV-2 | Todo swap es atómico: no existe ningún estado en el que parte de una operación se haya liquidado. | Semántica de transacción de la EVM; un solo marco |
| INV-3 | Sin oráculo: ninguna fuente de precios externa es entrada de la admisión, la ordenación, la división, el suelo ni las comisiones. | No existe ninguna llamada a una fuente de precios en los cinco contratos |
| INV-4 | La tasa de comisión, el reparto de la comisión y las constantes del suelo no tienen setters en el bytecode desplegado. | Constantes en tiempo de compilación en el Core y el Router (§13) |
| INV-5 | Los campos de la ruta aportados por el llamador pueden endurecer la protección, nunca relajarla. | Máximo de tres términos de E16; la compuerta de cobertura eleva una atestación, nunca la reduce (§11) |
| INV-6 | Un swap con mínimo cero no se ejecuta. | `RouterE(10)` en cada puerta |
| INV-7 | Ψ clasifica y trunca candidatos; ninguna cotización, suelo ni asignación lo lee. | Flujo de datos: nada posterior a la clasificación consume Ψ (§9.6) |
| INV-8 | El registro de un par contiene como máximo dieciséis pools, y el desalojo conserva los más aptos según el Ψ completo con el margen de E13. | Hub `_canInsert`; `invariant_neverExceedsMaxSlots` (el tope); `test_RecordSwap_EvictsWeakestWhenFullAndNewcomerClearsMargin` (el desalojo) |
| INV-9 | Ninguna dirección sin bytecode de runtime entra en una ruta. | `hasCode` en cada derivación (§7) |
| INV-10 | El callback universal paga solo al pool comprometido para la pata en curso, solo mientras haya una pata en vuelo, y nunca más que el presupuesto registrado de la pata. | Contexto transitorio de la pata; `RouterE(6)` para el pool, `RouterE(8)` para el presupuesto (§11.4) |
| INV-11 | Un hook de V4 cuya dirección declara permisos que alteran deltas se rechaza antes de que se mueva ningún token, sin llamarlo. | E18; `RouterE(9)`; Solver `_topKPools` |
| INV-12 | Ningún producto de precios puede desbordar: toda multiplicación peligrosa pasa por `mulDiv` de 512 bits, y un estado que no se puede acotar cotiza cero. | Core (§4.2, §6) |
| INV-13 | El redondeo va contra la sobrepromesa: las cotizaciones y los pagos se redondean a la baja, y los suelos de protección y la comisión, al alza. | Core `mulDiv` / `mulDivUp` en cada sitio |
| INV-14 | Donde no se puede demostrar una cota, el cálculo devuelve cero y se cede el venue — nunca una conjetura, nunca un revert programable dentro de una cotización. | Postura de fallo cerrado en todo el Core; `_solKFits` (§6) |
| INV-15 | La base de la comisión es el pull medido, limitado por la entrada comprometida de la ruta — nunca una salida declarada; nada lee `route.totalOut`. | `_chargeHopFee`; `test_FeeBase_IgnoresLiedAboutTotalOut` |
| INV-16 | La renuncia es de un solo sentido: el flag se escribe `true` en un sitio y `false` en ninguno; después de ella solo queda el nivel de curador de solo crecimiento, y una factory o un hook movidos no pueden rearmarse. | `renounceControl` en Hub y Router (§14.7) |
| INV-17 | Una comisión de respaldo declarada de producto constante o Solidly solo se cree dentro de un techo de 100 bps; fuera de él, los 30 bps canónicos fijan el precio del pool. | E20 |
| INV-18 | Ningún hop lleva más de cuatro patas ni ninguna ruta más de once en el plan del Solver; el Router acepta como máximo tres hops y cinco patas por hop. | Presupuestos del Solver; Router `_execute` → `RouterE(3)` |
| INV-19 | Un pool entra en el registro solo si demuestra que opera el par: `token0()`/`token1()` para los tipos con forma de par, re-derivación del id del pool para V4; un pool preguntado demuestra lo mismo antes de que el descubrimiento lo liste. | Hub `recordSwap`, `_probe`; `invariant_ActiveEntriesTradeThePair` |
| INV-20 | Una liquidación paga la comisión de protocolo exactamente una vez en una ruta anclada y una vez por hop en una agotada; una liquidación que no pagó nada no existe. | Libro mayor de comisiones, `RouterE(15)`/`RouterE(16)`; `invariant_SettledSwapEmitsExactlyOneFee` (anclada); `test/FeeSeals.t.sol`, `test/ExhaustionRegimePreviewParity.t.sol` (agotamiento) |
| INV-21 | Los hops se encadenan — la entrada de cada hop es la salida del anterior — y ninguna pata sin hook se ejecuta después de una con hook, en este hop o en cualquier hop posterior (`sawHooked` vale para toda la ruta). | Router `_execute` → `RouterE(3)`; `sawHooked` |
| INV-22 | Los cuatro contratos desplegados — Hub, Solver, Router, Quoter — se entregan bajo la compuerta de 24,000 bytes del proyecto en el perfil release, la librería Core se mide aparte (6,477 bytes de 24,576), y la suite corre en ese perfil. | `DeployedSizeGate`; `profile_parity.py` |
| INV-23 | En cada punto de entrada de staking que lleva `conserves`, el saldo más la deuda más la deuda incobrable registrada cambian en la misma cantidad que el stake más ambas reservas más las recompensas distribuidas pero no pagadas, dentro de 1e10 wei (E35). | Staking `conserves`, L419–457 → `Staking__InvariantBreached` |
| INV-24 | La emisión programada nunca supera `TOTAL_REWARDS − (TOTAL_REWARDS >> 8)`; la financiación acumulada nunca supera `TOTAL_REWARDS`; ninguna ventana de emisión paga más de lo que contiene `rewardReserve`. | `_emittedAt` L1458–1467; `fundEmission` L518 → `Staking__CapExceeded`; `_updateGlobal` L1442 |
| INV-25 | Toda pérdida realizada — un déficit de liquidación, una salida de emergencia bajo el agua, intereses incobrables — se carga primero a `protocolReserve`; solo el resto no cubierto se añade a `totalBadDebt` (E37). | Staking L1101–1106, L704–709, L1657–1663 |
| INV-26 | Mientras el saldo más 1e10 esté por debajo de `owed`, no se ejecuta ningún depósito, toma de préstamo, retiro, reclamo de recompensas o de pure-yield, retiro de reserva ni cancelación de emergencia (E36). | Compuertas `_hardBreach` L760, L826, L902, L932, L948, L1510, L539, L620 → `Staking__InvariantBreached` |
| INV-27 | `absorbBadDebt` no saca ningún BZPX, no paga nada al llamador y reduce `totalBadDebt` como máximo en su valor actual: primero la reserva, con el monto del llamador limitado al resto no cubierto (E38). | `absorbBadDebt` L639–649 |
| INV-28 | Los totales del peso de emisión, del peso de pure-yield y del patrimonio positivo los escribe solo `_applyBoost`, que sustituye el término registrado de una posición por el recalculado; un bloqueo gana boost solo mientras está vigente (E30). | `_applyBoost` L1726–1734; `_effectiveLockDays` L1687–1690 |
| INV-29 | Una toma de préstamo se ejecuta solo si la nueva deuda es como máximo la mitad del stake neto de la posición antes de ella y la utilización del libro después de ella es como máximo el 75 % (E41). | `borrow` L834 → `Staking__LTVExceeded`; L838–840 → `Staking__UtilTooHigh` |
| INV-30 | Una salida de emergencia paga como máximo el patrimonio neto de la posición y como máximo el saldo del contrato, escalado por pot/E′ cuando el saldo no alcanza el patrimonio registrado (E40). | `emergencyWithdraw` L689–696 |
| INV-31 | Ningún retiro, reclamo de pure-yield ni cambio de bloqueo se ejecuta dentro de `MIN_DEPOSIT_BLOCKS` = 10 bloques del último depósito de la posición. | L894, L947, L971 → `Staking__FlashLoanProtection` |

## Apéndice C · Comandos de reproducción

Todo número de este documento que proviene del repositorio se reproduce con uno de los siguientes comandos, desde un checkout limpio de `Blaze-Phoenix-Dex` en `8949a9d` con Foundry y Python 3 instalados; las suites fork necesitan una clave RPC de archivo en `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
```

Los hashes del Ejemplo resuelto 4 se reproducen con cualquier implementación de keccak-256 sobre `abi.encode(currency0, currency1, fee, tickSpacing, hooks)`; las cifras del documento se produjeron con un Keccak-f[1600] en Python puro contrastado con los vectores de prueba de la cadena vacía y de `"abc"` y con el selector `extsload(bytes32)` que el Core lleva fijado en el código.

## Glosario

**Los operadores.**

| Símbolo | Nombre | En una línea |
|---|---|---|
| Ω | Despachador Óctuple | fija el precio de cada pata — forma cerrada donde una forma cerrada es exacta, preguntar al venue donde el bytecode propio del venue es la única fuente honesta; “Óctuple” por herencia histórica, seis tipos activos hoy |
| 𝒟 | Derivación Determinista | calcula las direcciones de los pools con aritmética CREATE2 donde el despliegue es reproducible, pregunta y luego demuestra el par donde no lo es, deriva y demuestra los ids de pool del singleton de V4; `hasCode` descarta lo que no tiene bytecode |
| Φ | Suelo Iron-Law | el suelo de retención propio del protocolo: 96 % de base, que se relaja con el impacto medido y la concentración medida, con un recorte duro en el 80 %, aplicado a la cotización en el marco del último hop |
| Ψ | Campo de Vitalidad | calidad del venue calculada solo a partir del Monoslot; clasifica y trunca el conjunto de candidatos — puede ocultar un venue, nunca valorarlo mal |
| Ξ | proyector antiestafa | retira de la maximización, antes de la clasificación, cualquier ruta que toque un hook que altera deltas o no admisible, sin llamar al hook |

**Los términos.** **Régimen anclado** — la comisión se cobra una vez, sobre la primera moneda puente que tiene el Router (o sobre la salida de una ruta directa hacia una moneda puente). **Banda de credibilidad** — ±5 % en torno a la mediana ponderada por profundidad de las tasas marginales de los candidatos; inerte en un conjunto de un solo candidato. **Compuerta de cobertura** — el límite atestado de una pata se eleva a la cotización medida cuando la atestación cubre menos de la mitad de ella. **Pase Exacto** — la vista previa exacta por pool del Quoter: el swap propio del venue ejecutado y revertido por extracción del revert; neta de la comisión desde 2026-09-03. **Régimen de agotamiento** — ninguna moneda puente como entrada de ningún hop: cada hop paga 28 bps sobre su propia entrada medida; inmune por construcción a un prefijo sin valor. **Libro mayor de comisiones** — un recuento transitorio de pagos de comisión por liquidación, que rechaza cero y rechaza dos en una ruta anclada. **Diseño Metrológico** — construir el contrato como un instrumento: colapsar la diversidad en coordenadas, medir toda coordenada que pueda medirse, hacer monótono el resto. **Meta-Ecuación** — todo el agregador como una sola maximización, un producto para que un único factor que falle anule la ruta. **Monoslot** — todo el estado de enrutamiento de un pool en una palabra de 256 bits, sin precio dentro. **Doctrina del Callback Único** — un solo `fallback` responde al callback de contabilidad flash de todo venue con forma V3, protegido de tres maneras. **Puerta de un Solo Sentido** — la renuncia como bytecode: el nivel de control muere, el nivel de curador de solo crecimiento permanece. **Prueba de par** — un pool demuestra su `token0`/`token1` (o su id re-derivado) antes de que el registro lo almacene o el descubrimiento lo liste. **Distancia de proyección** — a qué distancia está el objeto que observa una negativa del objeto que decide. **Registro Autorreparable** — el protocolo aprende la liquidez operándola: dieciséis slots por par, desalojo ordenado por aptitud, decaimiento por aritmética. **Regla del Excedente** — la parte del protocolo es de 28 bps de una única base medida y nada que escale con la brecha entre cotización y entrega. **Tercera vía** — cualquier resultado de una fixture que no sea ni una liquidación dentro de los suelos sin nada que quede en el Router ni una negativa con un selector nuestro.

## Referencias

1. G. Angeris, A. Evans, T. Chitra, S. Boyd. *Optimal Routing for Constant Function Market Makers.* arXiv:2204.05238, 2022 (ACM EC '22). — El enrutamiento sobre CFMM como programa convexo; el óptimo al que se queda corto el asignador proporcional de §10.4.
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). — El algoritmo de descomposición dual para el mismo problema (§23).
3. H. Adams, N. Zinsmeister, D. Robinson. *Uniswap v2 Core.* 2020. https://uniswap.org/whitepaper.pdf — La referencia del producto constante y la comisión por defecto de 30 bps de §4.2.
4. H. Adams, N. Zinsmeister, M. Salem, R. Keefer, D. Robinson. *Uniswap v3 Core.* 2021. https://uniswap.org/whitepaper-v3.pdf — Liquidez concentrada, `slot0`, punto fijo Q64.96 (§4.2, §15.6).
5. Uniswap Labs (H. Adams et al.). *Uniswap v4 Core.* 2024. https://uniswap.org/whitepaper-v4.pdf — El PoolManager singleton, la contabilidad flash y la codificación en la dirección de los permisos de hook (§5, §14.2).
6. M. Egorov. *StableSwap — Efficient Mechanism for Stablecoin Liquidity.* 2019. https://curve.fi/files/stableswap-paper.pdf — El invariante amplificado de la familia extirpada en §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. — El adversario de ordenación que §14.3 acota en lugar de pretender eliminarlo.
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. — Ataques sandwich medidos y formalizados (§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. — El marco de la forma cerrada de un solo tick como abstracción sólida (§23).
10. M. R. Clarkson, F. B. Schneider. *Hyperproperties.* Journal of Computer Security 18(6), 2010. DOI 10.3233/JCS-2009-0393. — Propiedades de conjuntos de trazas; la clase a la que pertenecen los hallazgos de redistribución (§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. — El segundo juez de §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. — El análisis de mutación y la hipótesis de acoplamiento (§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. — El ancestro de §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. — Matrices de cobertura y cobertura de interacciones 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. — Por qué no se enuncia ninguna probabilidad de corrección (§15.11).
16. A. Akhunov, M. Salem. *EIP-1153: Transient Storage Opcodes.* https://eips.ethereum.org/EIPS/eip-1153 — El contexto de la pata, el libro mayor de comisiones y el bloqueo de reentrada (§11.4, §13.2).
17. V. Buterin. *EIP-170: Contract Code Size Limit.* https://eips.ethereum.org/EIPS/eip-170 — El muro de 24,576 bytes (§16.3).
18. V. Buterin. *EIP-1014: Skinny CREATE2.* https://eips.ethereum.org/EIPS/eip-1014 — La aritmética de direcciones 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 puerta de 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 — Las cuentas delegadas llaman a las mismas puertas (§11.1).
21. *ERC-7201: Namespaced Storage Layout.* https://eips.ethereum.org/EIPS/eip-7201 — El espacio de nombres de storage del Hub (§16.1).
22. MariaDB Corporation Ab. *Business Source License 1.1.* https://mariadb.com/bsl11/ — La licencia del código.
23. Fable & Mitra. *BlazePhoenix-Dex: an on-chain DEX aggregator with measured routing.* Repositorio, `main` 8949a9d, 2026-09-05. https://github.com/blazephoenixxyz-crypto/Blaze-Phoenix-Dex — La fuente canónica de cada afirmación de este documento; el Apéndice C reproduce los números a partir de ella.
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 cota de la mediana ponderada por liquidez detrás de E25 (§10.3, §23).
25. *Ormer: A Manipulation-resistant and Gas-efficient Blockchain Pricing Oracle for DeFi.* arXiv:2410.07893. — Un oráculo on-chain que estima la mediana (§23).
26. *From Means to Medians: Optimal Benchmark Design.* arXiv:2506.22142. — Qué benchmarks son más difíciles de manipular (§23).
27. *Optimal Routing across Constant Function Market Makers with Gas Fees.* arXiv:2603.02844. — Enrutamiento con gas (§23).
28. *Routing Frictions and Executable Liquidity in Fragmented Markets.* arXiv:2609.19013. — Liquidez anunciada frente a liquidez ejecutable (§23).

## Cómo citar

Mitra. *BlazePhoenix Technical Whitepaper: an on-chain liquidity aggregator whose quote and execution are one computation.* Versión 2.3.0, 2026-10-14. Zenodo. DOI [10.5281/zenodo.23084091](https://doi.org/10.5281/zenodo.23084091) (el v2.3 depositado). Todas las versiones: [10.5281/zenodo.23084090](https://doi.org/10.5281/zenodo.23084090). DOI de concepto de la primera serie de depósitos (v2.2): [10.5281/zenodo.22526574](https://doi.org/10.5281/zenodo.22526574). BibTeX y otros formatos: https://blazephoenix.xyz/research/whitepaper
