<!-- BlazePhoenix — Technical Whitepaper, версия 2.3.0 (машиночитаемый полный текст) -->
> Канонический PDF: https://blazephoenix.xyz/docs/BlazePhoenix_Whitepaper.pdf
> DOI: https://doi.org/10.5281/zenodo.23084091 (депонированная версия, v2.3) · все версии: https://doi.org/10.5281/zenodo.23084090 · концептуальный DOI первой серии депозитов (v2.2): https://doi.org/10.5281/zenodo.22526574
> Litepaper (сопроводительный текст простым языком): https://blazephoenix.xyz/docs/BlazePhoenix_Litepaper.pdf
> Verification Report и Swap Tests (сентябрь 2026) — измерения, на которые ссылается эта работа:
>   https://blazephoenix.xyz/verification-report-2026-09.md · https://blazephoenix.xyz/swap-tests-report-2026-09.md
> Этот файл — полный текст whitepaper в формате markdown, опубликованный для
> обработки ИИ и цитирования. Лицензия на содержимое: CC BY 4.0 — цитируйте с
> указанием BlazePhoenix и ссылкой. Исходный код на Solidity, который описывает
> работа, распространяется под BUSL-1.1 до 2030 года: цитируйте архитектуру, не развёртывайте и не
> клонируйте его. Машиночитаемые точки доступа: /llms.txt · /facts.json · /knowledge-graph.jsonld
> Цитируемые перечни внутри: инновации I-1–I-18, примитивные уравнения E1–E42
> (Приложение A) и обеспечиваемые инварианты INV-1–INV-31 (Приложение B).

# BlazePhoenix-Dex

*Перевод английского оригинала v2.3.0; английский текст имеет преимущественную силу.*

**Котировка и есть сделка.**

**Измеряемая биржа: ончейн-агрегатор ликвидности, у которого котировка и исполнение — одно вычисление**

*Technical Whitepaper — версия 2.3.0*

| | |
|---|---|
| Версия | 2.3.0 — 2026-10-14 |
| Дата | 2026-10-14 |
| Автор | Mitra |
| DOI | [10.5281/zenodo.23084091](https://doi.org/10.5281/zenodo.23084091) (v2.3) · все версии [10.5281/zenodo.23084090](https://doi.org/10.5281/zenodo.23084090) · концептуальный DOI первой серии депозитов (v2.2) [10.5281/zenodo.22526574](https://doi.org/10.5281/zenodo.22526574) |
| Организация | BlazePhoenix — https://blazephoenix.xyz |
| Лицензия на текст | CC BY 4.0 — цитируйте с указанием авторства и ссылкой |
| Лицензия на код | BUSL-1.1 (Change Date 2030-06-01) — цитируйте архитектуру, не развёртывайте повторно |
| Репозиторий | `Blaze-Phoenix-Dex`, `main` на `8916545`, 2026-09-24 — https://github.com/blazephoenixxyz-crypto/Blaze-Phoenix-Dex |

> Цену, которую вы видите, вычисляет тот же публичный код, что исполняет вашу сделку, в том же фрейме, где она исполняется. Никто — даже авторы — не может изменить комиссию или нижние границы: они вкомпилированы.

## Десять строк. Без маркетинга.

1. **Никакой сервер не выбирает ваш маршрут.** Его выводит транзакция, которая его исполняет, из публичного состояния пулов.
2. **Никакой оракул не оценивает вашу сделку.** Это делают пулы — прочитанные во фрейме, где происходит расчёт.
3. **Котировка и нижняя граница — один вычислитель.** Нет шва, через который цена могла бы уплыть.
4. **Ваш минимум обязателен.** Router отклоняет своп без него.
5. **Заявления пула подделать бесплатно; его измеренный выход — нет.** Маршрутизация работает на втором.
6. **Комиссия, распределение и нижние границы вкомпилированы.** Сеттера не существует. Никто — даже авторы — не может их изменить.
7. **Нет прокси, нет пути обновления, нет `selfdestruct`, нет баланса в покое.** Между сделками в Router нечего украсть.
8. **Враждебный пул стоит вам цены, а затем откатывается.** Он никогда не доставит меньше вашей нижней границы.
9. **Каждое число здесь напечатано рядом со своим знаменателем** — и рядом с описанием того, как его можно было бы подделать.
10. **Если сегодня ночью исчезнут все наши серверы, завтра контракты будут котировать, маршрутизировать и исполнять.**

Всё, что ниже, — доказательство: строка кода, которая обеспечивает каждое утверждение, и предел, который его ограничивает.

## Аннотация

BlazePhoenix-Dex — ончейн-агрегатор ликвидности, построенный на одном утверждении: котировка и её исполнение — одно и то же вычисление. Маршрут выводится внутри транзакции, которая его исполняет, из публичного состояния пулов, библиотекой без состояния, которую любой читатель может запустить заново; число, обязывающее при расчёте, заново выводится тем же диспетчером в исполняющем фрейме. Нет сервиса маршрутизации, нет ценового оракула, нет кипера и нет пути обновления. Эта работа специфицирует пять контрактов — Core, Hub, Solver, Router, Quoter — и операторы Мета-уравнения: Восьмеричный диспетчер Ω, Детерминированный вывод 𝒟, нижнюю границу Iron-Law Φ, Поле жизнеспособности Ψ и антимошеннический проектор Ξ. За ними стоит одна дисциплина — Метрологический дизайн: измерять каждую координату, которую можно измерить, а остальное делать монотонным, чтобы ложный ввод вредил только тому, кто его предоставил. Каждая гарантия сформулирована вместе с местом её обеспечения и её пределом. Аппарат верификации напечатан со своими знаменателями: 1,609 объявленных тестов в 236 файлах, 268 из 268 отобранных мутантов убиты, 14 из 15 мутантов, нацеленных на 43 инварианта с состоянием, замечены, покрывающие массивы силы 2 (63 строки, 258 пар) и 3 (168 строк, 1,636 троек), 14 + 5 метаморфных отношений, три сборки оптимизатора в согласии и исследование на 240 выборках о том, как стареет котировка. Дизайн держат восемнадцать именованных инноваций — от Мета-уравнения до иммунитета через исчерпание; каждая каталогизирована со своей математикой до начала спецификации. Поколение, работающее в Base, Ethereum, Optimism и Arbitrum, — V1; эта работа специфицирует V2, проверенное от начала до конца на форках живой ликвидности в каждой сети, которую оно обслуживает.

## Как читать эту работу

Каждый механизм сначала получает простую фразу, а затем точную, поэтому работу можно читать на двух уровнях: простые фразы и рисунки дают архитектуру; точные фразы, таблицы и приложения позволяют её проверить. Каждая гарантия — одна фраза: утверждение, место, которое его обеспечивает, и предел, который его ограничивает. Шесть разобранных примеров проводят арифметику вручную: котировка постоянного произведения (§4), разбиение между двумя пулами (§10), нижняя граница Iron-Law на двуххоповом маршруте (§11), идентификатор пула Uniswap V4, выведенный из его ключа (§5), два режима комиссии на одном маршруте (§13) и чтение расчёта (§11). Числа напечатаны рядом со знаменателем, относительно которого они измерены, а команда, воспроизводящая их, приведена в Приложении C. Репозиторий каноничен: где этот текст и исходный код расходятся, прав исходный код.

## Инновации

BlazePhoenix-Dex — не агрегатор с лучшим сервером. Это агрегатор **без сервера** — и чтобы к этому прийти, понадобился набор механизмов, которых раньше не существовало. Дизайн держат восемнадцать из них. Каждый перечислен здесь: что это, какая математика его определяет, почему он важен для трейдера и в каком разделе он специфицирован полностью.

### I-1 · Котировка ≡ Исполнение

**Что.** Число, которое вам котируют, и число, которое обязывает вашу сделку, даёт один вычислитель — диспетчер Ω в Core, — запускаемый один раз для планирования и снова внутри исполняющего фрейма для расчёта. Второй модели ценообразования в системе нет нигде.

**Математика.** Для каждой ноги $\ell$ планировщик и исполнитель вычисляют одну и ту же функцию $\Omega(p_{\ell}, x_{\ell})$ на одном и том же публичном состоянии; нижняя граница маршрута заново выводится из этого значения во фрейме (E15, E16) и никогда не читается из маршрута.

**Почему это важно.** Любому классу эксплойтов агрегаторов, живущему в зазоре между «что сказал сервер» и «что сделала сеть», негде жить. Измерено: котировки без дрейфа исполняются ровно по прогнозу **64 раза из 64**, на моках и в живом Base. (§3, §11, §12)

### I-2 · Ончейн-солвер, вызов бесплатный

**Что.** `Solver.findBestRoutePlan(tokenIn, tokenOut, amountIn)` — это `view`-функция. Любой получает лучший маршрут без доверия бесплатным `eth_call` — без API-ключа, без релейера, без подписи, — а `swapBestExactIn` решает и исполняет атомарно в одной транзакции.

**Математика.** Весь Solver — это условная максимизация Мета-уравнения (E1) по пространству маршрутов $\mathcal{R}_{\mathcal{D}}$, исполняемая как детерминированный конвейер: вывести → ранжировать → зондировать → фильтровать → распределить → пропустить через шлюз → установить нижнюю границу.

**Почему это важно.** Интеллект маршрутизации в отрасли работает на закрытых серверах, число строк которых нельзя проверить. Наш — это 29,183 байта публичного байткода, который любой может запустить заново и получить тот же ответ. (§2, §10, §11.1)

### I-3 · Мета-уравнение

**Что.** Весь агрегатор, записанный одним выражением.

**Математика.**
$$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)$$

**Почему это важно.** Форма произведения — это свойство безопасности: один несработавший фактор — нога с нулевой котировкой, пул без репутации, маршрут ниже своей нижней границы, враждебный хук — обнуляет оценку маршрута ровно до нуля. Никаких мягких штрафов, никаких настраиваемых весов, никакого маршрута, проскользнувшего «почти». (§3)

### I-4 · Восьмеричный диспетчер Ω

**Что.** Шесть типов площадок — Uniswap V2, V3, V4, Algebra, Solidly stable и Solidly volatile — сводятся к четырём математическим семействам за одной таблицей атрибутов и одним диспетчером.

**Математика.** Постоянное произведение $y = \lfloor x(\mathrm{BPS}-f)\,r_{\mathrm{out}} / (r_{\mathrm{in}}\mathrm{BPS} + x(\mathrm{BPS}-f)) \rfloor$ (E2); концентрированная ликвидность в Q64.96, $\sqrt{P}' = L\sqrt{P}/(L + x_{f}\sqrt{P}/Q_{96})$, $y = L(\sqrt{P}-\sqrt{P}')/Q_{96}$ (E3); каждое опасное произведение вычисляется через 512-битный `mulDiv`.

**Почему это важно.** Фрагментация оказывается проблемой языка, а не математики. Одна проверенная формула на семейство оценивает любую площадку в любой сети; новый форк известного семейства — это строка таблицы, а не новый код. (§4)

### I-5 · Детерминированный вывод 𝒟

**Что.** Пулы **вычисляются**, а не запрашиваются. Hub выводит адрес каждого кандидата из фабрики, пары и init-code hash и допускает только адреса, несущие runtime-байткод.

**Математика.** $\mathrm{pool} = \mathrm{addr}_{20}(\mathrm{keccak256}(\mathtt{0xff}\,\Vert\,\mathrm{origin}\,\Vert\,\mathrm{salt}\,\Vert\,\mathrm{initCodeHash}))$ (E8).

**Почему это важно.** Ответ фабрики можно подделать; арифметику — нельзя. Обнаружение не требует внешнего вызова на кандидата и работает одинаково в любой EVM-сети, а менеджеры пулов и фабрики — это конфигурация времени выполнения, никогда не зашитая в код. (§7)

### I-6 · Uniswap V4 целиком внутри фрейма

**Что.** У пулов V4 нет адреса, и они не отвечают на вызовы. BlazePhoenix выводит их идентификаторы, **доказывает** существование каждого по хранилищу PoolManager, читает цену и ликвидность через `extsload`, проходит книгу тиков на протяжении до шестнадцати участков, проводит расчёт через синглтон — включая нативный ETH — и отсеивает хуки одной лишь арифметикой.

**Математика.** Базовый слот хранилища $\mathrm{slot} = \mathrm{keccak256}(\mathrm{abi.encode}(\mathrm{poolId}, 6))$, где $\sqrt{P}$ в битах [0,160), а комиссия LP — в [208,232) (E4); шлюз хуков $(\mathrm{uint160}(\mathrm{hook})\,\&\,\mathtt{0x3FFF})\,\&\,((1\ll3)\mid(1\ll2)) \ne 0 \Rightarrow$ отказ, без вызова (E18); шлюз динамической комиссии E19.

**Почему это важно.** Вывести, доказать, оценить, отсеять и рассчитать V4, не доверяя ни одному ответу площадки, — самая глубокая интеграция V4, какая возможна для агрегатора, и вся она проверяема в сети. (§5)

### I-7 · Солвер Solidly stable

**Что.** Точный солвер с отказом в закрытое состояние (fail-closed) для квартичной стабильной кривой, которую используют площадки класса Aerodrome и Velodrome.

**Математика.** Инвариант $k(x,y) = x^{3}y + xy^{3}$ (E5), решаемый ограниченной итерацией Ньютона со сдвигом на одну единицу против тейкера и пределом в 64 шага (E6), после нормализации десятичных знаков до 18 разрядов (E7).

**Почему это важно.** Поток стейблкоинов и ликвидного стейкинга сосредоточен на этих кривых, где они отдают на выходе почти весь вход. Солвер котирует их точно — а там, где границу нельзя доказать, возвращает ноль, а не догадку. (§6)

### I-8 · Моно-слот (Monoslot)

**Что.** Всё маршрутизационное состояние пула — активность, класс глубины, комиссия, тип, флаги, свежесть — упаковано в **одно 256-битное слово**.

**Математика.** $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).

**Почему это важно.** Один `SLOAD` ранжирует пул; один `SSTORE` поверх одного `SLOAD` поддерживает его актуальным. И в слове нет цены, поэтому память реестра структурно неспособна неверно оценить сделку. (§8)

### I-9 · Самовосстанавливающийся реестр и Поле жизнеспособности Ψ

**Что.** Протокол узнаёт ликвидность, торгуя ею. Шестнадцать слотов на пару, оценка пригодности, заработанная на реальных свопах, затухание арифметикой и **доказательство пары**: пул доказывает, какими токенами он торгует, прежде чем его сохранят.

**Математика.** $\Psi = V \cdot 2^{b} \cdot (1 + 0.25\,\mathbf{1}_{\mathrm{bridge}}) \cdot (1 + 0.05\,\mathbf{1}_{\mathrm{conc}})$ (E10); активность уменьшается вдвое за каждые 24,576 секунд тишины, $V = \mathrm{swapCount} \gg \lfloor (t - t_{\mathrm{last}})/24{,}576 \rfloor$ (E11); класс глубины $b = \min(15, \lfloor \log_{10}(d/10^{15}) \rfloor)$ (E12); новичок вытесняет слабейшего только сверх запаса, $2^{b(d_{\mathrm{new}})} > \Psi_{\mathrm{worst}} + \lfloor \Psi_{\mathrm{worst}}/4 \rfloor$ (E13).

**Почему это важно.** Ни кипера, ни cron, ни куратора, решающего, какие пулы важны: реестр восстанавливает себя сам из потока, который пользователи и так отправляли. (§9)

### I-10 · Разбиение, привязанное к капиталу

**Что.** Качество цены и глубина пула измеряются раздельно. Крошечный зонд измеряет предельный курс каждого пула; **полоса правдоподобия**, привязанная к капиталу, отбрасывает выбросы; выжившие делят ордер пропорционально измеренной глубине.

**Математика.** Центр = медиана, взвешенная по глубине, — первое $r_{i}$, при котором $\sum_{j \le i} d_{j} \ge \lceil \sum d / 2 \rceil$, — с полосой $[0.95, 1.05]\times$ центр (E25); разбиение сохраняется, только если $\mathrm{out}_{\mathrm{split}} \ge \mathrm{out}_{\mathrm{single}}\cdot(1 + 25/10^{6})$ (E14).

**Почему это важно.** Чтобы сдвинуть якорь, атакующий должен внести больше половины реальной ликвидности пары — капитал под риском, а не бесплатное пожертвование. Постоянная гарантия: **разбиение никогда не платит меньше, чем лучший одиночный пул.** При сравнении с исчерпывающей сеткой в Base маршрут Solver находится в пределах 0.13 bps от оптимума. (§10)

### I-11 · Нижняя граница Iron-Law Φ

**Что.** Минимальный выход, заново выводимый **в сети, при исполнении**, из измеренного ценового влияния и концентрации маршрута — независимо от всего, что передал вызывающий.

**Математика.** $\mathtt{floorBps} = \max(9{,}600 - \mathtt{legShv} - \min(\delta, 10^{4}) - \sigma_{\ln}/10^{14},\ 8{,}000)$ (E15), с вычетом за концентрацию $\mathtt{legShv} = \sum_{\mathrm{hops}} \lfloor 200\,((\sum a)^{2} - \sum a^{2})/\sum a^{2} \rfloor$ (E23); доставка должна превысить $\mathtt{effMin} = \max(\mathtt{userMinOut},\ \mathtt{route.singleOutFloor},\ \lceil \mathtt{finalHopQuote}\cdot\mathtt{floorBps}/10^{4}\rceil)$ (E16).

**Почему это важно.** Данные вызывающего могут только ужесточить защиту, но никогда не ослабить её. На сделке в 1 % от глубины нижняя граница ограничивает сэндвич примерно 2.7 % сделки и **отказывает** сверх этого — за краем круговой путь атакующего уходит в минус. (§11.5, §14.3)

### I-12 · Измерение на каждом шве

**Что.** Router никогда не верит, что перевод состоялся; он измеряет. Каждое списание, каждое поступление на хопе и каждая доставка — это реальная дельта баланса, от первой инструкции свопа до кошелька получателя.

**Математика.** Каждый хоп пересчитывается относительно измеренного поступления; токены с комиссией при переводе (fee-on-transfer) на ногах постоянного произведения оцениваются по разнице, которую пул действительно видит, — ровно по той величине, которую проверит собственный инвариант пула.

**Почему это важно.** Токены, которые взимают налог, делают rebase или сообщают неверные данные, не могут вынудить Router обещать то, чего у него нет, — а между транзакциями Router **ничего не держит в покое** (INV-1). (§11.2, §11.4)

### I-13 · Доктрина единого колбэка

**Что.** Один `fallback` отвечает на колбэк flash-учёта любой площадки в форме V3 — Uniswap V3, Algebra, Pancake V3, Sushi V3, концентрированных форков Solidly, — а контекст текущей ноги хранится во временном хранилище EIP-1153.

**Математика.** Причитающийся токен определяется по знакам двух дельт; платёж ограничен тремя способами — только зафиксированному пулу, только пока нога в процессе, никогда не больше записанного бюджета ноги (INV-10).

**Почему это важно.** Одна небольшая точка входа с тройной защитой заменяет зоопарк колбэков под каждый форк — меньше байткода, меньше поверхности, одна вещь для проверки. (§11.4)

### I-14 · Правило излишка и иммунитет через исчерпание

**Что.** Комиссия 0.28 %, вкомпилированная, взимаемая один раз с первой мостовой монеты, которую держит Router, — и **никакого члена комиссии, растущего с разрывом между котировкой и доставкой.**

**Математика.** $\mathrm{fee} = \lceil 28 \cdot \mathrm{base} / 10^{4} \rceil$, распределение $t_{1} = \lfloor 3{,}000\cdot\mathrm{fee}/10^{4} \rfloor$, $t_{2} = \mathrm{fee} - t_{1}$ (E17); предикат режима $\mathrm{feeHop} = \min\{h : \mathrm{isBridge}(\mathrm{hops}[h].\mathrm{tokenIn})\}$ (E26). Маршрут, избегающий всех мостов, платит на каждом хопе, поэтому никакой пылевой префикс не может унести комиссию.

**Почему это важно.** Всё, что пулы доставляют сверх котировки, доходит до вас полностью. Комиссию нельзя поднять против трейдера или снизить для привилегированного интегратора — этого ключа нет ни у кого. (§13)

### I-15 · Курс байт–газ ρ*

**Что.** Четыре двери Router — это аффинная модель стоимости, превращённая в интерфейс: решать в сети или нести заранее решённый маршрут в calldata — смотря что дешевле в данной сети.

**Математика.** Нести маршрут в calldata тогда и только тогда, когда $p_{\mathrm{byte}} < \rho^{*}$, где $\rho^{*}$ измерено в пределах 66.9–105.5 газа на байт в зависимости от формы маршрута (E22).

**Почему это важно.** У каждой сети есть цена байтов и цена вычислений. BlazePhoenix измеряет курс между ними и отдаёт трейдеру более дешёвую дверь. (§17.6)

### I-16 · Антимошеннический проектор Ξ

**Что.** Любой пул, чей хук V4 объявляет разрешения, изменяющие дельты, удаляется из множества кандидатов **до ранжирования**, причём сам хук ни разу не вызывается.

**Математика.** $\Xi(R) \in \{0,1\}$ в Мета-уравнении, вычисляемый битовой проверкой E18 по адресу хука.

**Почему это важно.** Враждебный хук никогда не получает шанса солгать: его ни о чём не спрашивают. Три независимых слоя — биты адреса, allow-list с закреплённым codehash и Ξ — стоят между трейдером и вредоносной площадкой. (§5.5, §14.2)

### I-17 · Дверь в одну сторону

**Что.** Административные полномочия прекращаются необратимо, в байткоде. `renounceControl` записывает флаг, который никакой путь кода не может сбросить; после этого остаётся только кураторский уровень, способный лишь расти.

**Почему это важно.** «Мы не будем это трогать» становится «мы не можем это трогать». Комиссию, распределение и нижние границы изначально нельзя было задать (INV-4); после закрытия двери нельзя задать и ничего другого. (§14.7)

### I-18 · Метрологический дизайн и опубликованный знаменатель

**Что.** Дисциплина, стоящая за всем перечисленным, — измерять каждую координату, которую можно измерить, а остальное делать монотонным, чтобы ложный ввод вредил только тому, кто его предоставил, — и аппарат верификации, который сообщает **каждое число рядом с его знаменателем**.

**Математика.** 1,609 объявленных тестов в 236 файлах; 268 из 268 отобранных мутантов убиты, каждый в паре с единственным тестом, который обязан упасть; покрывающие массивы силы 2 (258 из 258 пар) и 3 (1,636 из 1,636 троек) по факторам режима; 14 + 5 метаморфных отношений; три сборки оптимизатора, совпадающие бит в бит; символьные доказательства в Halmos и Certora Prover.

**Почему это важно.** Большинство проектов публикует число тестов. BlazePhoenix публикует, как его доказательства могли бы быть подделаны и относительно чего он измеряет, — и пересчитывает это на каждом коммите. (§2.3, §15)

### Табло

| Измеряемое свойство | Результат |
|---|---|
| Котировки без дрейфа, исполненные ровно по прогнозу (исследование на 240 выборках) | **64 / 64** |
| Патологии площадок × двери, которые проводят расчёт или отказывают с нашим селектором — никогда третьим способом | **20 / 20** |
| Убитые отобранные мутанты | **268 / 268** |
| Управляющие действия, отказ которых проверен точным кодом | **31 / 31** |
| Величины, вычисляемые в двух местах и связанные именованным тестом | **7 / 7** |
| Пары значений факторов режима, покрытые по построению | **258 / 258** |
| Тройки значений факторов режима, покрытые по построению | **1,636 / 1,636** |
| Сборки оптимизатора для математики котировок в согласии (runs 1 / 300 / 20,000) | **3 / 3** |
| Предпросмотр → доставка в четырёх сетях | **0–1 bps** |
| Маршрут Solver против исчерпывающей сетки в Base | **в пределах 0.13 bps** |
| Наибольший сэндвич, который нижняя граница допускает на сделке в 1 % от глубины, прежде чем отказать | **≈ 2.7 %** |
| Указанные внешние исследователи безопасности · Критические находки | **25 · 0** |


## Содержание

- Инновации (I-1–I-18) и табло
- §1 Проблема: котировка — это обещание, вычисленное кем-то другим
- §2 Ответ и дисциплина: дизайн, управляемый инвариантами, и Метрологический дизайн
- §3 Мета-уравнение и бюджет зависимостей
- §4 Восьмеричный диспетчер Ω — типы, семейства, замкнутые формы
- §5 Uniswap V4 от начала до конца — синглтон, доказательство существования, шлюз комиссии, хуки
- §6 Солвер Solidly
- §7 Детерминированный вывод 𝒟
- §8 Моно-слот (Monoslot)
- §9 Самовосстанавливающийся реестр и Поле жизнеспособности Ψ
- §10 Маршрутизация и распределитель разбиения
- §11 Уровень исполнения и нижняя граница Iron-Law Φ
- §12 Поверхности котировок и то, как стареет котировка
- §13 Политика комиссий: два режима и Правило излишка
- §14 Модель безопасности
- §15 Аппарат верификации в измерениях
- §16 Архитектура и размеры
- §17 Измеренная производительность
- §18 DEX, раздетый до костей
- §19 Статус развёртывания
- §20 Токен — BZPX
- §21 Исследовательская программа и история безопасности
- §22 Чему вы должны доверять, а чему — нет
- §23 Смежные работы
- §24 Заключение
- §25 Стейкинг V4: дизайн
- Приложение A · Примитивные уравнения (E1–E42)
- Приложение B · Инварианты (INV-1–INV-31)
- Приложение C · Команды воспроизведения
- Глоссарий
- Литература

---

## §1 Проблема: котировка — это обещание, вычисленное кем-то другим

Представьте, что вы продаёте автомобиль в городе с двадцатью дилерами. Хороший брокер посетит всех двадцать, найдёт лучшую цену и, возможно, разделит продажу между двумя из них. Большинство ончейн-«брокеров» проверяют цены в собственной базе данных — возможно, устаревшей — и показывают вам результат. Вы не можете проверить, что они посмотрели всех двадцать или что показали вам лучшего. Вы можете проверить лишь то, что до сих пор они были честны.

Каждый агрегатор, документацию которого мы прочли, — 1inch, CoW Swap, Velora (ParaSwap), KyberSwap, OpenOcean, Jupiter и UniswapX, по состоянию документации на 2026-09-07 — работает так: сервис вычисляет маршрут вне сети, а контракт исполняет переданный ему маршрут. Контракт не выбирает; он проверяет. Это разумная инженерия — именно поэтому эти системы быстрые и дешёвые, — и у неё есть одно последствие, о котором редко говорят прямо: **самое сильное утверждение, которое может сделать такая система, — что её оператор честен.**

Разрыв обычно не мошенничество. Это дрейф из трёх обычных источников. Котировка вычислялась по резервам, которые сдвинулись до того, как транзакция попала в блок. Поиск был исчерпывающим лишь настолько, насколько позволял бюджет оператора. И каждая поправка между котировкой и исполнением — буфер проскальзывания, комиссия по пути — применялась кодом, который пользователь не читал, к числу, которое пользователь не может воспроизвести. Ничто из этого не требует злого умысла. Всё это невидимо.

### §1.1 Фрагментация реальна — и мельче, чем кажется

Трейдер, которому нужна лучшая цена, сталкивается с парадоксом. Ликвидность существует, распределённая по десяткам децентрализованных бирж, у каждой свои контракты пулов, своя кривая ценообразования, своя схема адресов, — но ни одна площадка не держит её целиком. Цена лучшего исполнения — это стоимость того, чтобы говорить на языке каждой биржи одновременно и доверять каждому переводчику между показанной ценой и сделкой, которая состоится.

Ответ действующих игроков — интегрировать площадки по одной: коннектор для Uniswap, ещё один для форков Solidly, ещё по одному для каждого варианта концентрированной ликвидности, у каждого своё котирование, свой поиск адресов, своё исполнение. Поверхность интеграции растёт линейно с числом площадок, и каждый коннектор — новое место для дефекта ценообразования или молчаливого расхождения между котированным и исполненным числом.

Фрагментация мельче, чем кажется. Под внешним разнообразием автоматические маркет-мейкеры, держащие большую часть ончейн-ликвидности, подчиняются небольшому набору инвариантов. Пулы постоянного произведения подчиняются $x \cdot y = k$. Пулы концентрированной ликвидности отслеживают квадратный корень цены в фиксированной точке над кусочно-постоянной активной ликвидностью. Семейство Solidly подчиняется квартике $x^{3}y + xy^{3} = k$ на стабильных парах и постоянному произведению — на волатильных. Форм — горстка, а не шестьдесят.

Если форм мало, стоимость агрегации не обязана расти с числом площадок; ей достаточно расти с числом форм. Протокол, который реализует каждую форму один раз, корректно, и направляет каждую площадку к нужной, сжимает поверхность интеграции с линейной до постоянной. Добавление площадки перестаёт быть инженерией и становится конфигурацией: назначьте тип и режим вывода адреса — и существующая математика её оценит, существующий маршрутизатор проведёт расчёт, существующий реестр её изучит.

**Центральное утверждение дизайна:** каждая площадка, через которую маршрутизирует BlazePhoenix, — это параметризация одной из небольшого семейства функций выхода — четырёх рабочих семейств в таксономии из шести типов (§4), — хранимых один раз, в единой библиотеке без состояния, и никогда не переопределяемых. Добавить площадку — значит назначить тип и режим вывода, а не писать новый код ценообразования.

### §1.2 Внесетевой запасной выход

Преобладающая архитектура агрегаторов котирует маршруты на парке серверов, проводит аукцион среди внесетевых солверов, индексирует ликвидность пулов ботами-киперами и отправляет результат тонкому ончейн-контракту расчёта — часто за обновляемым прокси, чей админ-ключ может целиком заменить логику. Веб-сервис производит цену, база данных хранит граф ликвидности, привилегированный оператор может менять правила, а сеть становится площадкой расчёта для решений, принятых где-то ещё, программами, которые пользователь не может ни увидеть, ни проверить.

Такое устройство возвращает ровно то доверие, которое технология должна была устранить. Внесетевая котировка может отличаться от ончейн-исполнения, и пользователь не может доказать, что так быть не должно. Кипер может отключиться — и маршрутизатор ослепнет. Администратор прокси может одной транзакцией превратить честный контракт во враждебный.

| Аспект | Агрегатор с внесетевым решением | BlazePhoenix-Dex |
|---|---|---|
| Котирование | Внесетевые серверы или аукцион солверов | Один ончейн-диспетчер, общий для предпросмотра и исполнения во фрейме |
| Обнаружение ликвидности | Боты-киперы индексируют и рассылают списки | Самовосстанавливающийся реестр — ликвидность, изученная торговлей ею, — плюс детерминированное обнаружение |
| Выбор маршрута | Проприетарный внесетевой оптимизатор | Ончейн-Solver, воспроизводимый любым из публичного состояния |
| Поверхность обновления | Прокси под контролем администратора | Core без состояния; нет прокси, нет пути обновления, нет `selfdestruct` |
| Привилегированная власть над средствами | Часто присутствует | Router ничего не держит в покое; ни одна функция не перемещает основные средства пользователя |
| Межсетевая согласованность | Логика бэкенда для каждой сети | Один байткод на сеть |
| Предположение о доверии | Честность оператора | Собственный минимум пользователя, который контракт отказывается принимать равным нулю |

BlazePhoenix отвергает обе половины этой сделки. Он отвергает линейную поверхность интеграции, показывая, что она не линейна, и отвергает внесетевой запасной выход, удерживая всё решение — обнаружение, котирование, оценку, маршрутизацию и расчёт — внутри контрактов, которые любой может прочитать и которые дают один и тот же ответ в каждой сети. Цена реальна: котировать концентрированный пул в сети дороже, чем прочитать его из базы данных. Эта цена покупает устранение одного класса отказов — молчаливого, недоказуемого расхождения между тем, что пообещал сервер, и тем, что доставила сеть. Каждое последующее проектное решение — частный случай этого обмена.

## §2 Ответ и дисциплина

**То, что выбирает, и есть то, что торгует.**

У BlazePhoenix-Dex есть точка входа, которая принимает шесть значений — два токена, сумму, ваш минимум, получателя, дедлайн — и вычисляет маршрут внутри транзакции, которая его исполняет, из резервов пулов, являющихся публичным состоянием: какие площадки, сколько на каждую, в каком порядке. Поскольку маршрут выведен из публичного состояния публичным кодом, любой может вывести его заново. Поскольку число, обязывающее при расчёте, заново выводится тем же диспетчером, в том же фрейме, из того же живого состояния, котировка и исполнение — не два артефакта, которые нужно сверять; это одно вычисление, и предпросмотр от исполнения отделяет время, а не вторая модель.

Ещё три точки входа принимают заранее вычисленный маршрут из calldata — для тех, кто хочет более дешёвый путь и готов сам предоставить ответ; один и тот же механизм измерения, нижней границы и расчёта применяется ко всем четырём дверям (§11). Мы не утверждаем, что офчейн-подход ошибочен, и не утверждаем, что мы дешевле. Мы утверждаем иной класс гарантии — проверяемой, а не обещанной — для пользователя, которому она нужна и который готов платить за неё газом.

Четыре решения в этом дизайне, насколько мы можем установить, необычны для отрасли, и каждое сформулировано вместе с границей, которая удерживает его честным.

- **Адреса выводятся, а не запрашиваются.** Везде, где развёртывание семейства площадок воспроизводимо, адрес пула вычисляется арифметикой CREATE2 — теоремой, которую любой может перепроверить по трём публичным входам, — а не читается из хранилища фабрики; где арифметика ненадёжна, протокол спрашивает, и опрошенный пул доказывает собственную пару, прежде чем попасть в список (§7).
- **Состояние маршрутизации пула — одно слово.** Моно-слот (Monoslot) упаковывает всё необходимое для ранжирования площадки в один 256-битный слот и намеренно не содержит цены, поэтому память реестра может влиять на то, какие пулы опрашиваются, но структурно неспособна оценить сделку (§8–§9).
- **Нижняя граница заново выводится из сделки, которая действительно произошла.** Router измеряет реализованное влияние каждой ноги и реализованную концентрацию каждого хопа внутри исполняющего фрейма и пересчитывает нижнюю границу по этим измерениям, поэтому поля, переданные вызывающим, могут ужесточить защиту, но никогда не ослабить её (§11).
- **Поиск не зависит от деплоера там, где у площадок нет адресов.** Для синглтона Uniswap V4 идентификаторы пулов выводятся из публичных параметров, а затем их существование доказывается по собственному хранилищу менеджера — доказательство, которое никто другой написать не может (§5).

Ни одно из этих решений не устраняет остаток, который несёт любой агрегатор, — состояние может измениться между подписью и включением в блок, — поэтому минимум, который вы задаёте сами, обязателен и окончателен: каждая дверь отклоняет нулевой минимум с `RouterE(10)` до того, как сдвинется хоть один токен.

### §2.1 Дизайн, управляемый инвариантами

За ответом стоит метод. Мы называем его дизайном, управляемым инвариантами: протокол специфицирован не как последовательность операций, а как набор свойств, которые должны выполняться в каждом достижимом состоянии, а код организован так, чтобы эти свойства были структурными — обеспечивались формой кода, а не бдительностью программиста.

Большая часть программ пишется императивно, и корректность — лишь возникающая надежда. Для обычного ПО это приемлемо, потому что сбои исправимы. В публичном блокчейне ничего из этого не верно: код неизменяем после развёртывания; он напрямую хранит ценности; он исполняется во враждебной среде, где ищут каждую лазейку; и одна арифметическая ошибка — это необратимая потеря, а не падение программы.

Инвариант — это свойство, истинное независимо от того, как система пришла в своё состояние. *Router не держит средств между транзакциями.* *Пара в реестре содержит не более шестнадцати пулов, и вытеснение сохраняет шестнадцать сильнейших.* *Расчёта, не уплатившего комиссию протокола, не существует.* Три принципа ставят такие утверждения в центр:

| Принцип | Формулировка | Где проявляется |
|---|---|---|
| Вычисляй, а не доверяй | Если значение можно вывести или доказать, никогда не запрашивай и не предполагай его. | 𝒟 вычисляет адреса пулов; Router заново выводит базу комиссии и нижнюю границу во фрейме по измеренным балансам. |
| Один источник истины | Каждый математический примитив определён один раз и импортируется; никогда не переопределяется. | Core содержит всю математику ценообразования, вывода, нижней границы и оценки; Hub, Solver, Router и Quoter импортируют её, и ни один не переопределяет примитив. Реестр общих величин (§15) оценивает каждую величину, у которой два производителя. |
| Отказ по умолчанию | Если свойство нельзя гарантировать, остановись, а не продолжай наугад. | Solver возвращает отсутствие маршрута, а не сфабрикованный маршрут (`SolverE(5)`); стабильный солвер возвращает ноль, а не насыщенную котировку (§6); защита согласованности реестра откатывает структурно невозможную площадку до записи какого-либо состояния (§9). |

Четвёртый принцип управляет хранением средств: **нет привилегированного пути к средствам.** Solver и Quoter — контракты пути чтения; Router — единственный контракт, который перемещает деньги, он перемещает их атомарно в пределах границы, заданной самим пользователем, а от каждого административного полномочия, которое могло бы их перенаправить, можно навсегда отказаться через Дверь в одну сторону (§14).

### §2.2 Почему эта дисциплина подходит этой среде

Каждое свойство среды, делающее императивный код опасным, — это свойство, которое код, управляемый инвариантами, обращает себе на пользу. Неизменяемость вознаграждает доказуемость: развёрнутый код нельзя исправить, поэтому ценность предварительного доказательства соответственно высока. Композируемость вознаграждает единый источник истины: функцию ценообразования могут вызвать внутри хранилища, которого её авторы никогда не предвидели, а с одним Core каждый вызывающий получает один и тот же ответ. Враждебность среды вознаграждает отказ по умолчанию: атакующему нужен лишь один путь, который автор не учёл, а система, останавливающаяся при нарушении своих допущений, лишает его возможности импровизировать. Ценности на кону вознаграждают читаемость: протокол, выраженный пятью именованными операторами над одним математическим ядром, могут построчно проверить люди, которые его не писали.

Эта дисциплина не бесплатна, и компромисс сознателен: доказать границу переполнения дольше, чем отмахнуться от неё допущением; проведение каждого произведения через полноточное умножение-деление стоит газа, которого наивное умножение не стоило бы; вывод адреса стоит инженерной работы, которую его запрос пропускает. Это больше затрат до развёртывания в обмен на устранение сбоев, которые после него исправить нельзя.

### §2.3 Метрологический дизайн: один морфизм, измеренные координаты, монотонные входы

Дизайн, управляемый инвариантами, говорит, что должно выполняться всегда. Ещё три правила говорят, какой *формы* должен быть код, который это обеспечивает, и вместе они образуют метод, которому мы не нашли названия в отрасли, поэтому эта статья даёт ему имя: **Метрологический дизайн** — построение контракта как *измерительного прибора*. Его тезис в одной строке: *код, который верит, — поверхность атаки; код, который измеряет, — защита.*

**Один морфизм, вычисленный в точке.** Агрегатор не содержит отдельной интеграции для каждой площадки; он есть вычисление одного параметризованного морфизма ценообразования в точке пространства (форма площадки, сеть, модель комиссии). Если выразить это ветвлениями, полдюжины действующих типов площадок с собственными соглашениями о комиссиях размножаются в сотни попарных комбинаций — мультипликативный байткод, мультипликативная поверхность аудита, мультипликативный дрейф. Если выразить это морфизмом, то же покрытие — это горстка *форм состояния*, один Core без состояния, соглашения о комиссиях в их родных единицах и таблицы регистрации — и байткод растёт *аддитивно*: новая площадка — это строка, а не ветвь. Правило, охраняющее это схлопывание, — `KIND → SHAPE`: семейство площадок — это *данные*, тип, назначенный существующей форме, и режим вывода, — и никогда не новый путь кода, который может выбрать враждебный вход. Core задаёт формы битовой таблицей (`THETA_ATTR`, §4), поэтому вопрос «читает ли этот тип резервы?» — это сдвиг и AND, и нет ветви по умолчанию, о которой кто-то мог бы забыть. У схлопывания есть тормоз: единой универсальной формулы нет. Две замкнутые формы вычисляются точно — постоянное произведение и концентрированная ликвидность, — а для каждой кривой, где репликация была бы обузой, опрашивается собственный байткод площадки (§6). Морфизм схлопывает *принадлежность*, но никогда не *проверку*.

**Координаты измеряются, а не принимаются на веру.** Контракт никогда не действует на основе модельного, номинального или переданного вызывающим значения там, где существует ончейн-измерение, — и никакие данные без разрешений никогда не выбирают, какой код выполнится. Данные от недоверенной стороны — маршрут, заявленная комиссия, заявленная глубина, идентификатор пула — это *координаты*: места, куда направить прибор, которые контракт затем читает сам, с отказом по умолчанию. Каждая несущая защита в этой статье — пример этого правила: баланс после списания измеряется вместо номинальной суммы (§11), база комиссии берётся из измеренной разницы балансов, а не из calldata (§13), полоса правдоподобия привязана к взвешенной по глубине медиане измеренных глубин, а не к сырому балансу, который можно раздуть пожертвованием (§10), ограничитель ёмкости срезается по измеренным активам (§10), шлюз комиссии V4 читает живое состояние вместо доверия метке (§5), поиск через доказательство существования читает собственное хранилище синглтона (§5), а опрошенный пул доказывает свои `token0`/`token1`, прежде чем поиск включит его в список (§7). Репозиторий публикует классификацию каждого поля, записываемого вызывающим, по этому правилу: 23 поля, 14 подтверждены наблюдением, 5 управляющих, 4 заявлены с указанием причины (§15).

**Где кончается измерение, начинается монотонность.** Некоторые числа нельзя ни измерить, ни вывести заново — нижнюю границу вызывающего, заявленный им масштаб, собственный минимум вызывающего. Для них дизайн принимает число и строит функцию так, чтобы ложь могла навредить только лжецу: заверенная нижняя граница маршрута может только *повысить* применяемую нижнюю границу (§11); заверенная котировка ноги поднимается до измеренной котировки, если не покрывает её (§11); заявленное масштабирование ограничено измеренным поступлением, поэтому завышение не может направить средства, которых никогда не было; а единственное число, которое протокол не может выбрать, — ваш минимум — обязательно. Монотонный вход не требует доверия, поэтому его можно вычислить офчейн, кем угодно, бесплатно, и передать в calldata — так этот протокол переносит работу офчейн, *не перенося доверие офчейн*, и потому Router может предложить решающую дверь и три двери с calldata с одинаковыми гарантиями.

Метрологический дизайн управляет и инженерной работой вокруг артефакта, потому что прибор хорош лишь настолько, насколько хороша его калибровка. Запасы прочности оцениваются по измеренным рискам: порог разбиения стоит на 25 ppm, потому что безубыточность ноги измерена в 3 ppm (§10); плановая скидка, превышавшая свой измеренный риск в 27×, была упразднена (§10). Внешние сравнения публикуются с погрешностями своего метода или не публикуются вовсе (§17). И сам измерительный аппарат измерен: каждый инструмент обеспечения качества был откалиброван по дефекту, уже известному иным путём, прежде чем его число было опубликовано (§15). Это вся теория; остальная часть документа — её практика.

## §3 Мета-уравнение и бюджет зависимостей

Весь агрегатор можно прочитать как максимизацию одного выражения. Для входного токена $t_{\mathrm{in}}$, выходного токена $t_{\mathrm{out}}$ и суммы $x$ Solver ищет среди допустимых маршрутов тот, что максимизирует чистую полученную ценность, при условии нижней границы допустимости и антимошеннического шлюза:

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

Здесь $\mathcal{R}_{\mathcal{D}}$ — пространство маршрутов по пулам, найденным Детерминированным выводом 𝒟 и реестром; $\Omega(p_{\ell}, x_{\ell})$ — выход диспетчера для ноги $\ell$, проталкивающей $x_{\ell}$ через пул $p_{\ell}$; $f_{\mathrm{proto}}$ — комиссия протокола; $\hat{\Psi}(p_{\ell})$ — нормированная жизнеспособность ноги; $\Phi(R)$ — нижняя граница Iron-Law для измеренного влияния и концентрации маршрута; а $\Xi(R) \in \{0, 1\}$ — антимошеннический проектор.

| Оператор | Название | Роль в уравнении | Раздел |
|---|---|---|---|
| Ω | Восьмеричный диспетчер | Оценивает каждую ногу — одна ветвь на тип пула — и сообщает глубину в единицах токена для распределителя разбиения | §4 |
| 𝒟 | Детерминированный вывод | Находит пулы, используемые маршрутом, — по вычисленному адресу, где семейство это допускает, по доказанному идентификатору пула для синглтона V4 | §5, §7 |
| Φ | Нижняя граница Iron-Law | Проецирует маршрут на допустимое множество: нижняя граница под чистым выходом, заново выводимая в исполняющем фрейме | §11 |
| Ψ | Поле жизнеспособности | Взвешивает каждый пул по заработанному качеству, прочитанному из его упакованного ончейн-состояния | §9 |
| Ξ | антимошеннический проектор | Оценивает любой маршрут, который небезопасно показывать, ровно в ноль | §5, §14 |

Факторизация сознательна. Ω отвечает, *сколько возвращает пул*; Ψ — *насколько ему доверять*; Φ — *допустим ли маршрут*; 𝒟 — *где живёт пул*; Ξ — *безопасно ли показывать маршрут*. Форма произведения означает, что одна несостоятельная нога обрушивает оценку маршрута — нога с нулевой котировкой или пул без репутации обнуляют произведение, а не уменьшают его, — а индикатор жёстко обнуляет любой маршрут, чистый выход которого ниже нижней границы. Никаких мягких штрафов, никаких настраиваемых весов, никакой проскальзывающий почти-подходящий маршрут.

![FIG-1 — Мета-уравнение как конвейер Solver: кандидаты из 𝒟 и реестра ранжируются и отсекаются по Ψ, котируются Ω, распределяются по ногам, проверяются по Φ и Ξ; Router заново вычисляет Ω и Φ в исполняющем фрейме.](https://blazephoenix.xyz/whitepaper/figs/ru/fig-1.png)

Ончейн Solver реализует уравнение как конвейер (`findBestRoutePlan`): кандидаты для каждой пары собираются реестром и, когда реестр не свеж, — 𝒟; ранжируются и отсекаются по Ψ до `MAX_CANDIDATES = 8`; зондируются и котируются Ω; фильтруются полосой правдоподобия; распределяются по ногам; а собранный маршрут несёт нижнюю границу, заверенную Φ. Затем Router заново вычисляет Ω и Φ по суммам, которые действительно поступили. Утверждение уравнения — то, по которому аудитор может проверять код, — состоит в том, что ни один шаг этого конвейера не обращается ни к чему вне пяти операторов. Если величина не является аргументом одного из пяти, она не может повлиять на маршрут: ни маркетинг площадки, ни офчейн-репутация, ни предпочтения оператора.

### §3.1 Бюджет зависимостей

Вместо прилагательных — *децентрализованный*, *бессерверный*, *не требующий доверия* перестали нести информацию — вот проверяемый список: всё, что должно существовать и продолжать работать, чтобы одна сделка прошла расчёт.

| Зависимость | Типичный агрегатор с офчейн-решением | BlazePhoenix-Dex |
|---|---|---|
| Сервис, вычисляющий маршрут | Обязателен | Не используется — Solver является `view`-функцией, которую обслуживает любой узел |
| Ценовой оракул или фид | По-разному | Нет — в пяти контрактах нет ни одного вызова фида |
| Сеть солверов или аукцион | Обычно | Нет |
| Кипер, cron или релейер | По-разному | Нет — жизнеспособность затухает арифметически при следующем чтении; реестр учится на обычной торговле |
| Обновляемый прокси | Обычно | Нет — ни прокси, ни `selfdestruct`, ни инициализатора; единственные `delegatecall` — собственные вызовы компилятора в публичную библиотеку Core, зафиксированные при линковке |
| Размещённый интерфейс | На практике да | Только для удобства |

Последняя строка — то место, где большинство бюджетов тихо проваливается. Размещённый интерфейс — удобство, а не зависимость: `swapBestExactIn` принимает шесть значений, которые человек может собрать вручную, и интегратор, скрипт или фронтенд конкурента могут вызвать её без каких-либо отношений с нами. Если сегодня ночью исчезнут все серверы, которыми управляет этот проект, завтра контракты будут котировать, маршрутизировать и проводить расчёт ровно так же, как сегодня. Цена этого свойства в том, что вызывающий платит за поиск газом; бюджет выше — список того, от чего этот газ избавляет.

Неизменяемость режет и в другую сторону. Дефект нельзя исправить — такова цена контракта, который аудируется один раз, — поэтому дефекты вместо этого раскрываются и вознаграждаются по программе баг-баунти (§21).

### §3.2 Три границы

У бюджета три границы, указанные рядом с утверждением, которое они ограничивают. Интерфейс размещён на сервере, как у всех, и скомпрометированный фронтенд — самая крупная уязвимость в модели угроз: контракт соблюдает любой ненулевой минимум, который ему передан, поэтому минимум, который вы задаёте, — единственная граница, которую никакой интерфейс не может у вас отнять. Поиск площадок читает состояние, записанное более ранними транзакциями, — публичное и без разрешений, но не созданное в момент сделки. И административные полномочия существуют, пока от них не откажутся через Дверь в одну сторону; после отказа уровень управления — пауза, роли, казначейства, связывание, удаления — исчезает навсегда, и остаётся лишь уровень куратора, который может только расти (§14). Ядро без состояния, бессерверная работа, управление только на расширение: таблица — это утверждение; слова — его тень.


## §4 Восьмеричный диспетчер Ω

*Просто:* одна функция, которая умеет оценивать пул любого типа. *Точно:* `universalQuote(QuoteCtx, amountIn)` в Core — диспетчер котировок для Solver и для предпросмотра Quoter; он возвращает ожидаемый выход и глубину в единицах токена для распределителя разбиения, а пул, который он не может оценить, возвращает ноль и отбрасывается до того, как что-либо отправлено. Router вычисляет те же примитивы Core для каждой ноги в собственном фрейме (`_hopScaleImpactAndQuote`) — родственная функция, сохранённая ради газа и глубины стека, а Exact Pass в Quoter задаёт ещё один, иной вопрос (§12); все три записаны вместе в реестре общих величин репозитория, и их согласие закреплено именованными тестами, а не предполагается (§15). Название сохранено от перечисления из восьми типов, с которым диспетчер был выпущен; с тех пор перечисление пополнилось девятым членом и лишилось трёх в результате намеренного удаления. Нумерация сохраняет пропуски: Моно-слот, записанный вчера, должен декодироваться одинаково всегда.

### §4.1 Шесть типов, четыре семейства, одна таблица атрибутов

| Тип | Константа | Семейство | Ветвь ценообразования | Атрибуты Θ | Шкала газа (Solver) |
|---|---|---|---|---|---|
| 0 | `KIND_V2` | Постоянное произведение (Uniswap V2, Sushi, форки) | замкнутая форма `outV2` по `getReserves` | резервы · проверяемая пара | 90,000 |
| 1 | `KIND_V3` | Концентрированная ликвидность (Uniswap V3 и форки формы V3) | замкнутая форма `outV3` по `slot0` | концентрирован в пуле · проверяемая пара | 110,000 |
| 4 | `KIND_V4` | Синглтон Uniswap V4 | замкнутая форма `outV3` по `extsload` | концентрирован в синглтоне | 180,000 |
| 5 | `KIND_SOLIDLY` | Solidly (Aerodrome, Velodrome, форки), stable и volatile | опрос пула `getAmountOut`; реплицированная кривая как запасной вариант | резервы · проверяемая пара | 90,000 |
| 6 | `KIND_ALGEBRA` | Algebra (класс Camelot), динамическая комиссия | замкнутая форма `outV3` по `globalState` | концентрирован в пуле · проверяемая пара | 110,000 |
| 8 | `KIND_V4_NATIVE` | Пул V4 с нативной валютой сети | та же ветвь, что у типа 4 | концентрирован в синглтоне | 215,000 |

Шесть типов, четыре рабочих семейства: постоянное произведение {0}, концентрированная ликвидность {1, 6}, Solidly {5} и синглтон V4 {4, 8}. Столбец атрибутов — не проза: это битовая таблица `THETA_ATTR = 0x040A9400A9` в Core, четыре бита на тип — *читает резервы*, *концентрирован по адресу пула*, *концентрирован в синглтоне*, *предоставляет `token0()`/`token1()`*, — поэтому любой вопрос о принадлежности в пяти контрактах («читает ли этот тип резервы?») — это один сдвиг и один AND (`kindHas`, `kindHasAny`), а тип без битов отказывает по умолчанию, без ветви по умолчанию, о которой кто-то мог бы забыть. Столбец газа — вторая таблица, `THETA_GAS`, которую читает только `estGas` в Solver, в единицах по 5,000 газа на ногу.

**Типы 2, 3 и 7 отсутствуют в результате намеренного удаления.** Взвешенное семейство (3) так и не дало ни одной пригодной для маршрутизации котировки. Два семейства Curve (2, 7) работали и всё равно были удалены при укреплении границы доверия в августе 2026 года, потому что их поддержка требовала единственного `approve` токена в Router, единственного значения глубины, заверяемого вызывающим, а не измеряемого из пула, и единственного исключения в доказательстве подлинности пары в Hub — три поверхности атаки, каждая последняя в своём классе. Упразднённые номера никогда не переназначаются: `decodeKind` читает тип из битов Моно-слота, поэтому повторное использование 2 переинтерпретировало бы каждый пул, когда-либо записанный под этим номером, а защита в CI проваливает сборку, если удалённый символ возвращается.

**Тип нативной валюты (8) существует потому, что один инвариант нарушается там и больше нигде.** Рабочее допущение Router — каждый актив является ERC-20 с `balanceOf` — перестаёт выполняться ровно тогда, когда валюта пула V4 — нативный актив сети, и кодирование этого исключения как *типа* делает его видимым в записи пула, переносимым в плане маршрута и находимым через grep для аудитора. Ценообразованию не нужна ветвь: идентификатор пула выводится из двух отсортированных валют, а `address(0)` по построению сортируется первым. Тип заслуживает своё место эмпирически: по измерению 2026-08-13 во всех обслуживаемых сетях 62.9 % ликвидности в ETH внутри V4 находилось в нативных пулах (Arbitrum 99.6 %, Optimism 95.0 %, Base 48.9 %).

### §4.2 Замкнутые формы

**Постоянное произведение.** Резервы удовлетворяют $x \cdot y = k$, а комиссия $f$ в базисных пунктах берётся со входа. Core вычисляет

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

без деления до последнего шага, поэтому единственное округление — это округление вниз последнего частного, а комиссия `BPS` или больше делает пул некотируемым (возвращает ноль). В эту формулу входит комиссия `effV2Fee(declared)`: заявленная комиссия, равная нулю или превышающая `V2_FEE_CEILING_BPS = 100`, заменяется канонической 30 bps, поэтому регистрация не может занизить собственную котировку — а вместе с ней и нижнюю границу, которую эта котировка задаёт, — заявив неправдоподобную комиссию; то же правило отражено на пути исполнения, так что обе стороны сделки оцениваются одним числом.

**Разобранный пример 1 — котировка постоянного произведения.** Пул содержит $r_{\mathrm{in}} = 1{,}000{,}000$ USDC и $r_{\mathrm{out}} = 500$ WETH при канонической комиссии 30 bps; трейдер продаёт $x = 10{,}000$ USDC. В собственных единицах пула (у USDC 6 десятичных знаков, у WETH 18) $x = 10^{10}$, $r_{\mathrm{in}} = 10^{12}$, $r_{\mathrm{out}} = 5 \times 10^{20}$:

1. Вход с учётом комиссии: $x(\mathrm{BPS}-f) = 10^{10} \times 9{,}970 = 9.97 \times 10^{13}$.
2. Числитель: $9.97 \times 10^{13} \times 5 \times 10^{20} = 4.985 \times 10^{34}$.
3. Знаменатель: $10^{12} \times 10^{4} + 9.97 \times 10^{13} = 1.00997 \times 10^{16}$.
4. Выход: $\lfloor 4.985 \times 10^{34} / 1.00997 \times 10^{16} \rfloor = 4.935790 \times 10^{18}$ wei, то есть **4.935790 WETH** (до шести знаков; контракт округляет последнее частное вниз до wei).

По спотовой цене 0.0005 WETH за USDC те же 10,000 USDC принесли бы 5 WETH; исполнение на 1.28 % ниже спота, из которых 0.30 % — комиссия. Мера влияния Core на основе резервов для этой ноги равна $\mathrm{impactV2Bps} = \lceil x \cdot \mathrm{BPS} / (r_{\mathrm{in}} + x) \rceil = \lceil 10^{14} / 1.01 \times 10^{12} \rceil = 100$ bps — это число потребляет нижняя граница Iron-Law в §11.

**Концентрированная ликвидность.** Пулы формы V3 не хранят резервов; они хранят квадратный корень цены $\sqrt{P}$ в фиксированной точке Q64.96 и активную ликвидность $L$. Внутри одного тика, для token0 → token1 с комиссией $f$ в миллионных долях и $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

а для token1 → token0 $\sqrt{P}\,' = \sqrt{P} + x_{f} Q_{96}/L$ и $y = L\,Q_{96}(\sqrt{P}\,' - \sqrt{P})/(\sqrt{P}\,\sqrt{P}\,')$, с делением в два этапа. Наивное вычисление переполняется: $L \cdot \sqrt{P}$ может достигать $2^{288}$. Core никогда его не формирует — каждое произведение раскладывается через полноточный `mulDiv` (512-битный промежуточный результат, 256-битный итог), поэтому каждый этап доказуемо помещается, а шаг, который сдвинул бы цену не в ту сторону или в ноль, возвращает ноль, а не число. Пулы Algebra (тип 6) используют ту же ветвь и отличаются в одном отношении, которое ветвь учитывает: их состояние и динамическая комиссия находятся в `globalState`, читаемом один раз (§5.4).

Формула точна в пределах одного тика и неточна при пересечении границ тиков, где однотиковая форма завышает результат. Диспетчер не реплицирует арифметику пересечения тиков: репликация внутренней математики площадки — структурная обуза, потому что любое расхождение между копией и оригиналом проявляется как неверно оценённая котировка. Вместо этого есть две вещи. Core содержит граничный ограничитель (`sqrtBoundary`), который обрезает шаг на краю текущего диапазона — точно для того, что помещается в диапазон, и строго ниже для остального, никогда выше, — и дизайн помещает его на сторону *обещания*, а не ранжирования, потому что ограничение только тех семейств, у которых есть тики, сравнивало бы площадки по двум соглашениям. В дереве кода, которое измеряет эта статья, ограничитель определён, но ещё не подключён: `Router._v4LegQuote` и ветвь ранжирования в Core обе вызывают неограниченную однотиковую форму, поэтому концентрированная нога, выходящая за свой диапазон, ограничена лишь нижней границей ноги из §11.3 и бюджетом хопов, а нижняя граница Iron-Law служит последней защитой. А при исполнении нижняя граница Iron-Law применяется к котировке, которую Router заново выводит во фрейме из сумм, действительно поступивших, поэтому завышение на границе тика стоит маршруту точности ранжирования, но никогда не стоит пользователю нижней границы. Exact Pass в Quoter (§12) даёт интегратору собственное число пула, когда оно ему нужно.

**Глубина.** Каждая ветвь также возвращает глубину в единицах токена — меньшую сторону резервов для семейств с резервами (`shortSide18`), а для концентрированных семейств виртуальный резерв $L/\sqrt{P}$ или $L\sqrt{P}$, который $L$ представляет при текущей цене (`depthFromL18`), поэтому пул V3 и пул V2 равной глубины сравниваются в распределителе как равные, а не отличаются в $\sqrt{P}$ раз.

### §4.3 Две единицы комиссии в родном виде

Площадки с постоянным произведением и Solidly указывают комиссии в базисных пунктах; концентрированные площадки — в миллионных долях. Диспетчер хранит каждую в её родной единице и никогда не конвертирует одну в другую, что устраняет класс ошибок округления ценой двух констант вместо одной. Два семейства площадок знают свою комиссию только в момент исполнения — пулы V4 с динамической комиссией и Algebra, — и оба обрабатываются измерением комиссии из того же чтения состояния, которое даёт цену (§5.4).

### §4.4 Один диспетчер, две поверхности

| Тип | Предпросмотр Quoter | Во фрейме (план Solver; повторный вывод Router) |
|---|---|---|
| V2 (0) | замкнутая форма `outV2` | замкнутая форма `outV2` |
| V3 / Algebra (1, 6) | замкнутая форма `outV3` по `slot0` / `globalState`; живая комиссия Algebra измеряется из того же чтения | то же |
| Solidly (5) | опрос пула; реплицированная кривая × 9,800 / 10,000 как запасной вариант | то же |
| V4 / V4-native (4, 8) | замкнутая форма `outV3` через `extsload`, шлюз комиссии с отказом по умолчанию (E19) | то же |

Поэтому предпросмотр от исполнения отделяет время: состояние, изменившееся между блоком предпросмотра и исполняющим фреймом. §12 измеряет ровно этот разрыв на 240 выборках.

## §5 Uniswap V4 от начала до конца

Uniswap V4 — это не новый контракт пула, а новая форма площадки, и она отменяет четыре допущения, на которых стоит каждый роутер, построенный с 2020 по 2024 год. У пулов нет адресов — это записи в хранилище одного синглтона, индексированные хешем. Внутри свопа выполняется произвольный код — хук срабатывает в заданных точках расчёта, который проводит роутер. Комиссии не обязаны быть постоянными. И пулы могут держать нативную валюту сети, у которой нет `balanceOf`. Этот раздел структурно отвечает на все четыре, так что нога V4 — для Solver, для нижних границ и для трейдера — неотличима от любой другой ноги.

### §5.1 Как найти пул, у которого нет адреса: вывести, затем доказать

Детерминированный вывод 𝒟 вычисляет адреса (§7), но у пула V4 нет адреса, который можно вычислить. У него есть идентификатор — keccak-хеш его `PoolKey`, — поэтому режим 9 таблицы вывода выводит *poolId*, а не адреса, а затем делает то, чего не может ни один вывод адреса: доказывает, что пул существует, читая собственное хранилище PoolManager по этому id и требуя ненулевой цены в виде квадратного корня и ненулевой ликвидности. Подделать это доказательство не может никто, потому что в это хранилище не пишет никто, кроме менеджера. Выдуманный id даёт нули и отбрасывается.

**Разобранный пример 4 — идентификатор пула V4 из его ключа.** Канонический id — это $\mathrm{poolId} = \mathrm{keccak256}(\mathrm{abi.encode}(c_{0}, c_{1}, \mathrm{fee}, \mathrm{tickSpacing}, \mathrm{hooks}))$ (`computeV4PoolId`), пять 32-байтовых слов. Возьмём пул нативный ETH / USDC на Base с уровнем комиссии 0.05 %: $c_{0} = \mathtt{0x0000…0000}$ (нативная валюта сортируется первой), $c_{1} = \mathtt{0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913}$ (USDC), fee $= 500$, tickSpacing $= 10$, hooks $= \mathtt{0x0}$.

1. `abi.encode` раскладывает 160 байт: две валюты, каждая дополненная слева до 32 байт, затем `500`, затем `10` (`int24`, расширенный со знаком до 32 байт), затем нулевой хук.
2. $\mathrm{poolId} = \mathtt{0x96d4b53a38337a5733179751781178a2613306063c511b78cd02684739288c0a}$.
3. Поле `pool` реестра для строки V4 — это младшие 160 бит id, $\mathtt{0x781178a2613306063c511b78cd02684739288c0a}$ — адрес без кода; поэтому у строк V4 никогда не запрашивают `token0()`, никогда не читают баланс, а доказываются они повторным выводом (§9).
4. Базовый слот хранилища пула — $\mathrm{keccak256}(\mathrm{abi.encode}(\mathrm{poolId}, 6)) = \mathtt{0x13a3a697…73b2893d}$: слот 6 менеджера — это маппинг `pools`. Core читает слово 0 в этом слоте и слово 3 по адресу $\mathrm{base} + 3$ (`v4SqrtAndLiq`, селектор `extsload(bytes32)` = `0x1e2eaeaf`).
5. Слово 0 упаковывает $\sqrt{P}$ в биты [0, 160), тик — в [160, 184), протокольную комиссию — в [184, 208) и комиссию LP — в [208, 232); слово 3 несёт активную ликвидность в младших 128 битах. Если $\sqrt{P}$ или $L$ равны нулю, пул для этого протокола не существует.

У обёрнутого собрата той же пары — $c_{0} = \mathtt{0x4200…0006}$ (WETH), тот же уровень — id $\mathtt{0x90333bb0…ebd943a0}$: это другой пул с другой глубиной, и поэтому скан проверяет оба ключа, причём нативный — первым (см. ниже). Раскладка из шага 5 не взята из документации: один и тот же пул, прочитанный через эту раскладку и через канонический контракт `StateView`, возвращает одинаковые числа на форке mainnet, и именно на это тождество ссылается комментарий в Core.

**Лестница стоимости.** Скан, порождающий кандидатные id, — это лестница, от самой дешёвой ступени к дорогим, потому что наивный перебор всех комбинаций комиссии и шага тиков обошёлся бы на пути решения недопустимым счётом за газ:

| Ступень | Что пробует | Стоимость при попадании |
|---|---|---|
| 1 | Шаблон уровней, уже выученный для этой пары токенов | одна проба — устойчивый путь |
| 2 | Проверка происхождения перед оплатой холодного скана | одно чтение |
| 3 | Канонические уровни Uniswap пакетом через `extsload(bytes32[])` | один вызов на пакет |
| 4 | Пары `(fee, spacing)`, объявленные в строке регистрации | ограничено регистрацией |
| 5 | Сетка генератора холодного старта — fee $= 10{,}000\,j$, spacing $= 100\,j$, $j$ убывает от `V4_GRID_MAX = 99` | не более `V4_GRID_PROBES = 40` проб |

Весь скан останавливается на `V4_CAP = 8` доказанных пулах. Два свойства вытекают из самой конструкции, а не из проверки: пул с хуком этот скан выдать не может никогда — он выводит id *без хука*, а id без хука для пула с хуком в менеджере не существует, так что такой пул проваливает доказательство жизнеспособности ещё до любого вопроса политики, — и дублирующиеся id от разных догадок об уровне схлопываются в одного кандидата, так что один пул не может забить воронку через псевдонимы. Порядок проб сам по себе — политика, потому что лимит расходуется на то, что проверено первым: у пары с нативным активом сети в V4 два ключа, нативный и обёрнутый, и на обслуживаемых сетях на момент выбора порядка нативный ключ по измерениям оказался глубже (3.76× на Base, 292× на Robinhood Chain), поэтому он проверяется первым.

![FIG-2 — Лестница «вывести и доказать» для V4: пять ступеней от выученного шаблона уровней до ограниченной сетки генератора; каждый кандидатный id доказывается живым по хранилищу менеджера, прежде чем засчитывается в лимит восьми; id с хуками на этом пути никогда не существуют.](https://blazephoenix.xyz/whitepaper/figs/ru/fig-2.png)

**Дверь без разрешений.** Любой может вызвать `claimV4(c0, c1, fee, tickSpacing)` и добиться допуска пула без хука, потому что заявка несёт собственное доказательство: id заново вычисляется из переданного ключа и сверяется с хранилищем менеджера, так что ложная заявка даёт нули и ничего не допускает (`HubE(9)`). Дверь охраняют ещё три условия: одна сторона пары должна быть маршрутизируемой мостовой монетой, чтобы место в ограниченном реестре никогда не тратилось на пару, которую роутер не сможет пересечь; эффективная комиссия должна поддаваться котированию по шлюзу комиссии ниже; и заявка должна пройти тот же порог допуска, что и регистрация через своп (§9), с измеренной ликвидностью пула в качестве глубины. Публика может обучать реестр, но не может ему лгать.

### §5.2 Чтение пула, который не отвечает на вызовы

Когда id доказан живым, диспетчеру нужны цена и ликвидность — а `slot0()` вызвать нельзя. Core читает хранилище синглтона напрямую через `extsload`, как в примере выше, и ветка V4 сводится к замкнутой форме концентрированной ликвидности из §4.2: та же математика, та же дисциплина переполнения. Синглтон меняет то, где живёт состояние, а не то, что оно означает. Менеджер без кода или несоответствующий стандарту даёт полностью нулевые слова — отказ по умолчанию, никогда не revert.

### §5.3 Расчёт через синглтон

Исполнение переворачивает привычный поток управления. Вместо того чтобы вызвать пул и получить обратный вызов за оплатой, Router вызывает `unlock` у менеджера и выполняет весь своп внутри колбэка, который ему предоставляет менеджер: своп, затем расчёт по каждой причитающейся валюте через `sync → settle → take`, затем снятие блокировки. Валюты в полёте переносятся через границу unlock в transient storage (`TSLOT_V4IN`, `TSLOT_V4OUT`), так что фрейм не загрязняет постоянное состояние, а контекст между свопами доказуемо пуст. `BalanceDelta`, который возвращает менеджер, упакован — `amount0` в старших 128 битах, `amount1` в младших, — и Router декодирует его именно так до начала любой арифметики расчёта.

Нативная валюта сохраняет главный инвариант. Router везде канонически работает с WETH — нативная стоимость, входящая в `swapExactInNative`, оборачивается ровно один раз у двери, а нативный выход сознательно не реализован: получатель получает WETH, который ни один путь не может незаметно запереть. Нативная нога V4 разворачивается *точно в момент* внутри собственного фрейма unlock — `WETH.withdraw`, затем `settle` со стоимостью, а ETH, полученный из нативного `take`, заново оборачивается в том же фрейме, — так что нативный ETH никогда не лежит в контракте в покое. Единственная новая поверхность, которая для этого понадобилась, — `receive()`, ограниченный настолько узко, насколько позволяет требование: transient-слот (`TSLOT_ETHOK`) хранит единственный адрес, которому в этот момент разрешено отправить Router сырой ETH, — канонический контракт WETH во время разворачивания, PoolManager во время нативного take, ноль в любой другой момент, — так что голый ETH от кого-либо ещё вызывает revert. Нативная дверь закрыта по умолчанию, пока не подключён `weth` (`RouterE(3)`).

### §5.4 Комиссия, которая существует только при исполнении

Пул V4 со статической комиссией несёт комиссию LP в своём ключе, где она неизменяема. Менеджер пулов может дополнительно взимать собственную комиссию менеджера Uniswap — ограниченную 1,000 pips, одной десятой процента, в каждом направлении, — которую статическая ветвь Core не добавляет, поэтому котировка по статическому ключу может завысить доставленный выход не более чем на эту комиссию менеджера, если контроллер комиссий Uniswap её включит (на момент написания она равна нулю во всех обслуживаемых сетях); недостача ложится внутрь нижней границы, а база комиссии, будучи измеренной, её игнорирует. Пул с динамической комиссией вместо этого несёт сигнальное значение `0x800000`, а его реальная комиссия живёт в `slot0`. Котирование сигнального значения за чистую монету оценило бы сделку как бесплатную, тогда как исполнение заплатило бы текущую ставку. Шлюз — три строки в 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

Ключ с динамической комиссией при ненулевой протокольной комиссии возвращает комиссию на уровне $10^{6}$ или выше, и `outV3` возвращает для неё ноль: отказ по умолчанию, потому что композиция протокольной комиссии и комиссии LP закреплена не так надёжно, как нам нужно, а измеренная протокольная комиссия на Base на момент написания равна нулю, так что на практике эта ветвь ничего не стоит. То же правило закрывает тот же шов в соседнем семействе: пулы Algebra по построению имеют динамическую комиссию и регистрируют нулевое сигнальное значение, поэтому `quoteV3Fee` принимает ненулевую настроенную комиссию за истину, комиссию динамического пула — из чтения `globalState`, которое уже дало его цену, а для форков с концентрированной ликвидностью, у которых комиссия привязана к шагу тиков и хранится в пуле, — собственный `fee()` пула, с отказом по умолчанию на `0xFFFFFF`, если никто не отвечает. Статические пулы V3 не требуют дополнительного чтения и котируются байт в байт так же.

Один шов остаётся, и он ограничен, а не устранён: хук с динамической комиссией может переопределить комиссию на каждый своп внутри `beforeSwap`, так что сохранённая в `slot0` комиссия не во всех случаях совпадает с исполненной. Этот остаток сдерживается списком разрешённых хуков Hub при допуске и нижней границей Iron-Law, заново выводимой из фактического выхода при исполнении, так что враждебное переопределение заканчивается revert или недостачей в пределах нижней границы, но никогда не исполнением ниже неё (§14).

### §5.5 Хуки: проверка арифметикой, а не допросом

Хук — это произвольный код, выполняемый внутри расчёта, который проводит Router. Интуитивная защита — спросить хук, что он собирается делать, — защитой не является, потому что враждебный хук лжёт или выполняет повторный вход. BlazePhoenix никогда ни о чём не спрашивает хук. Вместо этого стоят три независимых слоя, от самого дешёвого, и §14 разворачивает их: биты разрешений хука находятся в младших четырнадцати битах его собственного адреса и фиксируются при развёртывании, а два бита, разрешающие менять дельты свопа, отклоняются одной операцией AND ещё до движения токена (`hookAltersDeltas`, `RouterE(9)`); хук маршрутизируем, только пока он в списке разрешённых и пока его runtime-код всё ещё совпадает с codehash, зафиксированным при допуске (`isHookLive`); а проектор Ξ удаляет хук, меняющий дельты, из множества кандидатов Solver до ранжирования, так что такой маршрут даже непредставим. Остаток назван вместе с механизмом: разрешённый хук, не меняющий дельты, всё ещё может сделать revert посреди свопа и потратить газ попытки — это цена принципиального отказа вызывать недоверенный код, чтобы узнать, что он сделал бы.

### §5.6 Чего стоит эта интеграция

Каждый механизм выше — либо примитив Core, уже используемый в другом месте и применённый к новой раскладке состояния, либо действительно новый примитив, обобщающийся за пределы V4, — обнаружение через доказательство существования. Ничто не является интеграцией под конкретный пул, и ничто не является курируемым списком, который человек должен поддерживать в актуальном состоянии. Пулы V4 обнаруживаются без каталога, доказываются по хранилищу, в которое больше никто не может писать, оцениваются той же замкнутой формой, что и любая другая концентрированная площадка, проходят шлюз комиссии по измерению с отказом по умолчанию там, где измерение неточно, проверяются на враждебные хуки неизменяемой арифметикой адреса, рассчитываются через собственный протокол блокировки синглтона и доступны в нативном ETH без того, чтобы Router держал в покое хоть один wei. §17 приводит измерения: в переписи фабрик на Arbitrum семейство V4 выигрывает больше пар хопов, чем любая другая зарегистрированная площадка, а на Optimism перепись зафиксировала, насколько нам известно, первую в этой сети агрегацию с маршрутом через V4. Мы не знаем другого публичного агрегатора, который выводит, доказывает, оценивает, проверяет и рассчитывает пулы V4 полностью внутри исполняющей транзакции, включая нативный ETH, без курируемого списка пулов; если вы найдёте такой, это утверждение неверно, и мы хотели бы об этом узнать.

## §6 Солвер Solidly

У некоторых инвариантов нет замкнутой формы, достаточно простой, чтобы безопасно её воспроизвести, а воспроизведение — это ровно тот риск репликации, от которого протокол отказывается в других местах. Для таких площадок диспетчер спрашивает пул — через самый узкий честный канал, который открывает площадка, — и держит реплику в резерве для форков, которые не отвечают.

Семейство Solidly — Aerodrome, Velodrome и их форки — обслуживает коррелированные активы на стабильных парах с квартичным инвариантом, который прилегает к диагонали гораздо плотнее, чем постоянное произведение, но всё же допускает расхождение:

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

Вблизи точки равновесия эта кривая отдаёт на выходе почти весь вход — минимальное проскальзывание ровно там, где концентрируется поток стейблкоинов и liquid staking. Волатильные пары Solidly — это постоянное произведение, и волатильная ветвь Core *и есть* `outV2` — один производитель, что закрепляет метаморфное отношение MR7 (§15).

![FIG-3 — Выход в зависимости от входа для сбалансированной стабильной пары: квартика Solidly против постоянного произведения и диагонали нулевого проскальзывания, по собственным формулам Core.](https://blazephoenix.xyz/whitepaper/figs/ru/fig-3.png)

**Основной путь: спросить пул.** `getAmountOut(amountIn, tokenIn)` вычисляется собственным байткодом пары — с текущей комиссией, кривой и округлением, — поэтому своп, запрашивающий ровно эту сумму, по построению проходит проверку инварианта пула, и запас не нужен. Триггер перехода на запасной путь — ответ не больше 1 wei, согласованный по трём спрашивающим каналам (Solver, Router, Quoter) после того, как пул, ответивший ровно 1, однажды направил два из них по разным ветвям.

**Запасной путь: воспроизвести, затем недозапросить.** Если форк не открывает этот селектор, диспетчер читает текущую комиссию из фабрики пары (`getFee(pool, stable)`; объявленной запасной комиссии верят только в пределах того же потолка 100 bps, что и в ветви V2, иначе 30 bps), решает инвариант на резервах, нормализованных по десятичным знакам, и умножает результат на 9,800 / 10,000 — дисконт 200 bps, чтобы у округления пула, которое реплика не может наблюдать, всегда был запас. Дисконт применяется только на запасном пути и никогда — когда пул ответил сам.

**Решение.** По входному балансу после сделки $x$ и целевому инварианту $K$ из резервов до сделки Core ищет корень $f(y) = x^{3}y + xy^{3} - K$, где $f'(y) = x(x^{2} + 3y^{2}) > 0$, итерацией Ньютона с начальным значением $y_{0} = Y$ — противоположным резервом до сделки (`_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

Естественное на вид начальное значение — приближение постоянной суммы — ошибочно так, что незаметно портит цены: для пула, поглощающего вход, в несколько раз превышающий его резерв, оно оказывается далеко выше корня, в области, где $f$ чрезвычайно крута, шаг округляется вниз до нуля, и солвер застревает на уверенном, правдоподобном, неверном числе. Старт с противоположного резерва и шаги в обе стороны сходятся как для сбалансированных, так и для сильно перекошенных пулов. Шаг вниз, который пересёк бы ноль, заменяется половиной текущего приближения; каждый шаг обеспечивает продвижение минимум на одну единицу; итерация завершается, когда соседние приближения отличаются не более чем на единицу, и финальной поправкой на одну единицу против берущего попадает на безопасную для инварианта сторону. Цикл ограничен 64 итерациями, и этот лимит закрыт по умолчанию: солвер возвращает само начальное значение, которое проверка вызывающего $y \ge Y$ превращает в нулевую котировку, — пул считается неоцениваемым, так же как сам Aerodrome скорее отказывает, чем неверно оценивает. Исчезающая производная ведёт к тому же выходу.

**Нормализация десятичных знаков.** Инвариант однороден степени четыре, поэтому, когда у обоих токенов одинаковое число десятичных знаков, масштаб сокращается и сырые резервы работают напрямую. Пара вроде DOLA (18 знаков) против USDC (6) переносит это несоответствие в квартику, поэтому Core перед решением масштабирует обе стороны к общей базе $10^{18}$, а после — возвращает масштаб:

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

Токены, сообщающие больше восемнадцати десятичных знаков, котируются нулём, а не вызывают антипереполнение при масштабировании. Десятичные знаки другого токена читаются из пула, а не берутся у вызывающего: примитив с единственным производителем, принявший координату от вызывающего, заново открыл бы то расхождение, ради закрытия которого он существует.

**Отказ по умолчанию на всей области.** Четыре проверки решают, вычисляется ли кривая вообще, и каждая возвращает ноль, а не revert, потому что revert внутри котировки — это библиотечный `DELEGATECALL`, сворачивающий весь план Solver для пары: $X$ и $Y$ не должны превышать $3.4 \times 10^{38}$; $X + A$ — тоже; а `_solKFits` требует, чтобы и инвариант $k = xy(x^{2}+y^{2})/\mathrm{WAD}^{3}$, и производная $x(x^{2}+3y^{2})/\mathrm{WAD}^{2}$ помещались в 256-битное слово в начальной точке — самой большой точке, которую посещает Ньютон. Последняя проверка выполняется без деления — она вычисляет старшее слово 512-битного произведения и сравнивает его с одним WAD (`_mulFitsWad`), — потому что прежняя форма, $a \le \max \cdot \mathrm{WAD} / b$, сама переполнялась на пуле, содержащем меньше одной единицы токена. Эту лазейку нашла N-версионная линия из §15 2026-09-05; исправление на 35 байт меньше заменённого кода и внесено по принципу «сначала красный тест», с фаззингом на 5,000 прогонов по всей области, проверкой 60,000 выборок той же арифметики на Python и тремя мутантами в проверке.

**Измеренный остаток и запас 200 bps.** Реплика измерялась на живых пулах Aerodrome, где эталоном служил собственный `getAmountOut` пула:

| Пул | Состояние | Эталон | Выход Core | Отклонение |
|---|---|---|---|---|
| USDC / USDbC | сбалансирован (6 / 6 зн.) | 999,534,071 | 999,534,071 | 0.000 % |
| DOLA / USDC | перекошен (18 / 6 зн.) | 985,528,437 | 987,998,286 | 0.251 % |
| USDz / USDC | перекошен (18 / 6 зн.) | 899,325,141 | 901,565,830 | 0.249 % |

Сбалансированная пара совпадает до единицы; перекошенные пары укладываются в 0.5 %, с завышением, потому что округление баланса пула после комиссии снаружи не видно. Именно из-за этого остатка запасной путь недозапрашивает на 200 bps: запас гарантирует, что у проверки инварианта пары будет люфт при исполнении, превращая возможный revert из-за завышенной котировки в гарантированное исполнение, и он применяется одинаково в предпросмотре и во фрейме, так что они идут в ногу. Линия канонического оракула из §15 ограничивает кривую независимо: против реализации, написанной по спецификации площадки, стабильная котировка Core отклоняется не более чем на 4 wei на 5,000 фаззинговых входах.

## §7 Детерминированный вывод 𝒟

Прежде чем пул можно котировать, его нужно найти. Ответ индустрии — спросить: вызов фабрики площадки читает маппинг в хранилище и возвращает адрес, а вызывающий верит фабрике на слово. BlazePhoenix поддерживает этот ответ — четыре режима вызова фабрики покрывают площадки, чьи развёртывания невоспроизводимы, — и предпочитает более сильный везде, где позволяет арифметика: вычислить адрес без внешнего вызова. Вычисленный адрес может проверить любой, у кого есть те же три входа; полученному адресу можно только доверять.

Механизм — CREATE2. EVM делает адрес контракта чистой функцией от его развёртывающего, соли и хеша init-кода:

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

где $\mathrm{addr}_{20}$ берёт последние двадцать байт хеша. Core выписывает 85-байтовый прообраз ровно в одном месте (`create2Address`); `deriveAddress` выбирает полином соли и origin по режиму, а заверенный вывод Algebra в Hub переиспользует тот же производитель.

| Режим | Семейство | Разрешение | Соль / детали пробы |
|---|---|---|---|
| 0 | V2 | вызов фабрики `getPair(t0,t1)` — селектор `0xe6a43905` | — |
| 1 | V3 / Algebra | вызов фабрики `getPool(t0,t1,fee)` — `0x1698ee82`; при промахе — `poolByPair(t0,t1)` — `0xd9a641e1` | — |
| 2 | Solidly | вызов фабрики `getPool(t0,t1,stable)` — `0x79bc57d5` | — |
| 3 | Концентрированные форки Solidly (Slipstream, Velodrome CL) | вызов фабрики `getPool(t0,t1,tickSpacing)` — `0x28af8d0b` | — |
| 4 | V2 CREATE2 | чистая арифметика | `keccak256(packed(t0,t1))`, origin = фабрика |
| 5 | V3 CREATE2 | чистая арифметика | `keccak256(encode(t0,t1,fee))`; `fee == 0` — сигнальное значение Algebra → `keccak256(encode(t0,t1))`, origin = `poolDeployer()` фабрики (`0x3119049a`), заверяется при допуске |
| 6 | Solidly clone CREATE2 | чистая арифметика | `keccak256(packed(t0,t1,stable))`, origin = фабрика |
| 7 | CL CREATE2 | чистая арифметика | `keccak256(encode(t0,t1,tickSpacing))`, origin = фабрика |
| 8 | *выведен из употребления* | освобождён при удалении Curve; надгробие — `MODES_VALID = 0x2FF` его отклоняет | — |
| 9 | V4 derive-scan | вывести poolId без хуков и доказать их жизнеспособность в PoolManager (§5.1) | сетка ≤ 99 уровней, ≤ 40 холодных проб, стоп на 8 пулах |

Режимы 0–3 — по одному `staticcall`; режимы 4–7 — ноль вызовов, им нужен только правильный хеш init-кода; режим 9 обслуживает единственное семейство, чьи пулы нельзя найти ни тем, ни другим способом.

**Теорема, заявление и доказательство.** Выведенный адрес — это теорема о паре: при заданных origin, полиноме соли и хеше init-кода адрес следует из одного keccak, и любой, кто его оспаривает, может пересчитать его. Запрошенный адрес — это заявление фабрики о её собственном хранилище, а фабрика может ответить пулом на других токенах. С 2026-09 запрошенный пул (режимы 0–3) доказывает свою пару, прежде чем обнаружение его внесёт в список: Hub читает его `token0()` и `token1()` — те же два чтения, что делает Router на шве, где происходит оплата, — и несовпадение отбрасывает кандидата, так что пул, который исполнитель отклонил бы, никогда не попадает в список, в план или в ранжирование. Это изменила матрица враждебных площадок из §15: её первый прогон внёс в список пул, которым ответила допущенная куратором фабрика, планировщик его ранжировал, а исполнитель отклонил его на шве, где происходит оплата, — средства не были под угрозой, но пара отклонялась, пока самозванец выигрывал разбиение.

**Две тонкости в строках формы V3.** Algebra (класса Camelot) взимает динамическую комиссию, поэтому комиссия никогда не может быть частью её идентичности: регистрация Algebra объявляет нулевую комиссию как сигнальное значение, режим 5 исключает комиссию из соли, а origin для CREATE2 — это отдельный контракт `poolDeployer`, который открывают фабрики Algebra. Этот origin — единственный вход вывода, которым фабрика могла бы управлять после допуска: прокси меняет свой ответ, не меняя codehash, — поэтому Hub заверяет его один раз при `addFactory` (`factoryDeployer`) и дальше выводит от заверенного origin; после отказа от контроля заверение замораживается. Со стороны вызова фабрики фабрики Algebra открывают `poolByPair`, а не `getPool`, поэтому поиск в режиме 1, ничего не вернувший, повторяется с этим селектором, прежде чем сдаться.

**Каждый вывод проходит одни финальные ворота.** Hub отбрасывает любой выведенный адрес, у которого нет runtime-байткода, — `extcodesize` и есть вся проверка (`hasCode`), — так что неверный хеш init-кода, устаревшая регистрация или сеть, где семейство никогда не развёртывалось, дают ничто, а не фантомную площадку. Байткод по адресу доказывает существование, а не честность, поэтому между найденным пулом и исполненной сделкой по-прежнему стоят допуск, полоса правдоподобия и нижние границы. Один и тот же пул, выведенный при нескольких комбинациях `(fee, spacing)`, вносится в список один раз.

**Граница между выводом и запросом эмпирическая.** Площадки на клонах, такие как Velodrome и Aerodrome, остаются на вызове фабрики, хотя их хеш init-кода известен, потому что их origin для CREATE2 находится внутри внутреннего развёртывателя детерминированных клонов, а не в фабрике, — адрес невоспроизводим из публичных входов, поэтому протокол спрашивает. Uniswap V3 — флагманский случай в обратную сторону: его хеш init-кода — одна константа во всех сетях, потому что везде развёрнут один и тот же байткод пула, и тождество `derived == getPool` выполняется на форках Ethereum, Arbitrum, Optimism и Base против собственных фабрик каждой сети (§17). Выводите там, где арифметика надёжна, спрашивайте там, где нет, и в любом случае проверяйте всё.

**Обнаружение потребляет таблицу строка за строкой.** Для каждой из не более чем `MAX_FACTORIES = 16` зарегистрированных фабрик Hub проверяет каждую объявленную комбинацию `(fee, spacing)` — строки Solidly в обоих вариантах, стабильном и волатильном, строку V4 с резервом до восьми, — так что худший случай скана определяется регистрацией, а не настроением сети. Поверхность конфигурации охраняется на входе (§9): структурно невозможную площадку сохранить нельзя.

## §8 Моно-слот

Хранилище — доминирующая статья расходов любого действия в EVM: холодный `SLOAD` стоит 2,100 газа, и реестр, разбросавший атрибуты пула по отдельным слотам, платил бы его снова за каждый атрибут, каждый пул и каждый оцениваемый маршрут. BlazePhoenix упаковывает всё состояние маршрутизации пула в одно 256-битное слово — Моно-слот (Monoslot), — которое читается один раз и декодируется чистой арифметикой.

| Биты | Поле | Ширина | Значение |
|---|---|---|---|
| 0 | `active` | 1 | слот активен |
| 1–7 | зарезервировано | 7 | бит 5 = стабильная кривая (читается из собственного `stable()` пула при регистрации); бит 6 = нативная сторона переставлена; бит 7 — прежде флаг моста, больше не записывается: бонус моста читается из пары в реальном времени (§9) |
| 8–31 | `fee` | 24 | уровень комиссии в собственной единице площадки |
| 32–39 | `kind` | 8 | тип площадки (§4) |
| 40–47 | `tier` | 8 | 0 = курируемый (заносится оператором), 2 = без разрешений (выучен в торговле или заявлен) |
| 48–59 | `conc` | 12 | поле концентрации, маскируется до `0xFFF` перед упаковкой |
| 60–63 | `bucket` | 4 | логарифмическая корзина глубины |
| 64–95 | `lastUpdateTs` | 32 | метка реального времени последнего касания |
| 96–127 | `emaIn` | 32 | зарезервировано под EMA входного объёма; при регистрации записывается нулём, текущий тик его не обновляет |
| 128–159 | `emaOut` | 32 | зарезервировано под EMA выходного объёма; как выше |
| 160–191 | `swapCount` | 32 | счётчик активности, насыщается на $2^{32}-1$ |
| 192–223 | `regBlk` | 32 | блок регистрации |
| 224–255 | `lastBlk` | 32 | блок последнего касания |

![FIG-4 — Раскладка битов Моно-слота: одно 256-битное слово, четырнадцать именованных полей, поле концентрации маскируется до двенадцати бит, чтобы никогда не затекать в корзину глубины.](https://blazephoenix.xyz/whitepaper/figs/ru/fig-4.png)

Кодирование — последовательность сдвигов и OR (`encodeSlot`):

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

Одна деталь — это защита, а не удобство: поле концентрации маскируется до двенадцати бит *до* сдвига, поэтому оно никогда не может затечь в корзину глубины в битах [63:60], — упаковка исключает целый класс незаметного наложения полей, портящего упакованное состояние, вместо того чтобы доверять вызывающим оставаться в диапазоне. Декодирование столь же механично: каждый аксессор — это сдвиг и усечение; запись корзины очищает её четыре бита перед установкой; счётчик свопов насыщается, а не переполняется по кругу, так что полный слот никогда не обнулит историю загруженного пула. Метка реального времени, которую Hub ставит при каждом тике и регистрации, управляет и затуханием жизнеспособности из §9, и шлюзом свежести обнаружения в Solver.

Обратите внимание, чего в слове нет: цены. Моно-слот несёт всё, что нужно для ранжирования пула, — активность, класс глубины, комиссию, тип, флаги, давность, — и ничего, что можно было бы принять за котировку, поэтому память реестра структурно не способна оценить сделку. Цены вычисляются заново, из живых резервов, внутри фрейма, который по ним торгует; упакованное слово решает лишь то, какие пулы стоит спрашивать. Путь обновления следует той же экономии: своп через известный пул перезаписывает слово один раз — счётчик, корзина, метки времени, один `SSTORE` поверх одного `SLOAD`, — так что предельная стоимость поддержания реестра в актуальном состоянии едет внутри транзакций, которые пользователи и так отправляли.

Почему важно одно слово — вопрос умножения. Solver проверяет до `MAX_CANDIDATES = 8` площадок на пару в пределах до трёх топологий, так что одна решённая сделка может оценить пару десятков пулов, прежде чем сдвинет токен. При 2,100 газа за холодный слот раскладка по пять слотов на пул обошлась бы в этом проходе оценки в сотни тысяч газа; Моно-слот обходится одним чтением на пул, а `psisOf` в Hub возвращает оценки всех кандидатов одним пакетным вызовом. Моно-слот — не оптимизация, применённая к ончейн-маршрутизации; это причина, по которой ончейн-маршрутизация вообще доступна по цене.

## §9 Самовосстанавливающийся реестр и Поле жизнеспособности Ψ

Большинство агрегаторов знают свои площадки потому, что кипер индексирует пулы офчейн и заталкивает списки ончейн, — доверенный процесс, окно устаревания и операционная зависимость: три вещи, которые протокол, решающий маршруты ончейн, принять не может. BlazePhoenix занимает противоположную позицию: реестр узнаёт ликвидность, торгуя ею.

### §9.1 Обучение через торговлю и доказательство пары

На каждой исполненной ноге Router вызывает у Hub `recordSwap` (`onlyRouter`, `whenLive`), передавая пул, его тип, его комиссию, пару, суммы и измеренную им глубину. Сначала Hub отклоняет любой тип, который Router не мог бы исполнить (`KINDS_EXECUTABLE`), — локальная защита, не зависящая от того, остаётся ли Router тем, чем был при развёртывании, — а затем идёт по одному из двух путей.

Моно-слот известного пула обновляется: счётчик свопов растёт от своей *текущей, уже затухшей* базы, корзина глубины пересчитывается, блок и метка реального времени продвигаются — перезаписано одно упакованное слово, ни одного внешнего вызова. Неизвестный пул идёт холодным путём. Сначала он должен пройти допуск (`_canInsert`, ниже). Затем он должен доказать утверждение, которое собирается сохранить: для каждого парного типа (`KINDS_PAIR_PROOF` = V2, V3, Solidly, Algebra) Hub читает собственные `token0()` и `token1()` пула и требует, чтобы они совпали с парой, — не более двух вызовов `staticcall`, и только на холодном пути. Каждый аргумент `recordSwap` — пул, тип и глубина — это calldata под контролем вызывающего, переносимая в маршруте Router, так что без этого доказательства атакующий регистрирует написанный им контракт под выбранной им парой с выбранной им глубиной, не держа ни одного из токенов. Для двух типов V4, у которых поле `pool` — усечённый id без байткода, доказательство — повторный вывод: шаг тика восстанавливается, id пересчитывается и сверяется, только для пулов без хука, так что пул с хуком никогда не зарегистрируется через эту дверь; нативный пул V4 сверяется в обеих ориентациях `(address(0), T)`. Несовпадение *пропускает* регистрацию и никогда не вызывает revert, потому что своп пользователя уже рассчитан и никогда не должен провалиться из-за решения реестра.

Доказательство пары при регистрации появилось в сентябре 2026 года, и породило его измерение из §15: пятнадцать мутаций исходного кода прогнали против каждого инварианта с состоянием, и мутант, удалявший это доказательство, пережил все кампании уже с первого прогона, потому что ни один тест никогда не предлагал Hub пул, торгующий другими токенами. Теперь кампания Hub делает это при каждом прогоне — пара в обратном порядке, один общий токен, два чужих, — а `invariant_ActiveEntriesTradeThePair` читает `token0`/`token1` каждой активной записи; мутант стал сторожевой записью, которая от него погибает. Обнаружение несёт то же доказательство для запрошенных пулов (§7).

Нет индексатора, который нужно запускать, и нет момента, когда протокол слеп из-за упавшего сервера. Операторская дверь `seedPool` заранее заполняет пары при развёртывании (уровень 0, курируемые), а открытая для всех дверь `claimV4` допускает пулы V4 без хука под их собственным доказательством (§5.1); обе — оптимизации, а не зависимости: пустой реестр сам излечивается в заполненный через обычную торговлю.

### §9.2 Оценка

Поле жизнеспособности Ψ разделяет качество и ёмкость, читая только Моно-слот и живой мостовой статус пары (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

Корзина глубины $b = \min(15, \lfloor \log_{10}(d / 10^{15}) \rfloor)$ входит через свой вес $2^{b}$: каждый десятичный порядок глубины удваивает вес, поэтому глубина учитывается сублинейно — глубокий пул уверенно предпочитается, но просто здоровый пул не стирается гигантом. Пул на мостовой паре получает +25 %, потому что мосты — соединительная ткань графа ликвидности; концентрированный пул получает ещё +5 %. Мостовой бонус читается вживую из пары единственным источником (`_pairBridged`), а не из бита, замороженного при регистрации, и внутри пары он одинаков, поэтому никогда не может переупорядочить пулы одной пары.

**Затухание — один механизм.** Активность затухает с простоем по единому правилу реального времени (`_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

Шаг — `VITALITY_DECAY_STEP_SECONDS = 24,576`, около 6.8 часа, и тридцать два деления пополам гасят любую оценку, так что молчащий пул достигает нуля и полной вытесняемости через 786,432 с, примерно за 9.1 дня. Один своп оживляет его, и оживление честное: счётчик продолжается с того, что оставило затухание, а не с итога до затухания, так что один пылевой своп после долгого молчания не может вернуть пулу накопленный ранг. Оценщик различает два нуля: *мёртвый* слот — пустой, ни разу не обновлённый или за горизонтом — получает настоящий ноль и выпадает из ранжирования, тогда как у *живого* слота, чей малый счётчик просто округлился до нуля при сдвиге, оценка поднимается до единицы, так что реальный, недавно затронутый пул никогда не исчезает из-за артефакта округления. Окно намеренно задано в секундах реального времени: окно, отсчитываемое в блоках, означает разную реальную длительность в каждой сети, а ранняя реализация, делавшая сдвиг каждые 16 блоков, затухала до нуля примерно в 128× быстрее, чем было задокументировано.

![FIG-5 — Затухание жизнеспособности: счётчик 4,096 делится пополам каждые 24,576 с и достигает нуля через 32 шага; своп на 4-й день оживляет его от затухшей базы, а не от исторического итога.](https://blazephoenix.xyz/whitepaper/figs/ru/fig-5.png)

### §9.3 Вытеснение

Каждая пара держит не более `MAX_SLOTS = 16` пулов. Когда в неё хочет попасть семнадцатый, Hub оценивает каждого действующего участника по полному Ψ — с глубиной, бонусами и всем остальным, так что глубокий новичок может вытеснить мелкого, но «тёплого» захватчика, — и допускает новичка, только если его прогнозная оценка превосходит слабейшего действующего с запасом (`_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

причём новичок оценивается консервативно — жизнеспособность единица, собственная корзина глубины, без бонусов, — а новичок с нулевой глубиной отклоняется сразу. Запас закрывает вектор гриферства: при голом правиле «вытеснить слабейшего» противник мог бы держать шестнадцать мелких, едва активных слотов и не пускать глубокого конкурента; при ранжировании по Ψ с запасом по-настоящему более глубокий пул всё равно побеждает. Предел константы указан там, где она применяется: вес новичка — степень двойки, а оценка действующего непрерывна, поэтому эффективный запас колеблется примерно от 100 % (действующий чуть выше единицы) до примерно 25 % (действующий на 51, наименьший допустимый новичок 64); 25 % правила — это также нижний предел поведения, ниже которого оно никогда не опускается. Активно торгуемого действующего трудно вытеснить, пока затухание не сделает своё дело, — два дня простоя стоят ему семи делений пополам. Открытая заявка V4 проходит тот же запас, используя измеренную ликвидность пула как глубину.

### §9.4 Обнаружение и его газовый регулятор

Обнаружение — публичная функция view: для любой пары Hub обходит свой список фабрик, выводит кандидатов режимами из §7, применяет `hasCode` и доказательство пары, а Solver сливает результат с зарегистрированным набором, удаляя дубликаты по адресу, так что ни один источник не может подавить другой. Чтобы ограничить газ, Solver применяет фильтр свежести (`_registryFresh`): когда не менее `MIN_FRESH_VENUES = 3` *курируемых* зарегистрированных площадок пары были затронуты в пределах `DISCOVERY_TTL_SECONDS = 3,600`, он доверяет реестру и пропускает проход вывода. Ручаться могут только курируемые строки (уровень 0): открытую строку записывает тот, кто первым сделал своп, и их учёт позволял трём самозарегистрированным пылевым парам, которые поддерживались свежими тремя пылевыми свопами в час, выключить обнаружение для пары и скрыть каждую честную, ни разу не зарегистрированную площадку. Открытая строка остаётся кандидатом; путь она больше не выбирает. Фильтр — регулятор газа и покрытия, а не параметр безопасности: нижняя граница Iron-Law и минимум пользователя защищают каждое исполнение, как бы ни устарел реестр, потому что ни то ни другое вообще не читает реестр.

### §9.5 Поверхность конфигурации

Двери регистрации Hub охраняются на входе. `addFactory` отклоняет, ещё до записи какого-либо состояния (`HubE(5)`): тип вне `KINDS_ROUTABLE`; режим вне `MODES_VALID`; режим CREATE2 с нулевым хешем init-кода; слот V2-salt, привязанный к чему угодно, кроме типа V2; слот клонов, привязанный к типу, отличному от Solidly; слот V3-salt, привязанный к чему угодно, кроме V3 или Algebra, причём регистрация Algebra обязана объявлять каждую комиссию нулевым сентинелом; и строку derive-scan V4, привязанную к любому типу, кроме V4, или с несогласованными списками комиссий и шагов тика. Известная фабрика обновляется на месте и никогда не получает второй строки, так что один адрес не может исчерпать шестнадцать мест (`HubE(4)`); после отказа от управления фабрику, чей runtime-код сдвинулся, нельзя активировать заново, а строку derive нельзя превратить в строку ask. `allowHook` фиксирует codehash хука при допуске, и хук пригоден для маршрутизации, только пока он в списке разрешённых *и* не изменён (`isHookLive`); после отказа от управления исключение из списка недоступно, а изменившийся хук остаётся на паузе навсегда. Мостов не более `MAX_BRIDGES = 3`; `addBridge` — полномочие куратора, переживающее отказ от управления, `removeBridge` — полномочие управления, умирающее вместе с ним.

### §9.6 Структурная граница

Всё сказанное выше ограничивает одно предложение: **Ψ может скрыть площадку; неверно оценить её он не может никогда.** Оценка ранжирует и усекает набор кандидатов — ни котировка, ни нижняя граница, ни распределение её не читают, потому что они вычисляются из измерений внутри транзакции; память влияет на выбор маршрута только через пропуск. Что бы ни считал реестр, худшее, что он может вывести на поверхность, — худшая, но реальная живая цена, а стоимость, которую такая цена может пропустить, ограничена строжайшим из трёх: полосой правдоподобия, нижней границей Iron-Law, заново выведенной в том же фрейме, и минимумом, заданным пользователем. Основная сумма недосягаема по построению. Реестр решает, что видно; голоса в том, что платится, у него нет.

## §10 Маршрутизация и распределитель разбиения

Пул с постоянным произведением назначает цену по одному правилу: два его резерва, перемноженные, никогда не могут уменьшиться. Влейте $\Delta$ с удержанием комиссии $\gamma$ (комиссия площадки 0.30 % означает $\gamma = 0.997$), и вы получите $y\,\gamma\Delta / (x + \gamma\Delta)$, где ваш собственный вход стоит в знаменателе: чем больше вы проталкиваете через один пул, тем хуже оплачивается каждый следующий токен. Проскальзывание — это арифметика, и лекарство от него тоже: разбиение того же ордера по пулам той же пары сдвигает каждый пул меньше.

**Разобранный пример 2 — разбиение по двум пулам.** Один ордер на 100 против двух пулов одной пары, оба с комиссией 0.30 %: пул A держит по 1,000 с каждой стороны, пул B — по 4,000 с каждой стороны.

| Маршрут | Арифметика | Вы получаете |
|---|---|---|
| Все 100 в A | $1{,}000 \times 99.7 / 1{,}099.7$ | 90.661 |
| Все 100 в B | $4{,}000 \times 99.7 / 4{,}099.7$ | 97.275 |
| 20 в A, 80 в B | $1{,}000 \times 19.94 / 1{,}019.94 \;+\; 4{,}000 \times 79.76 / 4{,}079.76$ | $19.550 + 78.201 = 97.751$ |

Разбиение 20 / 80 — собственный выбор распределителя, а не догадка: веса пропорциональны измеренной глубине относительно самого глубокого кандидата — A весит $1{,}000/4{,}000 = 2{,}500$ из 10,000, B весит 10,000, — так что A получает $2{,}500 / 12{,}500 = 20$ % ордера. Разбиение обыгрывает лучший одиночный пул на 0.476 токена, то есть на 48.9 bps. Затем фильтр разбиения спрашивает, окупает ли этот выигрыш вторую ногу: порог равен $97.275 \times (1 + 25 / 10^{6}) = 97.278$, и 97.751 его превосходит, так что разбиение остаётся. Каждую строку можно проверить на калькуляторе, и эффект накапливается: ценовое влияние — это реакция каждого пула на *свой собственный* срез, так что каждая допустимая нога, добавленная к маршруту, сдвигает каждый пул меньше.

![FIG-6 — Выход в зависимости от доли, отправленной в глубокий пул, для примера с двумя пулами: вогнутая кривая с максимумом около 80 %, отмечена точка распределителя, пропорциональная глубине.](https://blazephoenix.xyz/whitepaper/figs/ru/fig-6.png)

### §10.1 Формы маршрутов и бюджеты

Solver строит три топологии и возвращает максимизатор (`findBestRoutePlan`, с занявшим второе место в качестве запасного варианта): *прямой* маршрут в один хоп; *мостовой* маршрут через один промежуточный токен; *двойной мостовой* маршрут через два — три хопа, самая глубокая форма, которую строит Solver, и максимум, который принимает Router (`MAX_HOPS = 3`). Каждый хоп разбивается на параллельные ноги в рамках двух намеренно раздельных бюджетов: `MAX_LEGS_PER_STAGE = 4` отвечает на вопрос *сколько ног помещается в хоп*, `MAX_LEGS = 11` — *сколько помещается в маршрут*, и вместе они дают 4, 4 + 4 и 4 + 4 + 3: последний хоп трёххопового маршрута получает три, потому что его сжимает глобальный потолок, а не потому, что кто-то написал особый случай. Мосты берутся из настраиваемого оператором набора не более чем из трёх токенов в Hub; Solver раскрывает каждый настроенный мост и позволяет измеренному `totalOut` решать для каждой сделки. Маршрут, чьё измеренное влияние превышает `MAX_ROUTE_IMPACT_BPS = 9,000`, вообще не возвращается, а для пары без допустимого маршрута выдаётся отказ `SolverE(5)` вместо выдуманного маршрута. Router применяет собственный лимит в пять ног на хоп к любому маршруту, переданному вызывающим (`MAX_LEGS_PER_HOP = 5`, `RouterE(3)`).

### §10.2 Воронка

Для каждой пары Solver собирает зарегистрированные пулы и, если реестр не свеж (§9.4), обнаруженные; удаляет любой пул, чей хук изменяет дельты (проектор Ξ, применённый здесь, чтобы запланированная дверь никогда не могла собрать маршрут, который Router отклонил бы); ранжирует объединение по Ψ одним пакетным вызовом и оставляет `MAX_CANDIDATES = 8` — намеренно больше бюджета ног любого хопа, так что глубокий пул, стоящий в списке за несколькими тонкими площадками, будет замечен, взвешен и сможет их вытеснить, а не останется обделённым из-за порядка в списке. Точная ничья по Ψ разрешается в пользу меньшей комиссии, так что на холодном реестре воронка — функция цены, а не истории.

Затем каждый кандидат зондируется один раз, на сотой доле ордера (`amountIn / 100`, или весь ордер, если это округляется вниз до нуля), и в одном вызове фиксируются его предельный курс и глубина. Предельные курсы чисто разделяют два сигнала, которые смешивают курсы на полный размер: у пула с устаревшей ценой предельный курс неверен независимо от размера, тогда как у небольшого, но здорового пула предельный курс верен и лишь выход на полный размер плох. Для парных типов заявленная глубина ограничивается тем, сколько выходного токена пул физически держит, так что синтетическая пара, заявляющая $3 \times 10^{30}$ и держащая по одному токену с каждой стороны, взвешивается по тому, что она держит.

### §10.3 Полоса правдоподобия

Перед распределением кандидатам нужно поверить, а доверие должно быть привязано к величине, подделка которой чего-то стоит. Центр полосы — **взвешенная по глубине медиана** предельных курсов выживших кандидатов (`_depthWeightedMedian`): курсы сортируются вместе со своими глубинами, и медиана — это курс, на котором пройдена половина *массы глубины*. Её точка устойчивости — 50 % массы: чтобы сдвинуть её, атакующему нужно превзойти по глубине половину реальной ликвидности пары, — а взвешиваемые глубины — это `getReserves` для резервных пулов, активная ликвидность для концентрированных пулов и измеренная ликвидность для V4: величины, которые простое пожертвование пулу не сдвигает. Именно из-за последнего свойства якорем не служит наибольший реальный баланс: перевод в пул раздувает `balanceOf`, не сдвигая ни резерв, ни цену, и может быть возвращён жертвователем, так что это не капитал под риском, — внешний исследователь продемонстрировал захват прежнего якоря по балансу через пожертвование, и якорь был заменён. Кандидаты, чей предельный курс отклоняется от центра больше чем на `MEDIAN_FILTER_BPS = 500` (±5 %), отбрасываются до того, как что-либо отправлено; набор из одного кандидата вообще не фильтруется, а набор, чей центр округляется до нуля, возвращается к простой медиане, чтобы иначе маршрутизируемый хоп не погиб из-за артефакта округления. Ширина — откалиброванный выбор: стабильные кривые Solidly, котирующие пары liquid staking, обычно отстоят от истинного курса на 20–40 % и исключаются; здоровый пул, чьё показание для данного размера сделки немного отличается от центра, допускается. Метаморфная линия из §15 измерила полосу извне: когда один пул оценён в $10^{10}$ раз дальше своего соседа, план платит на 35 % меньше, чем один только выброс, — это медиана отказывается ему верить.

### §10.4 Распределитель и три его стража

Веса пропорциональны измеренной глубине и нормированы относительно наибольшей глубины в наборе (`_weights`) — одна нормировка, без режимов, потому что глубина каждого семейства выражена в токенах (§4.2) и потому сопоставима. Пропорциональное распределение — надёжный выбор для враждебной среды: оно не требует итераций и модели кривизны для каждой площадки и избегает катастрофического провала, который даёт равномерное разбиение, — в измеренном случае WETH/USDC разбиение 50 / 50 между пулом на 790 WETH и пылевым пулом на 0.34 WETH вернуло 1,302 USDC, тогда как концентрация в глубоком пуле вернула 1,638; распределение, взвешенное по глубине, вернуло 1,638. Когда в воронке выживших больше, чем бюджет хопа, сохраняются лучшие в пределах бюджета *по весу*, так что глубокий пул, поздно попавший в список, вытесняет тонкие, стоящие раньше. Затем между распределением и маршрутом, в который оно превращается, стоят три стража.

- **Ограничитель ёмкости.** Формула концентрированной ликвидности для одного тика моделирует ликвидность текущего тика так, будто она охватывает все цены, поэтому на тонком пуле она может пообещать больше, чем пул когда-либо держал, — в одном наблюдённом случае в 117× больше. Поэтому концентрированная нога на площадке пулового вида (типы 1 и 6) ограничивается значением `MAX_CONC_DRAIN_BPS = 3,000` — 30 % — от *измеренного* реального баланса выходного токена пула. На ноги V4 ограничитель не распространяется: синглтон держит токены всех пулов на одном балансе, поэтому Solver пропускает это чтение для типов 4 и 8, и обещание V4 не несёт физического ограничения сверх нижней границы из §11.3 — эта граница и есть его ограничение. Ограничитель двухуровневый: когда котировка превышает всё, что держит пул, обещание физически невозможно, и ограничитель урезает *зафиксированный вход* ноги в той же пропорции, каскадом передавая освободившийся вход оставшимся ногам, а всё, что нельзя маршрутизировать, возвращается вызывающему; когда исполнение агрессивно, но возможно, ограничивается только обещание, потому что урезание капитала в этом случае, как показали измерения, вынуждает частичные исполнения на пулах, которые исполняются нормально. Формулы, ограниченные резервами, не могут пообещать лишнего и не затрагиваются. Этот ограничитель намеренно читает `balanceOf` там, где полоса этого не делает: полоса задаёт *относительный* вопрос, который пожертвование может исказить, ограничитель — *физический*: может ли этот пул это заплатить? — а пожертвование повышает то, что пул действительно может заплатить.
- **Фильтр разбиения.** Каждая дополнительная нога стоит реального газа, а распределитель намеренно не содержит члена с ценой газа, потому что сравнение газа в нативном токене с выходом в токене сделки потребовало бы ровно того ценового оракула, иметь который протокол отказывается. Безоракульный заменитель — порог: разбиение сохраняется, только если $\mathrm{out}_{\mathrm{split}} \ge \mathrm{out}_{\mathrm{single}} \cdot (1 + 25/10^{6})$ — `MIN_SPLIT_IMPROVEMENT_PPM = 25`, — иначе распределение схлопывается в одну ногу. Порог — измеренное число: истинная точка безубыточности между газом ноги и её улучшением составила 3 ppm на Base (320 байт calldata на дополнительную ногу при 105.5 газа за байт), а фильтр стоит примерно в восемь раз выше, выраженный в миллионных долях, потому что четверти базисного пункта в базисных пунктах не существует; его история — 20 bps, затем 5, затем 0.25 — это летопись двух значений, которые никогда не калибровались по измерению. Когда фильтр схлопывает маршрут, он измеряет на полном размере не одного одноногового кандидата, а двух — самый глубокий пул и пул с лучшим предельным курсом, — потому что предельные курсы льстят мелким пулам, а глубина служит заменителем способности выдержать весь размер; побеждает лучшее исполнение.
- **Страховка измерением, а не запасом.** Прежний дизайн на этапе планирования срезал каждую ногу длинных маршрутов на 5 bps в качестве страховки от дрейфа между ногами. Затем дрейф измерили на девяти парах Base в одном блоке: худший случай −2 bps, на порядок ниже страховки, которую каждый честный многоноговый маршрут платил в каждой сделке, — в 27× больше измеренной опасности. Срез упразднили; нижние границы, применяемые к реализованному выходу, несут этот риск при нулевой стоимости планирования. Измерение было сделано в спокойное окно, и контраргумент зафиксирован в исходном коде: если консерватизм когда-нибудь вернут, правильное место для него — нижняя граница, которая проверяется, а не оценка, которая только ранжирует.

Собранный маршрут несёт то, что Router выведет заново: зафиксированный вход и ожидаемый выход каждой ноги, взвешенное по долям ценовое влияние маршрута и нижнюю границу Φ, которую Solver удостоверяет той же арифметикой, что применяет Router (§11). Solver также оценивает исполнение каждой ноги по типу по лестнице `THETA_GAS` и публикует итог как `estGas` — справочную величину, которую несёт маршрут и которую ничто ончейн не читает. Практическое правило для читателя переживает всю эту машинерию без изменений: прежде чем подтверждать, сравните улучшение, которое заявляет роутер, с газом, который показывает ваш кошелёк.

## §11 Уровень исполнения и нижняя граница Iron-Law Φ

Solver предлагает; Router располагает — атомарно или никак. Router — единственный контракт, который перемещает средства, не держит их между транзакциями и построен на одном принципе: один механизм на одну задачу, применяемый везде. Он не предполагает ничего, что может измерить.

### §11.1 Четыре входа, один исполнитель

| Вход | Сигнатура (сокращённо) | Авторизация | Маршрут |
|---|---|---|---|
| `swapExactIn` | `(route, amountIn, userMinOut, recipient, deadline)` | классические `approve` + `transferFrom` | из calldata |
| `swapExactInWithPermit2` | `(route, amountIn, userMinOut, recipient, deadline, permit, signature)` | одношаговый перевод по подписи Permit2 через канонический контракт Permit2; никакого постоянного разрешения для Router | из calldata |
| `swapExactInNative` | `(route, userMinOut, recipient, deadline)` payable | нативная стоимость, оборачиваемая один раз на входе; отказ по умолчанию, пока не подключён `weth` (`RouterE(3)`) | из calldata |
| `swapBestExactIn` | `(tokenIn, tokenOut, amountIn, userMinOut, recipient, deadline)` | классическая | решается во фрейме Solver-ом (`findBestRoutePlan`), затем исполняется через самовызов с явно переданным плательщиком |

Только `swapBestExactIn` вызывает Solver внутри транзакции; остальные три принимают маршрут, который вызывающий вычислил вне сети, бесплатно, через Quoter (§12). Все четыре сходятся в одном приватном исполнителе (`_execute`), поэтому каждая проверка ниже выполняется на каждом входе. Аккаунт с делегированием EIP-7702 вызывает те же входы, что и контракт; отдельной точки входа для него нет.

Два отказа срабатывают до того, как что-либо сдвинется. Нулевой минимум отклоняется на каждом входе — `RouterE(10)`, без исключений, без флага совместимости, — потому что минимум — единственная граница, которую контракт не может вычислить за пользователя, а страж, которого может не быть, — не страж. А маршрут с более чем тремя хопами, более чем пятью ногами в хопе или с хопом, чей входной токен не является выходом предыдущего хопа, отклоняется с `RouterE(3)`: непрерывность хопов — это то, что делает известным «чужой» баланс каждого промежуточного токена до свопа, поэтому сконструированный маршрут не может назвать застрявший токен своим входом и пустить в своп весь баланс Router по этому токену.

### §11.2 Измерение — с первой инструкции

Какой бы вход ни использовался, первое, что делает Router, — измеряет, что пришло: он читает дельту собственного баланса после списания, а не доверяет номинальной сумме, поэтому токен, недопоставляющий при переводе, входит в маршрут в своём истинном размере. Базовые уровни фиксируются один раз: баланс входного токена до свопа (`baseIn`) и баланс выходного (`toutStart`) снимаются на входе, и каждое последующее измерение — дельта относительно них, поэтому токены, уже лежащие в контракте, — пыль, пожертвования, остатки несвязанной истории, — исключены из того, что своп может выплатить. Маршрут, которому удастся потратить ниже собственного базового уровня, падает на проверяемой арифметике — безопасный исход, ведь эти средства никогда не принадлежали этому свопу.

Многохоповый маршрут — это план, а планы встречаются с реальностью на каждой границе хопа. Router масштабирует ноги каждого хопа по балансу, который *действительно* пришёл, — хоп 0 по измеренному входу после списания, ограниченному тем, что зафиксировал маршрут (так Router никогда не впихивает заранее нарезанный ордер в тонкий пул); каждый следующий хоп по измеренному балансу мостовой монеты сверх её чужого базового уровня, — и каждая нога исполняет $\mathrm{amt} = \mathrm{amountIn}_{\mathrm{leg}} \cdot \mathrm{real}/\mathrm{planned}$ (`_hopScaleImpactAndQuote`). Нога, отмасштабированная до нуля, пропускается; непотраченный вход и остатки мостовой монеты возвращаются плательщику, никогда не Router. Тот же проход читает состояние пула каждой ноги один раз и из этого единственного чтения получает масштабированную сумму ноги, её измеренное влияние и её котировку во фрейме — три величины, которые потребляют нижние границы, — поэтому резервное и концентрированное семейства не платят лишний `staticcall` за своё измерение.

### §11.3 Три нижние границы под каждой ногой

**Нижняя граница на ногу.** Измеренный вклад каждой ноги в выходной баланс Router должен достичь `LEG_FLOOR_BPS = 8,000` — 80 % — её границы, иначе весь своп откатывается с `RouterE(5)` (`_execScaled`), так что один зажатый сэндвичем или манипулированный пул сразу проваливает транзакцию, а не прячет свою потерю в остальном здоровом итоге. Граница — это заверенная котировка вызывающего, пропорциональная фактически потраченной сумме, — *поднятая* до котировки, измеренной во фрейме, если заверение покрывает меньше `MIN_QUOTE_COVERAGE_BPS = 5,000` от неё. Это максимум, а не минимум, намеренно: для нижней границы `min(claimed, measured)` с заниженным заявлением возвращает заниженное и ослабляет защиту — именно эту атаку и закрывает шлюз. Страж срабатывает всегда, когда есть хоть какая-то основа для нижней границы — заверение вызывающего или котировка во фрейме, — поэтому запись нуля в маршрут больше не отключает собственную нижнюю границу вызывающего. Его предел: площадка без измеримой котировки и без заверения переходит к агрегатным нижним границам и минимуму пользователя, которые всё равно её ограничивают.

**Агрегатная нижняя граница на хоп.** Нижняя граница на ногу локальна, а композиция глобальна: атакующий, контролирующий одну ногу из $L$, мог бы извлечь около $20\,\% \cdot (L-1)/L$ хопа, не нарушив ни одной отдельной границы. Поэтому каждый хоп может потерять не больше, чем могла бы законно потерять одна *средняя* заверенная нога: $\sum \mathrm{got} + \lfloor (\mathrm{BPS} - \mathrm{LEG\_FLOOR\_BPS}) \cdot \lfloor \sum \mathrm{attested} / n \rfloor / \mathrm{BPS} \rfloor \ \ge\ \sum \mathrm{attested}$ — допуск равен 20 % средней заверенной ноги, иначе `RouterE(5)`. Среднее, а не максимум, потому что при максимуме атакующий раздувает собственную ногу, чтобы раздуть общий бюджет. Для одной ноги правило в точности сводится к нижней границе на ногу — ноль новой жёсткости, по построению.

**Нижняя граница маршрута Φ** — это §11.5.

### §11.4 Один колбэк, один unlock

Каждая площадка формы V3 — Uniswap V3, Algebra, концентрированные форки Solidly, Pancake V3, Sushi V3 — использует один и тот же паттерн flash-учёта под разными именами колбэка: пул вызывает обратно посреди свопа с дельтой для token0, дельтой для token1 и произвольными данными, требуя оплаты. Вместо отдельного именованного колбэка на каждый форк Router предоставляет единственный `fallback`, который отвечает им всем, — **Доктрина единого колбэка**: он читает две дельты, по их знакам определяет, какой токен должен, и платит ровно эту сумму, беря контекст текущей ноги из transient storage (`TSLOT_POOL`, `TSLOT_TOKEN`, `TSLOT_AMT`). Поскольку fallback доступен любому, он охраняется с трёх сторон: платит только пулу, зафиксированному для текущей ноги, отказывается действовать, когда никакая нога не исполняется (transient-контекст между свопами равен нулю), и никогда не выдаёт больше записанного бюджета ноги — второе требование в пределах одной ноги отклоняется с `RouterE(6)`. Новый форк V3 с новым именем колбэка поддерживается в день своего развёртывания.

Синглтон V4 обёрнут за тем же интерфейсом хопа: `unlock`, затем своп, `sync → settle → take`, повторная блокировка, с валютами в transient storage и нативным швом из §5.3. Прежде чем исполнится любая нога V4, Router читает адрес хука и отклоняет ногу, если его биты объявляют права на изменение дельт (`RouterE(9)`); отсеянный хук — это исполняемый хук, потому что канонический PoolManager возвращает данные unlock дословно (`test/V4SievedHookIsTheExecutedHook.t.sol`). Правило формы маршрута — канонический порядок на весь маршрут: как только исполнилась нога с хуком, ни одна нога без хука не исполняется после неё ни в этом, ни в любом последующем хопе (`sawHooked`), поэтому хук не может наблюдать более позднюю ногу без хука того же маршрута до её расчёта; нога с хуком может стоять в более раннем хопе, только если каждая нога после неё тоже с хуком.

**Комиссия при переводе (fee-on-transfer) — на ногах, которые могут это себе позволить.** Некоторые токены недопоставляют при каждом переводе. На ногах с постоянным произведением Router обрабатывает это каноническим паттерном — сначала перевод, затем чтение резервов пула и его баланса в одном и том же состоянии после перевода и расчёт выхода по разнице, которую пул действительно видит, — величина, верная по построению, потому что это ровно та величина, которую проверит собственный инвариант пула. Ветвь включается, только когда токен измеряется с недостачей (`TSLOT_FOT`), и цена её включения указана вместе с ней: выведенная из котировки нижняя граница маршрута вычислялась вслепую относительно налога и для этого свопа отбрасывается; протокольная нижняя граница перемасштабируется по измеренному чистому отношению (с округлением вверх, в консервативную сторону); границы на ногу переоцениваются по тому же отношению; а обязывающим ограничением становится минимум пользователя. Концентрированные ноги не поддерживают fee-on-transfer по замыслу — flash-учёт должен платить ровно то, что причитается, — поэтому облагаемый налогом токен на маршруте только из V3 отклоняется заранее с `RouterE(13)`, а не откатывается внутри пула.

### §11.5 Нижняя граница Iron-Law Φ полностью

Не каждый маршрут, который может исполниться, должен исполняться — маршрут, возвращающий 60 % справедливой стоимости, хуже, чем отсутствие сделки, — поэтому под всем вышеописанным лежит нижняя граница, которую протокол выводит сам. Это доля сохранения в базисных пунктах (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

Её определяют три константы: `FLOOR_BASE_BPS = 9,600`, `FLOOR_PER_LEG_BPS = 200`, `FLOOR_HARD_MAX_LOSS_BPS = 2,000`. Нижняя граница начинается с 96 % для чистого свопа в одну ногу, ослабляется на один базисный пункт за каждый базисный пункт измеренного влияния $\delta$ и на 200 bps за каждую *эффективную* дополнительную ногу и ограничена так, что никогда не опускается ниже 8,000 bps: протокол никогда сознательно не допустит маршрут, сохраняющий меньше 80 % своего эталона, какими бы ни были входные данные. Срез за ноги вычисляется из *концентрации* сделки по ногам — $(\sum a)^{2}/\sum a^{2}$, эффективного числа ног, ровно $N$ для $N$ равных долей и ровно 1 для одной доминирующей ноги, — поэтому разбиение получает ослабление пропорционально тому, насколько оно действительно разбито, а нога с исчезающе малой долей получает исчезающе малую величину, объявит ли она ноль или один wei. По неравенству Коши–Буняковского–Шварца числитель никогда не уходит в отрицательную область. Член влияния — средневзвешенное по долям измеренных влияний ног по маршруту. Два свойства опубликованы как часть определения: член волатильности $\sigma_{\ln}$ присутствует в сигнатуре и передаётся буквальным нулём в каждом месте вызова — задел для будущего оценщика, пока неактивный, — а влияние резервного семейства исключает комиссию, тогда как влияние концентрированного семейства её включает, каждое в той форме, в какой его даёт спецификация семейства.

Φ становится принуждением, а не эвристикой, благодаря тому, где и на чём она работает. Маршрут, переданный в Router, несёт рекомендательные поля — котируемый итог, заверенную нижнюю границу, — и Router не доверяет ни одному: внутри исполняющего фрейма он измеряет реальное влияние каждой ноги, вычисляет срез за ноги из переданных ему сумм, заново вычисляет `floorBps` и применяет его к **котировке последнего хопа во фрейме при сумме, которая действительно туда пришла**, — эталону, выраженному в выходном токене, выведенному из состояния пула, прочитанного во время этого исполнения, и неподделываемому через calldata. Выбор эталона — то, что вообще позволяет стражу срабатывать: доля *реализованного* выхода никогда не может превысить реализованный выход. Когда последний хоп нельзя прокотировать во фрейме — разрыв ликвидности на текущем тике, сентинел комиссии, — эталон откатывается к заверенной котировке хопа, которую шлюз покрытия уже поднял, а измерение fee-on-transfer переоценило, поэтому разрыв ликвидности just-in-time (JIT) в тонком пуле не может обезвредить нижнюю границу; якорь нижней границы — всегда хоп, который переместил стоимость и производит выход маршрута. Проверка выполняется над валовым выходом, до любой комиссии на стороне выхода. Эффективный минимум — максимум из трёх величин,

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

и вся суть — в его асимметрии: поля, переданные вызывающим, могут ужесточить защиту и никогда не могут её ослабить. Solver заверяет `singleOutFloor` из тех же `legShaveBps` и `ironFloorBpsShv` на тех же суммах, поэтому для маршрута, построенного Solver, заверенная и принуждаемая нижние границы совпадают по построению (`test/FloorParitySolverRouter.t.sol`), а режимная обвязка из §15 закрепляет два фрейма на каждой сгенерированной строке: при комиссии со входа принуждаемая нижняя граница лежит в $[\mathrm{attested} \times (1 - \mathrm{fee}),\ \mathrm{attested}]$; при комиссии с выхода она равна заверенной нижней границе.

![FIG-7 — Нижняя граница Iron-Law: сохранение в зависимости от измеренного влияния для одной ноги, двух равных ног и четырёх равных ног; жёсткое ограничение 80 %; отмечена точка Разобранного примера 3.](https://blazephoenix.xyz/whitepaper/figs/ru/fig-7.png)

**Разобранный пример 3 — нижняя граница на двуххоповом маршруте.** Маршрут A → WETH → B: хоп 0 делит 600 A в пул P и 400 A в пул Q; хоп 1 отправляет полученный WETH через один пул R. Измерено во фрейме: ноги хопа 0 показывают влияние 100 и 60 bps, нога хопа 1 — 40 bps, а средневзвешенное по долям по маршруту — 80 bps. Котировка хопа 1 во фрейме при фактически пришедшей сумме WETH — 1,000.000 B.

1. Концентрация хопа 0: $(600 + 400)^{2} = 1{,}000{,}000$; $600^{2} + 400^{2} = 520{,}000$; $n_{\mathrm{eff}} = 1.923$; срез $= \lfloor 200 \times 480{,}000 / 520{,}000 \rfloor = \lfloor 184.6 \rfloor = 184$ bps.
2. У хопа 1 одна нога: $(\sum a)^{2} - \sum a^{2} = 0$; срез 0. Итого `legShv` = 184.
3. $\mathtt{floorBps} = \max(9{,}600 - 184 - 80 - 0,\ 8{,}000) = 9{,}336$: маршрут должен сохранить 93.36 % своего эталона.
4. $\mathtt{protocolFloorOut} = \lceil 1{,}000.000 \times 9{,}336 / 10{,}000 \rceil = 933.600$ B.
5. При `userMinOut` = 990 B, заданном трейдером, и `singleOutFloor` = 933.6 B, заверенном Solver, `effMin` = 990 B — обязывающей является собственная граница пользователя, как и должно быть на честном маршруте. Поставка 998.4 B проходит расчёт; поставка 985 B отклоняется с `RouterE(5)`, и вся транзакция, включая своп каждого пула, никогда не происходила.

Если бы трейдер задал `userMinOut` = 900 B, обязывающей стала бы протокольная нижняя граница в 933.6 B: исполнение по 930 B, на 7 % ниже эталона во фрейме, отклоняется, хотя оно и проходит число пользователя.

### §11.6 Расчёт и как его читать

Расчёт закрывает фрейм так же, как он открылся: измерением. Router берёт дельту своего выходного токена относительно базового уровня на входе как валовой выход (`RouterE(8)`, если она равна нулю), выполняет описанную выше проверку нижней границы, взимает комиссию там, где её размещает режим (§13), переводит чистую сумму получателю и измеряет дельту баланса *получателя*, применяет `userMinOut` к этой доставленной сумме — так выходной токен с комиссией при переводе не может опустить пользователя ниже его границы, — и один раз читает реестр комиссий: расчёт, ничего не заплативший протоколу, отклоняется (`RouterE(15)`), а на якорном маршруте отклоняется второй платёж (`RouterE(16)`). Затем, и только на успешном пути, он вызывает `recordSwap` у Hub один раз на каждую исполненную ногу (`_recordHits`) с суммами, которые хоп смог потратить, — отношение измеренного к объявленному, применённое к каждой ноге, — и это единственная внешняя запись, которую выполняет своп; когда нижняя граница отклоняет, ничего не записывается.

Каждый расчёт порождает `ExecutionProof(user, tokenOut, quoted, realized, floorUsed, blockNumber)`: эталонную котировку, полученную в том же фрейме, что и исполнение, опубликованную как ончейн-ряд, который любой может заново вывести через `eth_call` к `findBestRoutePlan` в этом блоке. Его поля, без преувеличений: `user` — плательщик, никогда не `msg.sender`; `quoted` — котировка последнего хопа: она доказывает последний хоп, а не маршрут; `realized` — то, что доставлено, измеренное у получателя; `floorUsed` — протокольная нижняя граница, которую нужно было превзойти. Каждое событие `Fee(token, amount, toT1, toT2)` называет токен и распределение.

**Разобранный пример 6 — чтение расчёта.** Маршрут из Примера 3 рассчитывается и порождает `ExecutionProof(0xUSER, B, 1,000.000, 998.400, 933.600, N)` и одно `Fee(WETH, 0.014, 0.0042, 0.0098)`.

| Величина | Значение | Прочтение |
|---|---|---|
| `quoted` | 1,000.000 B | котировка последнего хопа во фрейме при фактически пришедшем WETH |
| `realized` | 998.400 B | доставлено получателю, измерено там; 9,984 bps котировки |
| `floorUsed` | 933.600 B | `floorBps` 9,336, применённый к котировке; исполнение оказалось на 64.8 B выше |
| `Fee` | 0.014 WETH в одном событии | якорный режим: 28 bps от 5.000 WETH, полученных хопом 0, оплачено на входе хопа 1, 30 % в первую казну; из B ничего не взято |
| расстояние до `userMinOut` | 8.4 B | граница пользователя в 990 B оставила 0.84 % запаса |

Доставленное минус котируемое — ответ рынка, а не протокола: комиссия ушла раньше, в WETH, и на этом маршруте нет удержания на стороне выхода. Событие `Fee`, выраженное в выходном токене, появляется только на прямом маршруте в мостовую монету (§13).

### §11.7 Отказы Router

| Код | Значение | Где |
|---|---|---|
| `RouterE(1)` | неавторизованный управляющий вызов | `onlyControl` |
| `RouterE(2)` | пауза, или попытка отказа от управления во время паузы | `whenLive`, `renounceControl` |
| `RouterE(3)` | неверный ввод — нулевой адрес, неподключённый нативный вход, число хопов, ног на хоп, разрыв хопов | точки входа, `_execute` |
| `RouterE(4)` | срок истёк | точки входа |
| `RouterE(5)` | выход ниже нижней границы — на ногу, агрегатной на хоп, границы маршрута или `userMinOut` | `_execScaled`, `_execute` |
| `RouterE(6)` | колбэк не авторизован — не тот пул, нет исполняемой ноги, второе требование | `fallback` |
| `RouterE(7)` | повторный вход | `nrEntrant` (transient-блокировка) |
| `RouterE(8)` | своп не удался — нулевой выход, неизвестный тип, комиссия не меньше суммы | `_execute`, `_execScaled`, `_chargeHopFee` |
| `RouterE(9)` | хук V4 изменяет дельты | предварительная фиксация ноги V4 |
| `RouterE(10)` | `userMinOut == 0` при `amountIn > 0` | каждый вход |
| `RouterE(13)` | токен с комиссией при переводе на маршруте только из концентрированных площадок | `_execute` |
| `RouterE(14)` | спасение не поставлено в очередь или находится внутри своей 48-часовой задержки | `executeRescue` |
| `RouterE(15)` | расчёт без оплаты протокольной комиссии | реестр комиссий, конец `_execute` |
| `RouterE(16)` | комиссия оплачена дважды на якорном маршруте | реестр комиссий, конец `_execute` |

Коды 11 и 12 не назначены. Поверхность откатов, столь же перечислимая, как API, — это то, что позволяет режимным массивам из §15 судить каждую строку по одному правилу — рассчитаться или отказать с нашим селектором — и называть всё остальное третьим исходом.

## §12 Поверхности котировок и как котировка стареет

Число под названием «ваша котировка» может прийти из двух мест, и это разные пути кода: **Quoter** — контракт предварительного просмотра только для чтения, который интерфейс вызывает до того, как вы подпишете, — и **путь во фрейме**, где Solver и Router вычисляют котировки внутри исполняющей транзакции. §4.4 дал истину по каждому типу; этот раздел описывает поверхности и измеряет разрыв между ними.

### §12.1 Поверхности Quoter

| Функция | Возвращает | Примечания |
|---|---|---|
| `previewPlan(tIn, tOut, amountIn)` | `Preview` | план Solver, упакованный с комиссией и буфером безопасности |
| `previewPlanWithMinOut(…, userMinOut)` | `Preview` | то же, но `canExecute` оценивается относительно минимума вызывающего |
| `previewAndEncode(…)` / `previewAndEncodeWithMinOut(…)` | `Preview` и calldata для `swapExactIn` | так интегратор получает котировку — исследование §12.2 использует ровно это |
| `batchQuote(entries[])` | до `MAX_BATCH = 32` превью | пара, которую нельзя спланировать, оставляет запись, инициализированную нулями |
| `previewPlanExact(tIn, tOut, amountIn)` | маршрут и точный чистый выход | Exact Pass: каждая концентрированная нога прогоняется вхолостую через собственный своп пула |

**Что такое превью.** Структура `Preview` по замыслу рекомендательная — обязывающие проверки те, что Router заново выводит во фрейме, — и её поля документированы в месте определения: `grossOut` повторяет итог плана; `protocolFee` — влияние комиссии на выход, смоделированное по режиму ровно так, как её взимает Router (§13), — один раз на якорном маршруте, один раз на хоп на маршруте в режиме исчерпания, с округлением вверх, как округляет Router; `safetyBuffer` — межножечный буфер

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

ноль при двух ногах или меньше, один базисный пункт на каждую ногу сверх двух, плюс пять за каждую ногу с постоянным произведением, оценённую по *предполагаемой* комиссии (объявленной комиссии, которую Core заменил в пределах своего потолка), под одним общим пределом в десять, потому что буфер без потолка перестаёт быть буфером и становится нижней границей; `netOut` — это $\mathrm{grossOut} - \mathrm{protocolFee} - s$; `ironFloor` повторяет заверенную нижнюю границу; `topology` и `bridgeUsed` выводятся из числа хопов и из якоря комиссии, который использует Router, — тот же просмотр того же производителя, поэтому превью и исполнитель не могут назвать разные мосты. Правило для интегратора умещается в одно предложение и является самым важным советом в этом документе: выводите свой минимум из собственного ожидания цены, никогда — из поля превью.

**Exact Pass.** Для точного по пулу превью концентрированной площадки Quoter вызывает реальную точку входа свопа пула; пул вызывает обратно, требуя оплаты; колбэк Quoter вместо оплаты откатывается, унося вычисленные дельты в данных отката. Откат сворачивает всё состояние, ничего не оплачено, а декодированный выход — собственный своп площадки над живым состоянием, выполненный и отменённый; V4 обрабатывается так же — через unlock, чей колбэк откатывается с упакованной дельтой. Пул, отказывающийся от холостого прогона, сохраняет собственное заявление плана, отмасштабированное по хорде под ним, — никогда не выше, потому что выход AMM вогнут. Хоп 0 ограничен тем, что зафиксировал план, поэтому маршрут, урезанный по ёмкости, не прогоняется вхолостую на размере, который Router никогда не потратит. С 2026-09-03 возвращаемый скаляр — за вычетом протокольной комиссии, вычтенной один раз так же, как её вычитает превью, поэтому минимум, выведенный из него, соблюдается нижней границей (`test/QuoterExactNetOut.t.sol`). Это инструмент Quoter на стороне превью; обязывающая поверхность — повторный вывод во фрейме.

### §12.2 Как котировка стареет

Котировка — это обещание о будущем блоке. Исследование получает её так, как это делает интегратор, — `previewAndEncode` возвращает превью и calldata, — даёт миру сдвинуться (от нуля до трёх сделок кого-то другого через те же пулы, каждая до 3 % мелкого резерва, все в направлении пользователя, так что каждая из них вредит), даёт пройти от нуля до десяти секунд и исполняет calldata без изменений. 240 выборок на трёхтокенной вселенной моков с постоянным произведением (A, B = мостовая монета, C; напрямую в мост, два хопа через него, два хопа в обратную сторону), каждый исход классифицирован (`test/QuoteDelayStatistics.t.sol`):

| Дрейф между котировкой и исполнением | Выборки | Рассчитано | Отклонено нижней границей (`RouterE(5)`) | Доставлено / предсказано — среднее | мин. |
|---|---|---|---|---|---|
| нет | 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 |

| Задержка между котировкой и исполнением | Выборки | Рассчитано | Отклонено (`RouterE(5)`) | Доставлено / предсказано — среднее | мин. |
|---|---|---|---|---|---|
| 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 — Как котировка стареет: рассчитанные и отклонённые выборки по корзинам дрейфа с отношением доставлено/предсказано для каждой рассчитанной выборки; те же 240 выборок, разбитые по задержке, не показывают тренда.](https://blazephoenix.xyz/whitepaper/figs/ru/fig-8.png)

Котировку двигает не время, а дрейф. Проверено на каждой выборке: котировка без дрейфа рассчитывается при любой задержке и доставляет ровно свой прогноз (64 из 64); расчёт никогда не доставляет ниже нижней границы, заверенной превью; третьего исхода нет. После срока (`deadline = now + 5`, исполнение на `+10`) 20 из 20 отклоняются собственным кодом срока, `RouterE(4)`, и ни одна не рассчитывается. При неблагоприятном дрейфе до 1 % каждая котировка всё равно исполняется, в пределах 2 % от прогноза; при 1–3 % нижняя граница отклоняет одну из четырёх, а не исполняет её ниже заверенного выхода, и исполнения ложатся в пределах 3.5 % от прогноза — кривая сэндвича из §14, увиденная со стороны котировки.

**Живой Base** (`test/fork/QuoteDelayFork.t.sol`, 1,000 USDC → WETH, реальные пулы, протокол развёрнут на форке на текущем блоке): при исполнении через 0, 3, 6 и 10 s, когда больше ничего не двигалось, котировка все четыре раза доставила ровно свой прогноз. Когда перед ней через тот же маршрут торговалось 10,000 · 50,000 · 200,000 · 1,000,000 · 5,000,000 USDC, а устаревшие calldata исполнялись через 10 s, все пять рассчитались внутри нижней границы на 9,999 · 9,998 · 9,994 · 9,974 · 10,000 bps от прогноза: миллион долларов перед тысячей стоит тысяче четверть процента, а сделка на пять миллионов прошла через пулы, которых маршрут на тысячу долларов не касается. Приведённые выше распределения — по мокам; живой Base даёт охват по одной паре на одном блоке, а не распределение.

### §12.3 Сколько стоит котировка через ABI

Газ измерен вокруг внешнего вызова, по сорок размеров на трёхтокенной вселенной (`test/QuoterGasStatistics.t.sol`) и пять размеров на живом Base (`test/fork/QuoterGasFork.t.sol`):

| Мир | `previewPlan` среднее | мин. | макс. | σ | `previewAndEncode` среднее | σ |
|---|---|---|---|---|---|---|
| моки — обнаружение: пара известна только фабрике, найден один пул | 121,313 | 116,468 | 123,491 | 1,433 | 123,317 | 1,851 |
| моки — свежий реестр: оценены три засеянных пула | 235,574 | 227,205 | 237,843 | 1,815 | 239,647 | 1,937 |
| живой Base — холодный: реестр пуст, допущенные фабрики просмотрены | 1,524,221 | | | | 1,525,341 | |
| живой Base — тёплый: свежий после одного исполнения, зарегистрированы два пула | 1,414,633 | | | | 1,416,037 | |
| живой Base — снова холодный после TTL обнаружения | 1,359,925 | | | | 1,361,582 | |

`batchQuote` из десяти записей на моках: 2,055,390 газа, 205,539 на запись. Мок со свежим реестром стоит дороже обнаружения, потому что оценивает три пула там, где обнаружение нашло один; честное сравнение — живое, где просмотр при обнаружении составляет около 7 % котировки, — а котировка — это view-вызов, за который интегратор никогда не платит в сети.

## §13 Комиссионная политика: два режима и Правило излишка

*Просто:* протокол берёт 0.28 % один раз, с одной измеренной суммы, в монете, которую казначейство хочет держать, и подсчитывает, что сделал это. *Точно:* `PROTOCOL_FEE_BPS = 28` — константа времени компиляции в Core, и ни в одном месте развёрнутого байткода у неё нет сеттера; комиссия равна `mulDivUp(base, 28, 10,000)` — с округлением вверх, поэтому нулевая комиссия недостижима для любой ненулевой базы — и делится так: `TREASURY1_SHARE = 3,000` б.п. первому казначейству, остаток второму (вычисляется как `fee − t1`, поэтому две части всегда в сумме дают комиссию без пыли); казначейства задаются при развёртывании и могут быть заморожены навсегда Дверью в одну сторону. Ни один ключ не может поднять ставку против трейдера и ни один ключ не может снизить её для привилегированного интегратора: вопрос комиссии решён во время компиляции и исключён из списка того, что может сделать ключ.

### §13.1 Где взимается комиссия: два режима, оба названы

С 2026-08-22 правило умещается в одно предложение: **взимать один раз, с первой мостовой монеты, которую держит Router.** Router просматривает маршрут в поисках первого хопа, входной токен которого — зарегистрированный мост (`hub.isBridgeToken`, в порядке хопов), — этот хоп и есть `feeHop` — и взимает 28 б.п. с *измеренного* входа этого хопа: на хопе 0 — с измеренного списания, ограниченного тем, что обязался потратить маршрут, на более позднем хопе — с баланса мостовой монеты у Router сверх его чужой базовой линии, то есть с величины, уже устойчивой к комиссии при переводе (fee-on-transfer) и к посторонним балансам. Больше на этом маршруте Router ничего не взимает. Прямой маршрут, назначение которого само является мостовой монетой (TOKEN → WETH), вместо этого платит с выхода, после проверки нижней границы: граница проверяет качество свопа, доля протокола вычитается из уже проверенной суммы, а минимум пользователя сравнивается с тем, что он фактически получает. Это **якорный режим**. Почему с моста, а не с назначения: мосты — это WETH и USDC, и казначейство получает ликвидную монету, которую хочет держать, а не пыль какого-нибудь хвостового токена назначения. Почему со входа хопа 1, а не с выхода хопа 0: это тот же токен и та же сумма, но вход хопа 1 измерен по реальному балансу — а это измерение Router и так выполняет.

У правила есть второй режим, который до 2026-09-05 в формулировке не назывался. Собранный вручную маршрут через пулы, которые реестр не стал бы держать, и без мостовой монеты на входе какого-либо хопа никогда не находит `feeHop`; предикат взимания тогда истинен для *каждого* хопа, и каждый хоп платит 28 б.п. со своего измеренного входа. Это **режим исчерпания**, и это сознательное правило, а не недосмотр: взимание с такого маршрута один раз, на хопе 0, было опробовано в тестовом наборе в тот день и снова открыло лазейку, ради закрытия которой существует политика исчерпания, — бесценный первый хоп переносит место комиссии на пыль, а реальный хоп не платит ничего (`test/FeeEscapeViaJunkPrefix.t.sol`); пять закреплённых тестов отвергли изменение в том же прогоне. Нет позиции, куда можно вставить пыль так, чтобы уйти от всех хопов, — именно это и означает «иммунитет через исчерпание». Solver строит каждый многохоповый маршрут через зарегистрированные мосты, поэтому маршрут, построенный Solver, всегда платит ровно один раз; режим исчерпания — то, с чем сталкивается вызывающий, когда маршрутизирует в обход реестра, и Quoter его моделирует (`_pack` взимает `hops.length` раз, когда ни один вход хопа не является мостом, и один раз в остальных случаях), так что предпросмотр и доставка совпадают в обоих режимах (`test/ExhaustionRegimePreviewParity.t.sol`).

![FIG-9 — Два режима комиссии, как их определяет Router: просмотреть хопы в поисках первого входа в мостовой монете; якорные маршруты платят один раз на этом хопе (или с выхода прямого маршрута в мостовую монету); маршруты без мостового входа платят один раз на хоп; учёт считает и отвергает ноль, а на якорном маршруте — и два.](https://blazephoenix.xyz/whitepaper/figs/ru/fig-9.png)

### §13.2 У обязательства один производитель, и контракт считает

Режимы сопровождают две пломбы. Сумму объявленных входов ног хопа — базу комиссии на хопе 0 и знаменатель масштаба — вычисляет одна функция, `_legSum`; до 2026-09-05 каждый суммировал ноги в собственном цикле — два производителя одного числа. И учёт комиссии: `_payFee`, единственная функция, переводящая комиссию, ведёт счёт в транзиентном слоте (`TSLOT_FEE`); каждый расчёт читает слот один раз, отвергает доставку, которая ничего не заплатила (`RouterE(15)`), а на якорном маршруте — и второй платёж (`RouterE(16)`), после чего очищает слот. Две проверки учёта — это *страховочные ремни*: в изоляции ни один тест не может заставить их сработать, потому что предикат перед ними не оставляет пути, который рассчитывается без оплаты или платит дважды на якорном маршруте. Это контракт, отвергающий во время выполнения то, что тесты отвергают во время ревью, на путях, которых пока не существует; они перечислены как ремни, нигде не засчитаны как покрытые и отсутствуют в мутационном контроле, который допускает только мутантов, убиваемых именованным тестом.

### §13.3 Правило излишка

**Правило излишка** — это аргумент согласованности интересов в дизайне комиссии, сформулированный так, как его реализует код: доля протокола — 28 б.п. от одной измеренной базы, один раз на якорном маршруте и один раз на хоп на маршруте исчерпания, и нигде в расчёте нет члена, который масштабируется с разницей между котировкой и доставкой. Когда база — вход (каждый многохоповый маршрут и каждый прямой маршрут, назначение которого не мостовая монета), выход не затрагивается: всё, что пулы доставляют сверх котировки, целиком доходит до получателя, и комиссия не может вырасти оттого, что исполнение оказалось выгодным. Когда база — выход (прямой маршрут в мостовую монету), комиссия составляет 28 б.п. от валовой доставки, включая излишек, и не более. Ось, на которой агрегатор сильнее всего соблазняется снять сливки, — это разрыв между котировкой и исполнением; этот дизайн не даёт такому разрыву вообще никакого комиссионного члена, поэтому заявленная ставка — истинная и единственная. База никогда не читается из calldata: `route.totalOut` приходит в Router как утверждение постороннего и не читается ничем (`test_FeeBase_IgnoresLiedAboutTotalOut`).

**Разобранный пример 5 — два режима на одном маршруте.** Мосты — {WETH, USDC}; трейдер продаёт 10,000 A.

*Якорный, два хопа.* A → WETH → B. Хоп 0 не облагается (его вход A — не мост). Пулы хопа 0 выплачивают 5.000 WETH, измеренные как баланс WETH у Router сверх его чужой базовой линии. `feeHop` = 1: комиссия $\lceil 5.000 \times 28 / 10{,}000 \rceil = 0.014$ WETH, из них 0.0042 первому казначейству и 0.0098 второму, и хоп 1 тратит 4.986 WETH на B. Одно событие `Fee`; учёт показывает 1 и удовлетворён; предпросмотр вычел 28 б.п. один раз.

*Якорный, напрямую в мост.* A → WETH за один хоп. На входе ничего не взимается. Пулы доставляют 5.000 WETH брутто; нижняя граница проверяется на 5.000; затем выплачивается комиссия на стороне выхода $\lceil 5.000 \times 0.0028 \rceil = 0.014$ WETH и доставляется 4.986 WETH; `userMinOut` сравнивается с 4.986. Одно событие `Fee`, номинированное в выходном токене.

*Исчерпание, два хопа.* A → C → B, где ни A, ни C, ни B не мост, — маршрут, который не строит ни один Solver. Хоп 0 платит $\lceil 10{,}000 \times 0.0028 \rceil = 28$ A и отправляет 9,972 A в свои пулы, которые выплачивают 2,000 C; хоп 1 платит $\lceil 2{,}000 \times 0.0028 \rceil = 5.6$ C и отправляет 1,994.4 C на B. Два события `Fee`; учёт показывает 2 без `feeHop`, поэтому `RouterE(16)` не применяется; эффективная ставка $1 - 0.9972^{2} = 55.9$ б.п., и предпросмотр так и показал.

### §13.4 Комиссия, измеренная снаружи Router

`test/FeeSeals.t.sol` не читает ни одного числа Router: токен комиссии берётся из правила и списка мостов, база — из дельт балансов пулов (что ушло из Router в комиссионный хоп или что выплатили пулы предыдущего хопа) или получателя, комиссия — из дельт казначейств, количество — из событий `Fee` — по всем формам, которые принимает Router (от одного до трёх хопов; мост ни в какой, в первой, средней или последней позиции; одна или две ноги на хоп), фаззинговые суммы от $10^{12}$ до $10^{21}$ wei, 2,000 прогонов, ни одного сбоя, и после каждого расчёта Router ничего не держит.

| Форма маршрута | Режим | Где берётся комиссия | Измерено |
|---|---|---|---|
| A → B (ни один не мост) | исчерпание, один хоп | вход хопа 0, один раз | комиссия = ⌈28 б.п. × вход⌉, одно событие |
| W → A (мост на входе) | якорный | вход хопа 0 | то же |
| A → W (мост на выходе, напрямую) | якорный, на стороне выхода | выход | комиссия = ⌈28 б.п. × валовой выход⌉, доставлено = валовой выход − комиссия |
| A → C → B (без моста) | исчерпание, два хопа | измеренный вход каждого хопа | два события |
| W → A → B | якорный на хопе 0 | вход хопа 0 | одно событие |
| A → W → B | якорный на хопе 1 | мостовая монета, произведённая хопом 0 | одно событие, ⌈28 б.п. × выплата пулов хопа 0⌉ |
| A → C → W (мост только как выход двух хопов) | исчерпание | вход каждого хопа | два события |
| A → C → D → B · A → W → C → B · A → C → W → B | как выше | как выше | 3 · 1 · 1 события |

**Как часто тесты комиссии замечают дефект** — измерено, а не предположено (`docs/assurance/fee-seal-detection.json`): каждый мутант комиссии — предикат режима, сдвинутый в любую сторону, производитель обязательства, две проверки учёта и три мутанта комиссии из исследования инвариантов — прогонялся на двадцати фаззинг-сидах для каждого фаззинг-теста и один раз для каждого детерминированного теста; доля обнаружения приводится с 95 % интервалом Уилсона, а там, где пропусков не было, — с границей вероятности пропуска по правилу трёх.

| Мутант | Фаззинг FeeSeals | Кампания Router (2 хопа, 2 ноги) | Покрывающий массив t = 2 | Обход через мусорный префикс | Паритет предпросмотра при исчерпании |
|---|---|---|---|---|---|
| исчерпание взимает только с хопа 0 | 20/20 [0.84, 1.00] | 20/20 [0.84, 1.00] | нет | да | да |
| исчерпание пропускает хоп 0 | 20/20 [0.84, 1.00] | 20/20 [0.84, 1.00] | нет | да | да |
| обязательство учитывает только первую ногу | 20/20 [0.84, 1.00] | 20/20 [0.84, 1.00] | нет | нет | нет |
| комиссия удвоена | 20/20 [0.84, 1.00] | 20/20 [0.84, 1.00] | да | да | да |
| комиссия на стороне входа никогда не взимается | 20/20 [0.84, 1.00] | 20/20 [0.84, 1.00] | нет | да | да |
| комиссия взимается с обеих сторон | 20/20 [0.84, 1.00] | 20/20 [0.84, 1.00] | нет | да | да |
| ремень: расчёт без комиссии больше не отвергается | 0/20 [0.00, 0.16] | 0/20 [0.00, 0.16] | нет | нет | нет |
| ремень: двойной платёж на якорном маршруте больше не отвергается | 0/20 [0.00, 0.16] | 0/20 [0.00, 0.16] | нет | нет | нет |

![FIG-10 — Доля обнаружения каждого мутанта комиссии каждым тестом в виде тепловой карты: убийства на прогоны с интервалом Уилсона; два ремня видны как строки, которые не видит ничто.](https://blazephoenix.xyz/whitepaper/figs/ru/fig-10.png)

Читайте по строке: какие тесты видят этот дефект. Читайте по столбцу: что тест может и чего не может увидеть. Первое измерение показало, что кампания Router слепа к мутанту исчерпания, щадящему хоп 0, и к производителю обязательства, потому что строила только прямые маршруты с одной ногой; в тот же день кампанию расширили: теперь она проходит два хопа и делит хоп 0 между двумя пулами, а мостовая монета есть во вселенной токенов, так что одна кампания охватывает все режимы, — и оба мутанта теперь видны 20 из 20. Покрывающий массив видит только удвоенную комиссию, потому что его строки всегда пересекают мост. Двадцать сидов с результатом 20 из 20 ограничивают вероятность пропуска одной кампании 15 %, поэтому мутационный контроль запускает и именованный тест, и кампанию.

Итог раздела в форме, принятой в этой статье, — утверждение, место принуждения, предел: никто не может сдвинуть ставку или распределение, потому что ни у одного из них нет сеттера в развёрнутом байткоде; вызывающий не может уменьшить комиссию, занизив котировку, потому что база — измеренный баланс, а `route.totalOut` ничем не читается; вызывающий не может уклониться от комиссии бесценным префиксом, потому что маршрут без мостового входа платит на каждом хопе; и расчёт не может не заплатить ничего или заплатить дважды на якорном маршруте, потому что Router считает, — с тем пределом, что две собственные проверки счётчика являются ремнями, которые ни один тест не может заставить сработать.

## §14 Модель безопасности

*Просто:* Router ничего не держит между транзакциями, каждый своп — «всё или ничего», и каждый класс атак, рассмотренный в этом репозитории, отражается именованной проверкой или назван как выходящий за рамки. *Точно:* каталог угроз — `docs/assurance/threats.json`, 23 класса, взятых из публичной истории эксплойтов; для каждого назван символ защиты, который его отвергает, и скрипт CI проваливает сборку, если символ исчезает из `src/` или именованный тест исчезает из `test/`. Покрытие каталога — нижняя граница того, что было рассмотрено, но никогда не верхняя граница того, что существует.

### §14.1 Атаки и проверки, которые их отвергают

| Класс (id) | Проверка, которая его отвергает | Где она применяется | Статус | Тест, показывающий, что проверка может сработать |
|---|---|---|---|---|
| Повторный вход (DASP-1) | транзиентная блокировка `nrEntrant` на каждой двери, перемещающей ценность; поверхность колбэков аутентифицирует вызывающего | Router `nrEntrant` → `RouterE(7)`; `fallback` → `RouterE(6)` | заблокировано | `invariant_reentrancyBlocked` (`test/RouterAdversarialV4FromV1.t.sol`) |
| Контроль доступа (DASP-2) | четыре роли; каждая привилегированная дверь защищена; уровень контроля можно отречь навсегда | Hub `onlyAdmin` / `onlyControl` → `HubE(1)`; Router `onlyControl` → `RouterE(1)` | заблокировано | `test/HubAllowHookAfterRenounce.t.sol`, `test/ControlEvents.t.sol` |
| Арифметическое переполнение/антипереполнение (DASP-3) | проверяемая арифметика повсюду; у каждого блока `unchecked` есть письменная граница; представимость сложных кривых проверена на границе (`MAX_SQRT_PRICE_MINUS_ONE`) | Core | заблокировано | `test/BoundaryEquality.t.sol`, `test/CompilerPanicSurface.t.sol` |
| Отказ в обслуживании через неограниченную итерацию (DASP-5) | каждое измерение маршрута ограничено: `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)` (фабрики), `_canInsert` (слоты) | заблокировано | `test/RouteHopCeiling.t.sol` |
| Зависимость от порядка транзакций / сэндвич (DASP-7) | нижняя граница Iron-Law, жёстко ограниченная `FLOOR_HARD_MAX_LOSS_BPS = 2,000`, применяется независимо от минимума вызывающего, который может её только ужесточить | Core `ironFloorBpsShv`; Router `_execute` → `RouterE(5)` | заблокировано (ограничено, §14.3) | `test/regime/SandwichCurve.t.sol`; `invariant_DeliveredNeverBelowTheProtocolFloor` |
| Межфункциональный повторный вход через площадку (SWC-107) | та же транзиентная блокировка охватывает весь своп, включая колбэки пулов | Router `nrEntrant` | заблокировано | `test/V4LockedRegionReentrancy.t.sol` |
| Манипуляция ценовым оракулом (SOK-ORACLE) | фида нет: эталонная котировка вычисляется в исполняющем фрейме по торгуемым площадкам, и нижняя граница выводится из неё (`ironFloorBps`) | Core; Router `_execute` | заблокировано | `test_Parity_SplitRoute_AttestedFloorEqualsEnforcedFloor` (`test/FloorParitySolverRouter.t.sol`) |
| Подмена измеренной величины величиной от вызывающего (SOK-FEE-BASE) | база комиссии — измеренный баланс (`_chargeHopFee`); `route.totalOut` ничем не читается | Router `_chargeHopFee` | заблокировано | `test_FeeBase_IgnoresLiedAboutTotalOut` (`test/BlazePhoenixRouter.t.sol`) |
| Токены с комиссией при переводе и ребейзом (SOK-FOT) | каждая значимая сумма — измеренная дельта баланса (`balanceOf`), а не предполагаемая сумма перевода | Router, каждый шов | заблокировано | `test/FotFloorReprice.t.sol`, `test/PartialFotAtPrePulledDoors.t.sol` |
| Допуск враждебной площадки в реестр (SOK-VENUE-ADMISSION) | допуск только куратором, одна строка на адрес; источник вывода удостоверяется при допуске (`factoryDeployer`) и замораживается после отречения | Hub `addFactory`, `_probe` | заблокировано | `test_C4_FirstPin_AfterRenounce_MustNotAttestTheLiveAnswer` (`test/T19ReadmissionEdge.t.sol`) |
| Допущенная площадка, логика которой меняется за прокси (SOK-PROXY-VENUE) | повторный допуск после отречения отвергается, если код выполнения изменился (`factoryCodehash`); там, где прокси может изменить ответ, не меняя кода, закрепляется сам ответ | Hub `addFactory` → `HubE(1)` | заблокировано | `test/FactoryCodehashPin.t.sol`, `test/RenouncedFactoryRearm.t.sol` |
| Строка площадки, переведённая из выводимой в запрашиваемую после окостенения (SOK-MODE-FLIP) | строка вывода не может стать строкой запроса после `controlRenounced`; ужесточение в обратную сторону остаётся разрешённым | Hub `addFactory` | заблокировано | `test_C4_S3_DeriveRowMustNotBecomeAnAskRowAfterRenounce` (`test/T19ReadmissionEdge.t.sol`) |
| Публикуемая метрика, отражающая заявление вызывающего (SOK-VOLUME) | реестру передаётся сумма, которую хоп смог потратить, из отношения измеренного к заявленному (`hopScale`) | Router `_recordHits` | заблокировано | `test_INV_F4_VolumeInEqualsTheMeasuredPoolDelta` (`test/VolumeEventFidelity.t.sol`) |
| Предпросмотр, не предсказывающий исполнение (SOK-QUOTE-EXEC) | равенство предпросмотра и доставки утверждается в обоих режимах комиссии (`previewRoute`, `_pack`) | Quoter; Router | заблокировано | `test_INV_F2_PreviewPredictsDeliveryWithNoBridge` (`test/ExhaustionRegimePreviewParity.t.sol`) |
| Детерминированный вывод, направленный на адрес атакующего (SOK-V4-DERIVE) | у прообраза CREATE2 один производитель (`create2Address`); id пула выводится из его собственного ключа; `hasCode` и доказательство жизнеспособности отбрасывают то, что не разрешается | Core; Hub `_probe`, `_scanV4` | заблокировано | `test/ConditionAdequacyCore.t.sol`, `test/KindIsDerivedNotDeclared.t.sol` |
| Вредоносный хук на площадке с хуками (SOK-HOOK) | три слоя, §14.2: неизменяемые биты адреса, список разрешённых с закреплением codehash (`hookAllowed`, `isHookLive`), проектор Ξ | Router → `RouterE(9)`; Hub `allowHook` → `HubE(8)`; Solver `_topKPools` | заблокировано | `test/V4SievedHookIsTheExecutedHook.t.sol`, `test/SeedPoolHookMustBeAllowed.t.sol`, `test/RenouncedHookRearm.t.sol` |
| Исчерпание реестра повторным допуском (SOK-GRIEF-SEATS) | одна строка на адрес; повторное добавление обновляет строку на месте, а не занимает место (`MAX_FACTORIES`) | Hub `addFactory` → `HubE(4)` | заблокировано | `test/RenouncedFactoryRearm.t.sol` |
| Произвольный внешний вызов или сток transfer-from (SOK-ARBITRARY-CALL) | исполнитель никогда не принимает цель вызова от вызывающего; взаимодействие с площадкой диспетчеризуется из закрытого набора форм, выбираемых по типу (`_execScaled`), а неизвестный тип откатывается | Router `_execScaled` → `RouterE(8)` | заблокировано | `test/regime/HostileVenueMatrix.t.sol` |
| Повтор подписи при делегированном одобрении (SOK-SIG-REPLAY) | делегированное одобрение обрабатывает канонический Permit2 (`permitTransferFrom`); собственной схемы подписей у системы нет | Router `swapExactInWithPermit2` | заблокировано | `test/RouterPermit2OneStep.t.sol` |
| Захват управления (SOK-GOV) | управления нет: ни голосования токенами, ни таймлока, ни прокси; единственные привилегированные полномочия — допуск площадок и пауза, и от обоих можно отречься | — | вне рамок | — |
| Вредоносное обновление (SOK-UPGRADE) | контракты неизменяемы; нет ни прокси, ни слота реализации | — | вне рамок | — |
| Компрометация внешнего моста или площадки (SOK-BRIDGE-EXT) | сторонние площадки компонуются, а не контролируются; их сбой здесь ограничен нижней границей и минимумом вызывающего, а об их внутренней безопасности ничего не утверждается | — | вне рамок | — |
| Компрометация приватного ключа пользователя (SOK-KEY) | вне границы доверия; записано, потому что опубликованные данные об инцидентах приписывают этому большую долю потерь | — | вне рамок | — |

Девятнадцать из двадцати трёх классов отражаются именованной защитой; четыре вне рамок, причина записана. Каталог перекрёстно проверяется с другой стороны замыканием активов (`assets.json`): шесть вещей, которые система держит или решает, — средства в пути, делегированное разрешение, комиссия протокола, целостность реестра, застрявшие балансы, газ вызывающего, — скрещены с пятью способами что-либо потерять; тридцать ячеек, каждая сводится к классу угроз, обоснованию «вне рамок» или явному *открыто*. Девятнадцать ячеек покрыты, три вне рамок, пять неприменимы, а три открыты и опубликованы как таковые: грифинг номинала комиссии, оставление пыли, делающее возврат неэкономичным, и ситуация, когда вызывающий получает одну из трёх достижимых паник компилятора вместо именованного отказа. Ни одна из открытых ячеек не ведёт к чужим средствам.

### §14.2 Враждебные хуки: три слоя, начиная с самого дешёвого

Хук — это произвольный код, работающий внутри расчёта, который выполняет Router, и наивная защита — спросить его, что он сделает, — не защита, потому что враждебный хук лжёт или входит повторно. Вместо неё стоят три независимых слоя, упорядоченных так, что самый дешёвый и самый надёжный работает первым; они ограничивают изменение дельт, а единственный остаток, до которого они не дотягиваются, — хук без дельт, переопределяющий свою динамическую комиссию (§5.4), — ограничивают Φ и минимум вызывающего.

**Слой 1 — биты разрешений живут в адресе, а адрес не может лгать.** V4 кодирует разрешения хука в младших 14 битах адреса его собственного контракта; эти биты фиксируются при развёртывании солью CREATE2, и сам PoolManager проверяет те же биты перед вызовом хука, так что у хука не может быть разрешения, которого не объявляет его адрес. Ровно два бита позволяют хуку изменять учёт свопа, и Router маскирует их одной операцией AND до перемещения любого токена (`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

Никакого вызова недоверенного кода, некого обманывать, а для обхода понадобился бы адрес, биты которого противоречат собственному принуждению менеджера. Прокси может заменить каждую строку своей логики и всё равно не получит бита, которого у его адреса никогда не было, — именно поэтому этот отказ находится на расстоянии проекции ноль (§15.9), а закрепление codehash под ним — нет.

**Слой 2 — список разрешённых, закрытый по умолчанию, с закреплённым кодом.** Хук с чистыми битами всё ещё может злонамеренно откатываться, чтобы вредить или сжигать газ, поэтому пул V4 пригоден для маршрутизации, только если его хук внесён куратором в список разрешённых, а Hub закрепляет codehash хука при допуске: хук, код выполнения которого позже меняется, автоматически ставится на паузу без исключения (`isHookLive` false) и возобновляется, только если допущен повторно, — а после отречения это полномочие отвергается для любого хука, чей код изменился. Предел закрепления назван на месте: оно привязывает код выполнения, поэтому ловит повторное развёртывание или прямую мутацию и не ловит делегирующий прокси, реализация которого подменена, пока его собственный код выполнения остаётся побайтно идентичным, — потому что EVM не даёт контракту способа прочитать хранилище другого. Допуск обновляемого хука — человеческое суждение, которое это закрепление заменить не может. Слои, не зависящие от него, стоят в любом случае.

**Слой 3 — маршрут даже не показывается.** Проектор Ξ удаляет любой пул, хук которого изменяет дельты, из набора кандидатов Solver до ранжирования (`_topKPools`), а канал чтения Hub отдаёт только живые хуки; маршрут, затрагивающий недопустимый хук, не участвует в максимизации. Дополняет это правило формы маршрута: канонический порядок по всему маршруту — ни одна нога без хука никогда не исполняется после того, как сработал хук (§11.4).

![FIG-11 — Три слоя против враждебных хуков, начиная с самого дешёвого: битовая маска адреса (без вызова недоверенного кода), список разрешённых с закреплением codehash (закрыт по умолчанию), проектор Ξ (маршрут непредставим). Каждый слой отвечает на свою возможность; остаток — чистый хук из списка, который откатывается, чтобы тратить газ, — назван.](https://blazephoenix.xyz/whitepaper/figs/ru/fig-11.png)

Чтобы победить этот стек, нужен хук, который не несёт битов дельт, стоит в списке разрешённых без изменений и всё же подрывает расчёт, — комбинация, которую исключает для изменения дельт опора Слоя 1 на собственное неизменяемое принуждение V4; остаток переопределения комиссии из §5.4 сохраняется, и его ограничивает нижняя граница. Остаток указан в таблице: хук из списка разрешённых без дельт всё ещё может откатиться посреди свопа и впустую потратить газ попытки. И единственный рычаг сита назван: отсеянный хук привязан к исполняемому свопу, потому что канонический PoolManager дословно возвращает данные разблокировки и вызывает обратно только того, кто вызвал разблокировку; `setV4Manager` в Hub, полномочие контроля, — тот единственный адрес, на котором держится это допущение, а нечестный менеджер обрушивает измеренный выход, так что собственная граница вызывающего отвергает своп (кампания `HostileV4Manager`), — ограничено, но не отсутствует.

### §14.3 Ограничено, а не устранено: устаревание, порядок и кривая сэндвича

Котировка — это утверждение о прошлом, а сделка происходит в будущем. Дверь решения внутри фрейма полностью закрывает операторскую половину этого разрыва: маршрут выводится внутри транзакции, которая его исполняет, из резервов в том виде, в каком они стоят в этом блоке. Остаётся собственная половина сети: пул может сдвинуться между моментом вашей подписи и моментом включения вашей транзакции. Никто не может предотвратить это изнутри контракта, потому что порядок определяется до запуска кода; протокол, заявляющий, что устранил фронтраннинг, либо увёл вашу сделку из публичной очереди, доверившись тому, кто теперь её держит, либо описывает нечто более узкое, чем звучит. Этот протокол её ограничивает, дважды и независимо: ваш минимум ограничивает ущерб выбранным вами числом, а нижняя граница Iron-Law — заново выведенная Router из измеренного влияния и котировки последнего хопа внутри фрейма, а не из чего-либо, что предоставили атакующий или маршрут, — ограничивает его ещё раз за счёт самого протокола.

Граница измерена со стороны атакующего (`test/regime/SandwichCurve.t.sol`): маршрут и нижняя граница жертвы фиксируются в момент котировки, как у ожидающей транзакции; атакующий торгует долей глубины пула перед жертвой, жертва исполняется, атакующий торгует обратно. Площадка — пара с постоянным произведением, форма, которую использует любая модель сэндвича, так что кривая — свойство нижней границы. На сделке в 1 % глубины (10,000 против 1,000,000 с каждой стороны):

| Атакующий сдвигает | Жертва | Потеря жертвы относительно котировки | Круговая сделка атакующего |
|---|---|---|---|
| 0.1 % глубины | исполняется | 0.47 % | +13.9 |
| 0.5 % | исполняется | 1.26 % | +68.9 |
| 1 % | исполняется | 2.22 % | +136.7 |
| 2 % | исполняется | 4.12 % | +268.7 |
| 3 % и более | **отказ** | 0 | −174.5 … −544.9 |

![FIG-12 — Кривая сэндвича со стороны атакующего: потеря жертвы относительно котировки и круговая сделка атакующего в зависимости от доли глубины, сдвинутой перед жертвой; граница отказа около 3 %, замкнутая вверх.](https://blazephoenix.xyz/whitepaper/figs/ru/fig-12.png)

Гарантия, утверждаемая в каждой точке: исполненная жертва никогда не получает меньше нижней границы, удостоверенной в момент котировки, поэтому потеря ограничена расстоянием между удостоверенной котировкой и удостоверенной границей; а область отказа замкнута вверх — за границей отвергается любая более крупная манипуляция, и атакующий остаётся с ценой, которую сам сдвинул. Число, которое стоит цитировать, — последняя исполненная строка: нижняя граница ограничивает то, что сэндвич может забрать у сделки в 1 %, примерно 2.7 % от неё и превращает сделку атакующего в убыток, как только он попытается взять больше. Маршрут внутри фрейма добавляет более тихий сдерживающий фактор: нет заранее опубликованного пути, который мог бы изучить сёрчер, потому что путь не существует до блока, который его исполняет. Широкий минимум — приглашение; узкий — самый действенный контроль в этом разделе, который принадлежит вам.

### §14.4 Враждебные площадки

У патологий площадок своя матрица (§15.5): десять видов неправильного поведения — пул, который ничего не платит, платит половину, возвращает 64 KiB returndata-бомбу, сжигает весь газ при чтении или при свопе, токен, чей `decimals()` никогда не возвращается, колбэк оплаты, вызванный дважды, пул, повторно входящий в Router до оплаты, откатывающийся `slot0()`, фабрика, отвечающая пулом на других токенах, — скрещены с дверью calldata и дверью решения: 20 из 20 ячеек либо исполняются с доставленной суммой, равной дельте баланса, и ничем не оставленным на Router, либо отказывают с нашим селектором. Своп, сжигающий весь переданный газ, — единственная ячейка, которую ни один вызывающий не может решить за вызываемого; транзакция откатывается целиком, и баланс пользователя не затронут. Каждое защищённое чтение, которое Core делает у площадки, ограничено `GAS_CAP = 100,000` газа, поэтому площадка, сжигающая газ при *чтении*, стоит ограниченную сумму и отбрасывается, а не становится фатальной.

### §14.5 Враждебный фронтенд

В каждом когда-либо построенном агрегаторе минимум предлагает интерфейс, и небрежный интерфейс предлагает его плохо — ни один контракт не может знать, что выбрали бы вы. Контракт может сделать предложение доказуемым: каждый своп испускает `ExecutionProof` — использованную котировку, доставленную сумму, применённую нижнюю границу — навсегда, ончейн, так что качество исполнения любого интерфейса становится публичной записью по каждой транзакции, которую может проверить кто угодно, включая вас. Интерфейс, систематически предлагающий плохие минимумы, сам пишет себе обвинение в журнал событий. Защита, завершающая механизм, — это привычка: посмотрите на минимум перед подписью; это единственное поле, которое целиком ваше.

### §14.6 Что враждебный ключ может и чего не может

До отречения административные ключи существуют, и честный способ их описать — через возможности, предполагая враждебного держателя; полный перечень полномочий — в §16.4.

**Что может враждебный ключ Router:** перенаправить получателя комиссии (`setTreasuries`); поставить свопы на паузу (`setPaused`); перенаправить контракты WETH и Permit2, которые вызывают нативная дверь и дверь Permit2 (`setWeth`, `setPermit2`), из-за чего эти две двери заслуживают доверия лишь настолько, насколько заданные адреса; поставить в очередь и через 48 часов исполнить спасение токенов, застрявших в Router (`queueRescue`, `executeRescue`, `RESCUE_DELAY = 48 hours`, объявляется ончейн событием `RescueQueued`). **Чего он не может:** изменить ставку комиссии, распределение или нижние границы, потому что это константы времени компиляции без сеттеров; забрать основные средства, потому что нет ни прокси, ни пути обновления, ни `selfdestruct`, ни постоянного одобрения средств пользователя для Router, а Router ничего не держит в покое, — функции, которые сделали бы это, не отключены, их просто нет.

**Что может враждебный ключ Hub:** внести в список площадку или хук (`addFactory`, `allowHook`), засеять пулы (`seedPool`, через место оператора), поставить на паузу обучение реестра (`setPaused`), перенаправить представление Hub о Router, Solver и Quoter (`setRoles`) и перенаправить менеджер V4 (`setV4Manager`, единственный рычаг сита). **Чего он не может:** неверно оценить исполнение, потому что реестр решает только, что будет увидено (§9.6), — самое острое, до чего дотягивается враждебное внесение в список, — это разрыв между котировкой и *вашим минимумом*; основные средства недосягаемы, ваш допуск проскальзывания — нет, и это ещё одна причина устанавливать минимум самостоятельно и жёстко.

### §14.7 Дверь в одну сторону

Административная власть заканчивается необратимо, в байткоде, а не обещанием. И Hub, и Router предоставляют `renounceControl`, и каждый отказывается от вызова во время паузы (`RouterE(2)`, `HubE(2)`): протокол, поставленный на паузу, а затем отрёкшийся, оказался бы в терминальном состоянии, которого никто не хочет и из которого никто не может выйти, а пауза имеет смысл именно потому, что контроль сохраняется для действий. Флаг отречения записывается в `true` ровно в одном месте каждого контракта и ни в одном месте не записывается в `false`, поэтому ни одна функция в развёрнутом байткоде не может снова открыть дверь — разница между «мы не станем» и «мы не можем».

Что закрывается и что остаётся открытым, перечислено, а не обобщено. В Router умирает всё, что под `onlyControl`: `setAdmin`, `setTreasuries`, `setPermit2`, `setWeth`, `setPaused`, путь спасения — казначейства, связи и флаг паузы навсегда замораживаются в текущих значениях, и Router продолжает исполнять свопы в этой фиксированной конфигурации. В Hub умирает **уровень контроля**: `setRoles`, `setOperator`, `setPaused`, `setV4Manager`, `removeBridge` и *исключение* хуков из списка. **Уровень куратора** выживает и только расширяет реестр: `addFactory` (после отречения отвергает любую фабрику, чей код выполнения изменился, и любую строку вывода, которая стала бы строкой запроса), `addBridge` (только добавление, не более трёх, идемпотентно), `allowHook(h, true)` (отвергает хук из списка, чей код изменился); существующие места операторов сохраняют `seedPool` и `addV4`, и новые места выдать нельзя. Вредоносное внесение в список после отречения не может опустошить средства: пулы проверяются при котировке и при исполнении и ограничены нижней границей и минимумом вызывающего. Реестр продолжает учиться через торговлю. Любой, кто повторяет «отречено» об этом протоколе, должен уметь сказать, какие двери остаются открытыми — допуск, мосты, внесение хуков в список, кураторский посев, — и какие закрываются: каждое полномочие, которое перенаправляет или замораживает.

### §14.8 Чего эта модель требует от вас

Протокол закрывает то, что может закрыть контракт: подделку адресов, подмену колбэков, подделку учёта, враждебные хуки, арифметику. Он ограничивает то, что ни один контракт не может устранить: устаревание и порядок — вашим минимумом и нижней границей. И он публикует свидетельства против того, что не может даже ограничить: фронтенда, предлагающего плохие минимумы от вашего имени. Каждая строка сходится к одному и тому же полю. Задавайте минимум сами, обдуманно, каждый раз — это единственная защита в этом разделе, качество которой не зависит ни от чьей компетентности, кроме вашей.

## §15 Аппарат верификации в измерениях

*Просто:* каждое число здесь напечатано рядом со знаменателем, относительно которого оно измерено, заново вычисляется из чистого checkout скриптом или названной командой `forge` (Приложение C), и за ним следует то, чего оно не устанавливает. *Точно:* измеренное дерево — `main` на `8949a9d` и ветка, ставшая им, на релизном профиле (`optimizer_runs = 300`, `via_ir`), 2026-09-05.

### §15.1 Набор тестов в числах

| Что | Количество | Как считается |
|---|---|---|
| объявленные тесты | 1,609 функций `test*` / `invariant*` / `check*` в 236 файлах `.t.sol` | `grep` по `test/` |
| зелёные на релизном профиле, без форк-наборов | 1,319 пройдено · 0 провалено · 1 пропущен, из 1,320 | `forge test --no-match-path 'test/fork/**'` |
| зелёные на живой ликвидности | 119 из 119 в 39 наборах форк-линии, пять сетей; плюс три теста на живом Base из §12 | `forge test --match-path 'test/fork/**'` с архивным RPC |
| инварианты с состоянием | 43 функции `invariant_*` в 14 кампаниях | `grep -rc 'function invariant_' test` |
| отобранные мутанты, все убиты | 268 из 268 | `.github/scripts/mutants.py`; `check_targets.py` |
| покрывающие массивы режимов | сила 2: 63 строки, все 258 пар · сила 3: 168 строк, все 1,636 троек · из 5,184 комбинаций | `covering_array.py --check` |
| размеры поставляемого кода (байты runtime; лимит EIP-170 24,576; порог проекта 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 Цепочка доказательств и страж отобранных мутаций

Свойство стоит ровно столько, сколько кратчайший путь от угрозы до чего-то, что упало бы при нарушении свойства: `threat → property → guard → test → mutant`. Страж называется по символу, тест — по имени, мутант — по точной строке, которую он меняет; если любой из трёх покидает дерево, сборка падает. Каждый регрессионный тест набора хотя бы раз видели падающим — по той причине, в честь которой он назван: исправление приходит вместе с тестом, который был красным на коде без него.

Страж мутаций содержит 268 написанных вручную мутантов, каждый из которых связывает одну точную строку исходника с единственным тестом, обязанным упасть, как только эта строка изменена: удалённая проверка, сравнение, перевёрнутое на границе, решающей отказ, расширенная авторизация, код ошибки, переставленный с соседним. Три свойства делают его стражем, а не счётом: связанный тест сначала прогоняется зелёным на немутированном дереве; мутация, которую удаляет оптимизатор, — идентичный runtime-байткод — сообщается как *инертная*, никогда как убитая; а односекундная статическая проверка подтверждает, что каждый мутант по-прежнему указывает ровно на одну строку. 203 из 203 убиты. Это число — адекватность относительно *этого* реестра: насыщенный счёт — нижняя граница, а не потолок.

### §15.3 Мутанты, нацеленные на инварианты

До 2026-09-05 ни один из отобранных мутантов не называл инвариант с состоянием тестом, который должен от него погибнуть. 43 функции `invariant_*` в 14 кампаниях были зелёными, и никто не спрашивал, может ли какая-нибудь из них покраснеть: кампания, чей обработчик никогда не достигает состояния, которое защищает свойство, удостоверяет это свойство над пустой вселенной и снаружи выглядит так же, как кампания, которая его достигает. Измерение — это страж, направленный на кампании: изменить одну проверку в исходнике, прогнать все инварианты на одном seed, записать, какие из них это заметили (`docs/assurance/invariant-mutants.json`).

| Мутант | Что он убирает | Кто заметил |
|---|---|---|
| финальный перевод платит на один wei меньше | ничего не хранить | `RouterHoldsNothing`, `routerHoldsNothing`, `HoldsNothingBeyondTheSeed` |
| комиссия протокола удваивается | потолок комиссии | `FeeNeverExceedsProtocolMax` |
| комиссия на стороне входа никогда не взимается | нижнюю границу комиссии | `FeeNeverEscapes`, `DeliveredNeverBelowUserMinOut` |
| комиссия взимается с обеих сторон | одна комиссия, одна сторона | `FeeIsChargedOnExactlyOneSide` |
| 30 % каждой комиссии уходит на мёртвый адрес | сохранение | `conservationA/B/C/H`, `TokenConservation`, `LedgerConservationA` |
| проверка пары ног допускает ногу на чужой паре | однородные хопы | `DivergentLegNeverSettles`, `HoldsNothingBeyondTheSeed`, `StrandedMoneyIsNeverSwept` |
| `whenLive` больше не проверяет `paused` | паузу | `PausedRouterNeverSettles` |
| блокировка реентерабельности больше не отказывает | блокировку | `reentrancyBlocked` |
| нижняя граница протокола уменьшена вдвое | нижнюю границу | `DeliveredNeverBelowTheProtocolFloor` — закрыто 2026-09-05 |
| `MAX_SLOTS` становится 17 | границу реестра | `neverExceedsMaxSlots` |
| доказательство пары при регистрации удалено | подлинность пары | `ActiveEntriesTradeThePair` — закрыто 2026-09-05 |
| выметание остатка входа игнорирует свою базовую линию | застрявшие деньги | `StrandedMoneyIsNeverSwept`, `HoldsNothingBeyondTheSeed` |
| выметание остатка мостовой монеты игнорирует свою базовую линию | застрявшие деньги | та же пара — закрыто 2026-09-05 |
| измерение выхода игнорирует свою базовую линию | застрявшие деньги | та же пара |
| проверка `userMinOut` после комиссии уменьшена вдвое | — | выживает, намеренно: регрессионный часовой, недостижимый во вселенных кампаний без выходного токена с комиссией при переводе (fee-on-transfer); за ним следят два модульных мутанта |

![FIG-13 — Мутанты, нацеленные на инварианты: пятнадцать мутаций исходника против сорока трёх инвариантов с состоянием, инварианты, покрасневшие для каждого мутанта, три, закрытые 2026-09-05 добавлением в кампанию недостающего действия, и один часовой, выживающий намеренно.](https://blazephoenix.xyz/whitepaper/figs/ru/fig-13.png)

Одиннадцать из пятнадцати были замечены при первом измерении; двадцать различных имён инвариантов покраснели хотя бы раз. Три из четырёх выживших были закрыты в тот же день добавлением в кампанию недостающего действия — чужие пулы, предложенные под пару, двуххоповый маршрут поверх заранее засеянного промежуточного баланса, кит, сдвигающий пул на 0–8 % против котировки маршрута до исполнения устаревшего маршрута, — и каждый проверен красным без своего стража; две проверки вообще без наблюдателя — именно это число измерение и существовало, чтобы напечатать, и теперь оно равно нулю. Четырнадцать из пятнадцати замечены; эти четырнадцать — записи стража, связанные с инвариантом, который от них погибает. Зелёный набор с ненаблюдаемой проверкой внутри — такова была форма обеих задокументированных регрессий в этой кодовой базе; измерение, которое её находит, теперь выполняется для каждой проверки, на которую претендуют кампании.

### §15.4 Покрывающие массивы режимов

Критерии покрытия индексируют код; мутация индексирует внесённый дефект; ни то, ни другое не индексирует состояние, которое *фикстура* фиксирует до вызова. Перечислено десять таких факторов — семейство площадки (V2, V3, Solidly), хопы (1–3), ноги на хоп (1–2), является ли входной токен зарегистрированным мостом, является ли им промежуточный, форма комиссии при переводе (нет, только при pull, при каждом переводе), десятичные знаки входного токена (18, 6), дверь (маршрут в calldata, решение в транзакции, Permit2), контроль (активен, отказан) и заполнена ли пара — 5,184 комбинации. `covering_array.py` генерирует массивы, в которых каждая пара (сила 2) или каждая тройка (сила 3) значений факторов встречается хотя бы в одной строке; каждая строка — одна фикстура через один харнесс (`test/regime/RegimeHarness.sol`) с одним утверждением: своп рассчитывается, причём доставленная сумма равна изменению баланса получателя, не меньше нижней границы, выпущенной Router, и на Router ничего не остаётся, — или отклоняется с нашим селектором. Паника, чужой селектор, недоставка или застрявший баланс — это третий путь, и строка проваливается.

| Сила | Строки | Удержанные кортежи | Рассчитано | Отказ, наш | Не конструируемо | Третий путь |
|---|---|---|---|---|---|---|
| 2 | 63 | 258 пар | 53 | 4 | 6 | 0 |
| 3 | 168 | 1,636 троек | 158 | 10 — `SolverE(5)` ×7, `RouterE(13)` ×3 | 0 | 0 |

![FIG-14 — Покрывающие массивы режимов: исходы каждой сгенерированной строки при силе 2 (63 строки) и силе 3 (168 строк) — рассчитано, отказ с нашим селектором, не конструируемо, третий путь — над 5,184 комбинациями десяти факторов.](https://blazephoenix.xyz/whitepaper/figs/ru/fig-14.png)

Отказы — это планировщик, которому не из чего построить путь через мост, и исполнитель, отклоняющий облагаемый токен на маршруте только из концентрированных пулов; шесть строк при силе 2, которые нельзя построить (у токена, облагаемого только при pull, нет шестизначной формы), напечатаны по имени и учитываются в знаменателе. Семейства вне массива — V4, нативный V4, Algebra, нативная дверь, хуки — указаны в `regimes-covering.json`. Первый прогон массива сделал явной рамку, которую тесты паритета закрепляли только с одной стороны: нижняя граница, которую обеспечивает Router, равна аттестованной нижней границе, когда комиссия снимается с выхода, и лежит внутри $[\mathrm{attested} \times (1 - \mathrm{fee}), \mathrm{attested}]$, когда она снимается со входа, — обе рамки теперь утверждаются в каждой строке. Сгенерированный файл сверяется со своим генератором в CI, поэтому массив не может уйти от факторов, которые он заявляет покрывать.

### §15.5 Матрица враждебных площадок

У патологий токенов были свои тесты; у патологий площадок не было ни одного в матрице. `test/regime/HostileVenues.sol` содержит по одному нарушению на площадку, а `HostileVenueMatrix.t.sol` скрещивает каждое с дверью calldata и решающей дверью по правилу покрывающего массива:

| Площадка | Дверь calldata | Решающая дверь |
|---|---|---|
| не платит ничего / платит половину | отказ `RouterE(5)` | отказ `RouterE(5)` |
| бомба returndata при чтении резервов | рассчитывается | рассчитывается |
| чтение резервов сжигает весь газ | отказ `RouterE(8)` | планировщик никогда её не выбирает |
| своп сжигает весь газ | вся транзакция откатывается, баланс не тронут | вся транзакция откатывается, баланс не тронут |
| `decimals()` сжигает весь газ | рассчитывается | рассчитывается |
| колбэк оплаты вызван дважды | отказ `RouterE(6)` | отказ `RouterE(6)` |
| повторно входит в Router до оплаты | рассчитывается; вложенный своп так и не выполнился | рассчитывается; вложенный своп так и не выполнился |
| `slot0()` откатывается | рассчитывается по аттестованной котировке | планировщик никогда её не выбирает |
| фабрика отвечает пулом на других токенах | никогда не попадает в список | никогда не попадает в список |

Двадцать из двадцати ячеек: расчёт или отказ с нашим селектором, никогда третий путь. Последняя строка — то, что изменил первый прогон матрицы: обнаружение вносило в список пул, которым ответила допущенная куратором фабрика, а исполнитель отклонял его на шве, который платит; теперь запрошенный пул доказывает собственную пару, прежде чем обнаружение внесёт его в список (§7).

### §15.6 Канонические оракулы

Каждый мок в наборе котирует по собственным формулам Core, поэтому дефект в формуле невидим для каждого теста паритета, который их использует: оракул и есть объект. `test/regime/CanonicalOracles.t.sol` содержит три реализации, написанные по опубликованным инвариантам площадок, — постоянное произведение с комиссией на входе, шаг V3 по корню из цены в пределах одного тика с собственным округлением пула против трейдера, стабильная кривая Solidly, решаемая методом Ньютона, — и фаззит Core против них, по 5,000 прогонов на каждую, утверждая сначала направление (Core никогда не обещает больше, чем даёт математика площадки), а затем точность.

| Семейство | Направление | Измеренная точность |
|---|---|---|
| Uniswap V2 | Core никогда не превышает спецификацию | точно до wei |
| Solidly stable | Core никогда не превышает кривую больше, чем на последний шаг самого решателя | в пределах 4 wei |
| Uniswap V3 | Core никогда не превышает спецификацию больше, чем на одну единицу последнего разряда (ulp) корня из цены, равную $L / 2^{96}$ wei | меньше одного wei для каждого пула с $L < 2^{96}$ |

Граница V3 выражена в той величине, которая её вызывает: пул округляет свою новую цену против трейдера, а Core округляет её один раз, поэтому они могут расходиться на одну единицу $\sqrt{P}$, — и утверждается граница, а не выборка (13 wei на выходе $6.5 \times 10^{34}$ при $L = 10^{30}$). Оракулы написаны по спецификациям, а не по байткоду площадок; в этом их предел.

### §15.7 Метаморфные отношения

Оракул, написанный по той же формуле, не может поймать формулу. Метаморфное отношение вместо этого спрашивает, как выход должен *сдвинуться*, когда сдвигается вход, и собственная кривая площадки отвечает на это без эталонной реализации. `test/CoreMetamorphicRelations.t.sol` содержит четырнадцать отношений над математикой котировок Core, а `test/RouteMetamorphicRelations.t.sol` — пять над планом Solver во вселенной из двух пулов с настоящим Hub и настоящим Solver:

| Уровень | Отношение | Что оно утверждает |
|---|---|---|
| Core, V2 · V3 · stable | MR1 монотонность | больше на входе — никогда не меньше на выходе |
| Core, V2 · V3 · stable | MR2 субаддитивность | разбиение ордера по одному и тому же пулу никогда не выигрывает |
| Core, V2 · stable | MR3 нет кругового выигрыша | туда, затем обратно по обновлённым резервам — никогда не возвращает больше, чем вошло |
| Core, V2 · V3 · stable | MR4 эквивариантность масштаба | совместное масштабирование ордера и пула масштабирует выход с точностью до округления |
| Core, V2 · V3 | MR5 монотонность по комиссии | более высокая комиссия никогда не платит больше |
| Core, V3 | MR6 симметрия направления | при цене 1.0 две ветви `outV3` совпадают с точностью до двух ulp |
| Core, Solidly | MR7 тождество | волатильная ветвь *и есть* `outV2`, один производитель |
| план | MR-R1 | разбиение никогда не платит меньше, чем лучший одиночный пул |
| план | MR-R2 | монотонен по `amountIn` |
| план | MR-R3 | добавление пула внутри ценовой полосы никогда не ухудшает план |
| план | MR-R4 | порядок регистрации сдвигает план не более чем на одну единицу веса разбиения |
| план | MR-R5 | аттестованная нижняя граница никогда не превышает ожидаемый выход |

Три границы были измерены до того, как их записали, и число в утверждении — это механизм, а не выборка. MR6: при $L = 8.5 \times 10^{37}$ и 1,374 wei на входе одна ветвь котировала 1,073,741,823 wei — ровно $L / 2^{96}$, один ulp, — а другая котировала ноль: её шаг округлился в ничто, отказ в закрытую сторону; отношение задаёт два ulp, по одному на ветвь. MR-R1 и MR-R3: когда один пул оценён в $10^{10}$ раз дальше от другого, план платил на 35 % меньше, чем один лишь выброс, — медиана, взвешенная по глубине, отказывалась ему верить, — поэтому отношения выполняются над пулами, которые допускает полоса, а домен держит две цены в пределах ±4 %. MR-R4: пул, чей вес по глубине округляется вниз до минимума, сохраняется как пылевая нога при регистрации первым и отсекается при регистрации вторым, сдвигая план на $3 \times 10^{-7}$ выхода, — решение разбиения сохранить или отсечь пул минимального веса зависит от его позиции, и теперь это записано там, где обнаружено.

### §15.8 N-версионность: тот же исходник при другой кодогенерации

Набор исполняет один бинарник — тот, который выдают релизные настройки, и `profile_parity.py` удерживает набор на этих настройках. Любая другая настройка оптимизатора — это другая программа, скомпилированная из того же исходника, и компилятор — это компонент. `test/nversion/` компилирует математику котировок Core ещё в двух профилях — `optimizer_runs` 1 и 20,000, всё остальное идентично, — а `NVersionCore.t.sol` развёртывает три бинарника рядом и утверждает равенство на фаззинговых входах: `outV2`, `outV3`, `outSolidly`, `outSolidlyStable`, `mulDiv`, `mulDivUp`, по 3,000 прогонов каждая по всему домену входов. Линия сначала утверждает, что альтернативный байткод *отличается* от поставляемого, поэтому она не может пройти, сравнивая программу саму с собой, а отсутствующий артефакт — это провал, а не пропуск.

**Три бинарника согласны.** Находкой оказался не компилятор. Первый прогон линии по полному домену заставил откатиться *эталон*: проверка соответствия стабильной кривой делила `max · WAD / b`, и само это деление переполнялось на пуле, держащем меньше одной единицы токена; второй выход за пределы — китовый резерв входа против пылевого резерва выхода — проходил проверку инварианта и переполнял производную. Ни то, ни другое не было неверным числом; оба были откатами внутри котировки, а `universalQuote` — это библиотечный `DELEGATECALL`, поэтому зарегистрированный пул, держащий один wei выходного токена, разворачивал план Solver для своей пары. Исправление (§6) задаёт вопрос, который подменяло собой деление, и вошло через красный тест; ещё одна проверка — ограничение итерации Ньютона — была написана, признана ненаблюдаемой ни одним тестом при выполнении двух проверок и удалена, а не оставлена. Её класс — тот, что этот репозиторий называет первым, — открытый отказ (fail-open), найденный инструментом, который его не искал.

### §15.9 Проекционное расстояние

Каждый дефект, подтверждённый в этом репозитории, внутренний или сообщённый извне, имеет одну форму: проверка наблюдает один объект, а поведение решает *другой* объект. Поэтому интересное свойство отказа — не его сложность, а его расстояние: насколько далеко то, что он читает, отстоит от того, что решает. `projection_distance.py` классифицирует предикат, охраняющий каждый `revert` в пяти контрактах: константа, immutable, дельта баланса или величина, вычисленная во фрейме из одного из них, — расстояние 0; поле маршрута вызывающего или поиск в изменяемом хранилище другого контракта — расстояние 1; свойство контракта, который может делегировать своё поведение, — неограниченно. По 99 местам отказа: **86 на расстоянии 0, 11 на расстоянии 1, 2 неограниченных.** Оба неограниченных места — пины `EXTCODEHASH`: кода допущенного хука и кода допущенной фабрики, — и они не симметричны: для хуков остаток закрывается битами адреса, которые пин не несёт (§14.2); для фабрик эквивалента нет, и остаток ограничен допуском пулов, нижней границей на ногу и минимумом пользователя, поэтому ущерб — деградация маршрута, а не вывод средств, что задокументировано на месте. Предел этого фильтра: он читает один предикат и не видит, что значение было ограничено тремя фреймами выше; объявленное вызывающим поле, которое одновременно является селектором пула, находится на расстоянии 0 только потому, что их связывает одно присваивание, и это присваивание закреплено `test/V4SievedHookIsTheExecutedHook.t.sol`.

### §15.10 Независимость доказательств, реестр общих величин и артефакт

Для каждой несущей проверки аппарат записывает, сколько существует *независимых* подтверждений — по источнику, оракулу, инструменту, среде и методологии, — и сообщает минимум, потому что цепочка доказательств рвётся на самом слабом звене; несущие свойства подтверждаются как минимум двумя путями, не разделяющими ни одной гипотезы. Реестр общих величин (`SHARED_QUANTITIES.md`) перечисляет каждую величину с более чем одним производителем или потребителем, формулирует *вопрос*, на который она отвечает, и оценивает, что связывает копии, — `SINGLE`, `PINNED`, `WEAK`, `OPEN`, `UNVERIFIED` — с правилом CI, понижающим строку, чей тест не достигает того, что он заявляет закреплять; каждая величина с двумя производителями в реляционном реестре (7 из 7) связана названным тестом. И читается скомпилированный артефакт, а не только исходник: опкоды, которые никогда не должны появляться (`SELFDESTRUCT`, `CALLCODE`, `ORIGIN`); транзиентные опкоды, по-прежнему присутствующие в исполнителе, чтобы блокировка реентерабельности не стала тихо постоянным хранилищем; каждая объявленная функция, присутствующая в диспетчере; три из пяти контрактов без единой записи в хранилище; 88.3 % потока инструкций в поставляемой форме, исполнение которых доказано корректной нижней оценкой, проверенной на эталонном контракте, который инструмент обязан правильно классифицировать, прежде чем что-либо напечатать; и для одного записанного честного свопа V2, воспроизведённого на релизном Router с проверкой опкода на каждом шаге, выполнено 4,382 инструкции Router и 407 инструкций Hub, ноль расхождений — 28.2 % и 2.5 % двух секций кода, причём остаток — это код, который не исполнил ни один записанный сценарий, а не мёртвый код.

### §15.11 Чего всё это не устанавливает

- **Никакой вероятности корректности.** Литература о валидации сверхвысокой надёжности прямо говорит, что тестирование не может её дать; здесь она не заявляется.
- **Адекватность мутаций — это адекватность относительно этого реестра.** Он отобран вручную; насыщенный счёт — нижняя граница.
- **Покрытие угроз и режимов — нижняя граница того, что было рассмотрено.** Классы и факторы, которые никто не назвал, по построению находятся вне знаменателя.
- **Распределения §12 получены на моках.** Живой Base даёт охват, а не распределение.
- **Частоты обнаружения указаны на тест при 256 фазз-прогонах на seed.** Тест, промахивающийся при таком бюджете, может поймать при большем.
- **Этот репозиторий — V2, и V2 не развёрнут** (§19).

Каждый инструмент выше был откалиброван до того, как было напечатано его число: сначала прогнан против дефекта, уже известного другим путём, и исправлен там, где он его пропустил, — текстовый сопоставитель, принимавший документацию за поведение, потому что сопоставлял текст внутри комментариев; регулярное выражение покрытия, не учитывавшее блок call-option; поиск соответствий, чей список шаблонов был пуст. Инструмент ничего не стоит, пока не найдёт уже известный случай; вердикт без поиска — пустота; а всё, что решает компилятор, измеряется, никогда не оценивается.

Нам не известна другая публичная кодовая база DeFi, которая публикует отобранный, именованный страж мутаций с убивающими тестами для каждого мутанта и отчётностью об инертных мутациях; реестр общих величин с правилом CI, которое его понижает; мутанты, нацеленные на её инварианты с состоянием, с опубликованным выжившим; покрывающий массив силы 3 над её факторами режима под одним утверждением; N-версионное тестирование её математики котировок; измеренную кривую старения её котировок; или частоту обнаружения её собственных мутантов комиссии с доверительным интервалом. Артефакты лежат в репозитории; если вы найдёте предшествующий пример, утверждение неверно, и мы хотели бы об этом знать.

## §16 Архитектура и размеры

*Просто:* пять контрактов, один из которых — чистая математика, импортируемая остальными четырьмя, и ни один фрагмент математики не определён дважды. *Точно:* Core — библиотека без состояния, без хранилища, без владельца и без пути обновления; Hub, Solver, Router и Quoter импортируют её, и ни один не переопределяет примитив, поэтому один аудит математики покрывает всех потребителей.

### §16.1 Пять контрактов: что каждый хранит и что каждый может вызывать

| Контракт | Хранит | Может вызывать | Пишет постоянное состояние | Размер (байты runtime) |
|---|---|---|---|---|
| **Core** (библиотека) | ничего — функции pure и view: Ω, 𝒟, Φ, Ψ, кодек Моно-слота (Monoslot), безопасные переводы, `mulDiv` | пулы и токены, только чтение, каждый охраняемый `staticcall` ограничен `GAS_CAP = 100,000` | никогда — ни `tstore`, ни `sstore` | 6,442 |
| **Hub** | реестр (один Моно-слот на пул, ≤ 16 на пару), строки фабрик (≤ 16), мосты (≤ 3), список разрешённых хуков с пинами codehash, записи V4, роли; пространство имён ERC-7201 | пулы и фабрики, только чтение, через Core; V4 PoolManager через `extsload` | в `recordSwap` (только Router), при допуске и конфигурации | 23,648 |
| **Solver** | ничего — только `view` | Hub (чтение реестра, обнаружение), пулы через Core | никогда | 19,686 |
| **Router** | админ, казначейства, `permit2`, `weth`, `paused`, `controlRenounced`, очередь спасения; **никакого баланса токенов в покое** | входной токен, пулы маршрута, выходной токен, Hub (`isBridgeToken`, `isHookLive`, `recordSwap`), Solver (только `swapBestExactIn`, `view`), WETH и PoolManager для V4, Permit2 для своей двери, казначейства, получатель — никогда цель, переданная вызывающим | собственная конфигурация; единственная внешняя запись за своп — `Hub.recordSwap` на успешном пути | 23,781 |
| **Quoter** | ничего | Solver (`view`), Hub (`isBridgeToken`, только чтение), пулы для пробного прогона Exact Pass (вызов с откатом) | никогда | 11,429 |

![FIG-15 — Топология пяти контрактов: Core (чистая математика: Ω · 𝒟 · Φ · Ψ), импортируемая Hub (реестр, состояние Ψ, мосты, пины хуков), Solver (оптимизатор маршрута, view), Router (атомарное исполнение, единственный контракт, двигающий средства) и Quoter (предпросмотр, view и пробный прогон). Стрелки — вызовы; единственная сплошная стрелка в хранилище за своп — Router → Hub.recordSwap; все остальные стрелки от Solver и Quoter — только view.](https://blazephoenix.xyz/whitepaper/figs/ru/fig-15.png)

Доверие идёт в одну сторону. Hub доверяет Router вызывать `recordSwap` (`onlyRouter`) и больше никому; Router доверяет Hub три факта, нужные маршруту, — является ли этот токен мостом, жив ли этот хук, и запись регистрации — и ничего, что оценивает сделку. Solver доверяет реестру Hub лишь как списку координат для измерения. Quoter считает план Solver рекомендательным и переоценивает концентрированные ноги по самим пулам. Никто не доверяет calldata: маршрут приходит в Router как заявление незнакомца.

### §16.2 Жизнь одного свопа

![FIG-16 — Жизнь одного свопа: `previewAndEncode` (view) → calldata → дверь → `_execute` → площадки → расчёт, с проверкой на каждом шаге, названной по её коду отказа: минимум и срок на двери; форма и непрерывность хопов; измерение поступления; на каждом хопе — перемасштабирование, комиссия, если это комиссионный хоп, нижняя граница на ногу и шлюз покрытия, агрегат хопа; выметания; валовой выход; нижняя граница маршрута Φ; комиссия на стороне выхода, если есть; доставка, измеренная у получателя; `userMinOut`; журнал комиссий; `recordSwap` на каждую исполненную ногу; `ExecutionProof`.](https://blazephoenix.xyz/whitepaper/figs/ru/fig-16.png)

1. **Предпросмотр** (вне сети, бесплатно): `previewAndEncode` запускает `findBestRoutePlan` — реестр и, если он не свежий, обнаружение; Ξ; ранжирование Ψ; пробы; полоса; распределение; зажим; шлюз разбиения; аттестованная нижняя граница — и возвращает `Preview` и calldata. Трейдер задаёт `userMinOut` по собственному ценовому ожиданию.
2. **Дверь**: `swapExactIn` (или родственная функция) проверяет срок (`RouterE(4)`), отклоняет нулевой минимум (`RouterE(10)`), забирает вход и измеряет дельту.
3. **`_execute`**: число хопов, ноги на хоп и непрерывность хопов (`RouterE(3)`); сканирование комиссии; базовые линии рождаются один раз.
4. **На каждом хопе**: перемасштабирование по измеренному поступлению; взимание комиссии, если это комиссионный хоп (или каждый хоп в режиме исчерпания); каждый пул читается один раз для суммы, влияния и котировки; каждая нога исполняется через форму, выбранную её типом (`RouterE(8)` для неизвестного типа, `RouterE(9)` для хука, меняющего дельту); нижняя граница на ногу со шлюзом покрытия и агрегат хопа (`RouterE(5)`); колбэк платит только зафиксированному пулу (`RouterE(6)`).
5. **Выметания**: неизрасходованный вход и остатки мостовых монет сверх их базовых линий возвращаются плательщику.
6. **Расчёт**: валовой выход как дельта (`RouterE(8)`, если ноль); `floorBps` из измеренного влияния и концентрации; нижняя граница маршрута по котировке последнего хопа во фрейме и тройной максимум (`RouterE(5)`); комиссия на стороне выхода на прямом маршруте в мостовую монету; перевод, доставка, измеренная у получателя, `userMinOut` на доставленную сумму (`RouterE(5)`); журнал комиссий (`RouterE(15)`, `RouterE(16)`).
7. **Единственная запись**: `Hub.recordSwap` на каждую исполненную ногу, с измеренными суммами; затем события `ExecutionProof`, `Swap` и `Fee`. Если любая проверка отказывает, ничего не записывается; транзакции, включая своп каждого пула, никогда не было.

### §16.3 Размеры относительно EIP-170

| Контракт | Байты runtime | Запас до порога 24,000 байт | Запас до 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 |
| Итого | 84,986 | | |

![FIG-17 — Размеры runtime пяти контрактов относительно порога проекта 24,000 байт и лимита EIP-170 в 24,576 байт, измеренные на релизном профиле; для масштаба — развёрнутый роутер V1 (14,624 байт).](https://blazephoenix.xyz/whitepaper/figs/ru/fig-17.png)

Порог утверждается внутри самого набора (`DeployedSizeGate`) на том же профиле, под которым работает набор, а `profile_parity.py` держит его идентичным релизному профилю: набор, тестирующий бинарник, который сеть не смогла бы принять, тестировал бы не тот объект. Размер измеряется, никогда не оценивается: проверка, оценённая в десятки байт, однажды при измерении заняла несколько сотен, потому что выбранная форма продлила время жизни переменной. Router и Hub находятся в нескольких сотнях байт от стены, поэтому доказательство пары из §9 стоит 179 байт, а исправление стабильной кривой из §6 на 35 байт *меньше* кода, который оно заменило: каждый добавленный байт оплачен удалённым байтом.

### §16.4 Поверхность атаки в числах

Поверхность атаки — это то, что противник может записать, что контракт хранит, кого он вызывает и у кого есть власть над ним. Каждое из этого здесь — счётная величина.

**Что может записать вызывающий.** Маршрут — типизированная структура: не более 3 хопов × 5 ног, у каждой ноги десять полей (`pool`, `hooks`, `kind`, `fee`, `tickSpacing`, `zeroForOne`, `stable`, `amountIn`, `expectedOut`, `auxId`), — и ни одно поле не называет цель вызова и не несёт блоб calldata. `docs/assurance/fields.json` классифицирует каждое записываемое вызывающим поле, которое достигает состояния, события или проверки, — всего 23:

| Класс | Количество | Значение | Примеры |
|---|---|---|---|
| **подтверждённые** | 14 | исполнение читает поле, *и* проверка сравнивает его с наблюдением | `leg.kind`, заново выводимый из формы пула (Hub `recordSwap`); `leg.fee` на концентрированных пулах, где отвечает пул; `leg.amountIn`, перемасштабируемый по измеренному поступлению; `leg.expectedOut`, поднимаемый шлюзом покрытия; `hop.tokenIn`/`tokenOut`, сверяемые с собственными `token0`/`token1` пула; `leg.hooks` и `leg.auxId` на V4, которые входят в id исполняемого пула |
| **направляющие** | 5 | поле направляет движение токенов, поэтому ложное значение наказывает вызывающего и не требует отдельной проверки | `leg.pool` на парных и концентрированных площадках (токены отправляются в него); `leg.zeroForOne` на V4 (неверное направление закрывается отказом в учёте менеджера); `route.singleOutFloor` (монотонно — может только ужесточать); рекомендательные поля маршрута (`totalOut`, `estGas`, …), которые никто не читает |
| **объявленные** | 4 | оно достигает состояния, события или проверки, и ничто его не наблюдает — для каждого записана причина | `leg.fee` на парах V2 (ни одна пара не раскрывает `fee()`; ограничено потолком 100 bps и используется и котировкой, и исполнением, так что они не могут разойтись); `leg.hooks` на ногах не V4 (питает только порядок «хук последним»; ложь стоит вызывающему его собственного маршрута); `leg.expectedOut` в виде, опубликованном в событии `Volume` (ограничено около +25 % нижней границей на ногу, потребляющей то же поле; ончейн-потребителя нет); `leg.stable` (Hub измеряет `stable()` при регистрации, и оба потребителя читают одно и то же объявление) |

Ранжирование реестра, нижняя граница протокола и база комиссии вычисляются только из подтверждённых величин. Hub подтверждает `kind`; Router подтверждает суммы, токены и концентрированные комиссии; вывод подтверждает поля V4 по построению.

**Что Router хранит между транзакциями.** Нулевой баланс токенов — `INV-1`, утверждаемый `invariant_RouterHoldsNothing`, `invariant_routerHoldsNothing` и `invariant_HoldsNothingBeyondTheSeed` во всех кампаниях, под наблюдением четырёх мутантов-стражей (перевод на один wei меньше и удаление каждой из трёх базовых линий). Комиссии текут в казначейства внутри свопа; остатки возвращаются плательщику внутри свопа; единственное, что может лежать в Router, — ошибочный перевод, для возврата которого существует 48-часовое спасение и который базовые линии каждого свопа исключают из выплаты. Три из пяти контрактов — Core, Solver, Quoter — вообще не выполняют записей в хранилище.

**Кого вызывает своп.** Контрагенты одного свопа образуют замкнутое множество из восьми классов: входной токен; выходной токен; пулы, названные маршрутом (не более 15); Hub; Solver — только через `swapBestExactIn` и только как view; WETH и PoolManager — только для ног V4 и нативных ног; Permit2 — только через свою дверь; казначейства и получатель. Девятого нет: исполнитель не принимает цели вызова от вызывающего (SOK-ARBITRARY-CALL), диспетчеризуя взаимодействие с площадкой из замкнутого множества форм, выбираемых типом, хранящимся в реестре. Каждое охраняемое чтение ограничено 100,000 газа. Точное число инструкций свопа записывается, а не утверждается: 4,382 инструкции Router для одного честного свопа V2 на релизном бинарнике, с проверкой опкода на каждом шаге (§15.10).

**У кого власть.** Девять функций контроля в Router и шесть в Hub, каждая перечислена в §14.6–§14.7 с тем, до чего она может и не может дотянуться; ни одна не может двигать основную сумму, комиссию, распределение или нижние границы; все они заканчиваются на `renounceControl`, и выживает только растущий уровень кураторов из четырёх функций плюс существующие места операторов. Нет `selfdestruct`, нет прокси, нет инициализатора после одноразового `initialize` в Hub, и единственные `delegatecall` в артефакте — собственные вызовы компилятора в публичную библиотеку Core.

**Что должно существовать, чтобы сделка рассчиталась.** Бюджет из §3.1: ни сервиса маршрутизации, ни оракула, ни сети решателей, ни кипера, ни прокси — пять нулей там, где у действующего дизайна стоит «обязательно», «обычно» или «по-разному».

**Подсчитанная разница.** По сравнению с дизайном, который получает маршрут вне сети и исполняет обобщённый список вызовов, или держит инвентарь:

| Поверхность | Этот дизайн | Исполнитель с решением вне сети | Площадка или роутер с инвентарём |
|---|---|---|---|
| записываемые вызывающим поля, достигающие проверки | 23, из них 14 подтверждены наблюдением и 0 называют цель вызова | список пар `(target, calldata)`: неограничен, и каждая цель — вызов, который контракт делает на слово вызывающего | — |
| баланс токенов в покое в исполнителе | 0 (`INV-1`) | обычно 0 | сам инвентарь |
| контракты, пишущие в хранилище во время свопа | 1 (`Hub.recordSwap`, одна запись на исполненную ногу, только на успешном пути) | по-разному | учёт площадки |
| кто вычисляет маршрут | сеть (`findBestRoutePlan`, `view`) или вызывающий, в любом случае заново измеряемый во фрейме | сервер; контракт проверяет, но не выбирает | — |
| оракулы, киперы, прокси, пути обновления | 0 | обычно ≥ 1 каждого | обычно прокси |
| привилегированные функции, способные дотянуться до основной суммы | 0 | админ прокси может заменить логику | по-разному |
| развёрнутые байты, которые должен прочитать аудитор | 84,986, пять контрактов, все в пределах EIP-170 | контракт расчёта плюс сервер, чей размер в проверяемых строках непознаваем | — |

Сравнение подсчитано, а не аргументировано: разница — это ограниченный, типизированный, подтверждённый маршрут против неограниченного списка; ноль против инвентаря; одна запись против многих; и маршрут, выведенный сетью — или выведенный вызывающим и заново измеренный сетью, — против маршрута, выведенного сервером и лишь проверенного сетью.

## §17 Измеренная производительность

*Просто:* всё в этом разделе измерено на форках реальных сетей — реальные площадки, реальные резервы, внесённые посторонними людьми, один байткод — и помечено именно так. *Точно:* число появляется здесь, только если его воспроизводит публичный стенд в репозитории; стенд, сеть и дата указаны рядом. Измерение на форке — свидетельство о поведении, а не запись о развёртывании; запись о развёртывании — это §19. Измерения §12 и §15 сделаны 2026-09-05; кампания этого раздела проведена в августе 2026 года, и её стенды лежат в `test/fork/`.

### §17.1 Межсетевая идентичность

Один и тот же байткод прогонялся на форках Ethereum, Arbitrum, Optimism и Base, подстраиваясь под набор площадок каждой сети — Aerodrome и Pancake рядом с V4 на Base, Camelot на Arbitrum, Velodrome на Optimism — без единого изменения кода под конкретную сеть. Эти четыре сети выбраны потому, что вместе они задействуют каждое семейство площадок, которое допускает реестр, так что особый случай для отдельной сети, если бы он существовал, имел четыре шанса проявиться. В каждой сети две идентичности измерялись против эталона: для вывода CREATE2 — каждый адрес пула, вычисленный 𝒟, против адреса, который собственная фабрика сети сообщает для той же пары и параметров, причём `hasCode` подтверждает байткод по каждому выведенному адресу; для Exact Pass — собственный своп площадки, вызванный над живым состоянием, с дельтами, вынесенными в revert-нагрузке, по каждой сети, с соблюдением соглашений о колбэках каждого семейства, включая Algebra. Обе идентичности выполняются во всех четырёх сетях, и столбец расхождений, который понадобился бы таблице, пуст.

### §17.2 Точность «превью → доставка» в четырёх сетях

Тезис допускает прямое измерение: прокотировать своп, исполнить его, сравнить. Форк-стенд `FidelityMatrix` котирует, исполняет и рассчитывает реальные пары на Arbitrum, Base, Optimism и Robinhood Chain на зафиксированных блоках и печатает превью против доставки для каждой затронутой пары; в нём стоит постоянная тревога на 50 bps, так что регрессия точности сама заявит о себе. Измерено 2026-08-24:

| Сеть | Превью → доставка | Отмечено попутно |
|---|---|---|
| Arbitrum | 0–1 bps на каждой исполненной паре | маршрут Solver совпадает с исчерпывающей сеткой двустороннего разбиения с шагом 10 % |
| Base | 0–1 bps | в пределах 0.13 bps от максимума сетки; реальные исполнения 222,000–326,000 газа всего |
| Optimism | 0–1 bps | первое измеренное на форке исполнение этим движком с маршрутом через V4 на Optimism |
| Robinhood Chain | 0–1 bps | точное совпадение с сеткой; первые измеренные исполнения в этой сети |

Матрица исполняет от начала до конца ноги с постоянным произведением, концентрированные, V4 — включая нативные — и Solidly. Полоса — один базисный пункт на каждой исполненной паре, а за ней стоит тревога на 50 bps; сравнение Solver с сеткой из §17.1 — единственный результат, совпадающий до единицы, и он так и заявлен. Попутно получены два поведенческих результата: мостовые посредники выбираются адаптивно для каждой пары как максимизатор Мета-уравнения, а намеренно изолированная пара не возвращает маршрута вместо выдуманного.

### §17.3 Перепись фабрик

Регистрация площадки — это заявление, и у этого заявления есть собственный инструмент. Стенд переписи (`FactoryCensus`) наследует продакшн-обвязку развёртывания — он разворачивает через тот же скрипт, который получают сети, за шлюзом равенства, отказывающимся запускаться, если стенд и развёртывание разошлись. Он живёт вместе с этим скриптом в закрытом дереве развёртывания и не входит в публичный репозиторий, поэтому таблица ниже сообщается, а не воспроизводится из этой копии; публичный обход, разделяющий с ним фикстуры и шлюз против вакуумности, — `test/fork/TokenSweep.t.sol`. Он сообщает для каждой зарегистрированной фабрики в каждой сети по фиксированному корпусу токенов три столбца: зарегистрирована, держит пул для пары, выиграла пару (дала ногу выигравшего маршрута). У столбцов один знаменатель — пары-хопы, с явной корзиной неразрешённых, которая в измерении дала ноль во всех четырёх сетях: каждая нога, которую направил движок, приписана названной фабрике.

| Arbitrum, 88 пар-хопов | Держит пул | Выиграла пару |
|---|---|---|
| Uniswap V4 (нативный + обёрнутый) | 67 | 48 |
| Uniswap V3 | 86 | 33 |
| Pancake V3 | 77 | 13 |
| Camelot V3 | 66 | 10 |

Пара засчитывается каждой площадке, давшей ногу её выигравшего маршрута, поэтому столбец может превышать число пар — это атрибуция, а не разбиение. На Arbitrum семейство V4 выигрывает больше пар, чем любая другая зарегистрированная площадка, включая каноническую фабрику V3, — внешнее подтверждение порядка проб «сначала нативный» из §5.1. Перепись — ещё и инструмент отсечения: фабрика, которая зарегистрирована, держит пулы и ничего не выигрывает на сотнях измеренных пар, — кандидат на вывод из обращения, оценённый атрибуцией, а не репутацией, и каждый обход несёт шлюз против вакуумности — пара, о которой сообщено, что у неё есть глубина, должна также давать ненулевую котировку.

### §17.4 Газ

Каждая цифра прослеживается до названного стенда; холодные и тёплые значения указаны раздельно.

| Измерение | Значение | Источник |
|---|---|---|
| холодное решение (первое обнаружение, 4 фабрики) | 169,093 газа | `test/fork/DiscoveryColdWarm.t.sol`; `REPORTS.md` |
| тёплое решение (кэшированные Моно-слоты, обнаружение пропущено) | 132,691 газа | тот же прогон |
| тёплая экономия | −36,402 газа (−21 %) | разность |
| предельная стоимость каждой дополнительной ноги | ≈ 28,700 газа | `LifecycleMetrics` (август 2026) |
| второй хоп через мост | ≈ 59,000 газа | `LifecycleMetrics` (август 2026) |
| `previewPlan` на живом Base, холодно / тепло / холодно после TTL | 1,524,221 / 1,414,633 / 1,359,925 газа | `test/fork/QuoterGasFork.t.sol` (§12.3) |

![FIG-18 — Сколько стоит котировка через ABI: `previewPlan` и `previewAndEncode` на моках (обнаружение против свежего реестра, среднее и разброс по сорока размерам) и на живом Base (холодно, тепло, холодно после TTL).](https://blazephoenix.xyz/whitepaper/figs/ru/fig-18.png)

Три структурных вывода верны без всякого измерения, потому что следуют из архитектуры. Тёплая экономия растёт с широтой реестра, а не с размером сделки: холодный путь платит за обход фабрик, а кэшированное чтение, заменяющее обход, — это в любом случае один `SLOAD`. Ноги — единица предельной стоимости, поэтому и существует шлюз разбиения (§10.4). А хопы — дорогое измерение: мостовой маршрут должен выиграть в пулах достаточно, чтобы окупить себя на газе. Вызывающие выбирают свою точку на этой поверхности для каждой сделки — решать внутри фрейма и платить за поиск или передать маршрут и платить только за исполнение, — а шлюз свежести из §9.4 оставляет обход для пар, которых протокол ещё не знает. Стоимость живой котировки — 1.4–1.5 миллиона газа *view*-вычислений, которые в сети никто не оплачивает.

### §17.5 Исполнитель против конкурентов

Таблица газа без внешнего эталона — самовосхваление, поэтому слой исполнения был сравнён на том же форке, на том же блоке, с реальными продакшн-calldata: `MetaAggregationRouterV2` от KyberSwap (`0x6131B5fae19EA4f9D964eAc0408E4408b66337b5`) на форке Base, зафиксированном на блоке, на котором строился каждый маршрут, 2026-08-22, с Paraswap, прокотированным рядом как второй ориентир: собственные закодированные маршруты Kyber, запрошенные из его публичного API и воспроизведённые против его развёрнутого контракта, с нашими маршрутами, исполненными рядом, и с доставкой обеих сторон, измеренной как дельты балансов. Стенд сначала проверяет сам себя: измеренная доставка конкурента совпадает с обещанием его API до wei, так что revert на любой стороне не может выдать себя за результат. На одну ногу исполнитель оказался в 2.4× – 10.9× эффективнее по газу, чем эталон, — на самой наглядной паре 322,172 газа за четыре ноги против 879,553 за одну, 2.7× на транзакцию, — по данным стенда (`BenchExec.t.sol`), который живёт вместе с захваченными calldata в закрытом дереве, поэтому числа сообщаются, а не воспроизводятся из этой копии, скомпилированной с `optimizer_runs = 999999`, — так что разница в дизайне, а не в настройках компилятора: их роутер интерпретирует общий список вызовов, решённый сервером, наш исполняет типизированную структуру, а интерпретация стоит денег. На самой наглядной паре их одноногий маршрут стоил 879,553 газа; четыре наши ноги — 322,172. На девяти измеренных парах полностью решающая дверь и дверь calldata доставили идентичный выход. Погрешности метода — часть результата: котировки фиксировались на блоке, для которого были построены, а оценки газа со стороны API отброшены после того, как реальное исполнение показало, что одна из них занижена на 148 %.

### §17.6 Курс байт–газ ρ*

Какой дверью пользоваться вызывающему — нести маршрут в calldata или дать сети решить? Стоимость транзакции аффинна и разделима — $21{,}000 + \mathrm{bytes} \cdot p_{\mathrm{byte}} + \mathrm{gas} \cdot p_{\mathrm{gas}}$ — поэтому весь размен между calldata и исполнением сводится к одному скаляру: **ρ\***, газу исполнения, который покупает один байт calldata. Измеренный с `test/EquationBench.t.sol` на релизном профиле, ρ* составляет от 66.9 газа на байт на одноногих маршрутах до 105.5 на четырёх- и пятиногих, по всей транзакции. Ему противостоит живая цена байта каждой сети, считанная из собственных предразвёрнутых контрактов комиссий сети — газового оракула OP-Stack, `ArbGasInfo` у Arbitrum — без ключа и без стороннего фида.

| Сеть, цены считаны вживую 2026-08 | $p_{\mathrm{byte}}$ (газ/байт) | Доля данных в свопе | Дешёвая дверь |
|---|---|---|---|
| Base | 7.2 | 0.9 % | маршрут в calldata |
| Arbitrum | 12.8 | 3.4 % | маршрут в calldata |
| Optimism | 33.9 | 14.0 % | маршрут в calldata |
| ZK-роллап, оплачивающий доступность данных по цене calldata | 243,256 | 99.9 % | решение внутри фрейма — в 14× дешевле от начала до конца |

Там, где $p_{\mathrm{byte}} < \rho^{*}$, дешёвая дверь — calldata; там, где $p_{\mathrm{byte}} > \rho^{*}$, — решение внутри фрейма. В сетях OP-Stack и Arbitrum член данных составляет 0.9–14 % стоимости свопа, так что деньги уходят на исполнение — и именно там накапливается описанная выше эффективность исполнителя. Четыре двери Router — не функция для удобства; это аффинная модель стоимости, превращённая в интерфейс.

## §18 DEX, раздетый до костей

Семнадцать разделов механизмов сводятся к одной странице. Если не читать ничего другого, прочтите это.

**Одна задача.** Принять два токена, сумму, ваш минимум, получателя и дедлайн. Вернуть наибольший выход, который способна доставить публичная ликвидность сети, — или отказать с кодом, объясняющим почему. Третьего исхода нет: в матрице враждебных площадок двадцать из двадцати фикстур «патология площадки × дверь» рассчитываются в пределах нижних границ или отказывают с нашим селектором (§15.5).

**Пять контрактов, одна математика.**

| Контракт | Что это, в одной строке |
|---|---|
| **Core** | Библиотека без состояния: замкнутая форма каждой площадки, вывод 𝒟, нижняя граница Φ, оценка жизнеспособности Ψ. Определена один раз, импортируется остальными четырьмя. |
| **Hub** | Реестр, который узнаёт ликвидность, торгуя ею, — и доказывает каждую пару, прежде чем сохранить её. Он ничего не оценивает. |
| **Solver** | Строит маршрут и разбиение из *измеренного* предельного выхода и глубины. Только `view`. |
| **Router** | Единственный контракт, который перемещает средства. Измеряет каждую дельту баланса, обеспечивает нижнюю границу, ничего не держит в покое. |
| **Quoter** | Бесплатное превью — тот же вычислитель, которым рассчитывается Router, поэтому превью и доставка совпадают. |

**Шесть типов площадок, четыре семейства.** Uniswap V2, V3 и V4 (включая нативный ETH), пулы Algebra с динамической комиссией и пары Solidly stable и volatile — каждая оценивается одной замкнутой формой в Core, один диспетчер Ω на все (§4). Curve и Balancer удалены, а их номера типов выведены из обращения навсегда (§4.1).

**Четыре двери, один исполнитель.** `swapExactIn`, `swapExactInWithPermit2` и `swapExactInNative` принимают маршрут в calldata; `swapBestExactIn` решает и исполняет атомарно в сети. Все четыре приходят в один и тот же `_execute`, к тем же нижним границам и той же комиссии (§11.1).

**Три нижние границы под каждой сделкой.** Граница на ногу, агрегат по хопу и граница маршрута Φ, заново выведенная из измеренного влияния в исполняющем фрейме, — а затем *ваш* `userMinOut` на то, что реально приходит получателю. Доставка — большее из границы протокола и вашей (§11.5).

**Одна комиссия, вкомпилированная.** 0.28 %, взимаемая один раз с первой мостовой монеты, которую держит Router, с округлением вверх, чтобы она никогда не была нулевой, и без какой-либо части, растущей с разрывом между котировкой и доставкой (§13).

**Чему он отказывается доверять.** Заявленной ликвидности. Объявленным комиссиям. Глубине из calldata. Собственному рассказу пула о том, какими токенами он торгует. Каждое из этого либо измеряется по пулу, либо выводится арифметикой, прежде чем попасть в общее состояние (§7, §9, §16.4).

**Чего это стоит.** Газ: вывод маршрута внутри фрейма дороже, чем расчёт по ответу, вычисленному где-то ещё, и §17.6 говорит вам для каждой сети, какая дверь дешевле. Неизменяемость: дефект раскрывается и вознаграждается баунти, но никогда не патчится (§21).

## §19 Статус развёртывания

Этот документ описывает репозиторий, и репозиторий каноничен: там, где этот текст и исходный код расходятся, побеждает исходный код. Существуют два поколения, и они никогда не смешиваются.

**V1 — это то, что работает в сети.** Это другая, более ранняя кодовая база, заархивированная в собственном репозитории, — а не более ранний коммит этого. По измерению через RPC 2026-09-04, развёрнутый роутер V1 — один и тот же контракт размером 14,624 байта на Base, Ethereum, Optimism и Arbitrum. `test/fork/DeployedParity.t.sol` и `test/fork/DeployedCodehashPin.t.sol` фиксируют, что такое V1, в каждой сети, которую называет SDK, — codehash каждого развёрнутого контракта, записанный в `test/fork/deployed-codehash.json`, — так что изменение развёрнутого будет замечено, и они проверяют свойства, которые выполняются независимо от поколения: нет `SELFDESTRUCT`, нет `CALLCODE`, нет полномочий через `tx.origin`, есть транзиентная блокировка повторного входа, фиксированный размер рантайма, живая обвязка и ещё не отозванный контроль.

**Этот репозиторий — V2, и V2 не развёрнут.** Дерево, измеренное в этом документе, собирает Router размером 23,781 байта и несёт внешние функции, которых нет у V1, — среди них дверь для нативного значения, шов wrap/unwrap, путь спасения средств и дверь Permit2. Контракты V2 разворачиваются на форках живой ликвидности и прогоняются от начала до конца при каждом форк-прогоне (§15.1, §17). Ничто в §15 или §17 не защищает развёрнутые контракты V1; каждое утверждение в этом документе — утверждение о V2 в состоянии на `8949a9d`.

Соответственно, читателей защищают две политики. Этот документ не содержит адресов контрактов — адреса переживают документы, которые их цитируют, а живая поверхность адресов поддерживается там, где её можно обновлять и машинно проверять: в записях репозитория, на странице верификации сайта и в манифесте. А у записи о развёртывании есть заданный формат — сеть, адрес, коммит развёртывания и хеш рантайм-байткода, записанные в момент отправки транзакций (broadcast), с шагом верификации, который заново выводит хеш из чистой сборки, — так что когда V2 будет развёрнут, каждое утверждение об адресе будет арифметикой, а не доверием.

## §20 Токен — BZPX

Один миллиард BZPX, зафиксированный навсегда, как обычный ERC-20: 18 знаков после запятой, нет пути выпуска, нет комиссии при переводе (fee-on-transfer), нет ребейзинга, нет хуков перевода, нет паузы или чёрного списка — шесть инвариантов развёртывания, проверяемых по байткоду токена в момент обвязки. Полностью разводнённое предложение известно с генезиса; дефицитность — свойство контракта, а не политика.

| Распределение | Токены | Доля | Примечание |
|---|---|---|---|
| Рыночная ликвидность | 520,000,000 | 52.0 % | оборотный объём, через который маршрутизирует агрегатор |
| Эмиссия стейкинга | 180,000,000 | 18.0 % | зарезервировано; дизайн эмиссии V4 — в §25 |
| Аирдроп и баунти | 130,000,000 | 13.0 % | один пул: бывший аирдроп на 50,000,000, бывший исследовательский пул на 50,000,000 из §21 и 30,000,000, перенесённые из рыночной ликвидности |
| Команда и операционная деятельность | 103,000,000 | 10.3 % | единственный заблокированный инсайдерский транш; линейный вестинг 30 месяцев |
| Партнёры и экосистема | 60,000,000 | 6.0 % | интеграции, партнёрства с площадками |
| Маркетинг | 7,000,000 | 0.7 % | узнаваемость при запуске |

**Что изменилось в сентябре 2026 года.** Аирдроп и пул исследований безопасности были двумя строками, которые платили одним и тем же людям за одно и то же — за пристрастное чтение протокола и реальное его использование. Теперь это одна строка в 130,000,000 BZPX: 50,000,000 из аирдропа, 50,000,000 из исследовательского пула и 30,000,000, взятые из рыночной ликвидности, которая уменьшается с 550,000,000 до 520,000,000. Ни у чьей другой строки ничего не взято, и предложение по-прежнему ровно один миллиард.

Ончейн-запись контракта токена, когда она будет опубликована, станет канонической версией этой таблицы. На момент написания у токена нет предоставленной ликвидности и не было события генерации токена; нулевой ончейн-объём — ожидаемое состояние на этом этапе, а не сигнал о коде.

## §21 Исследовательская программа и история безопасности

Ещё до того, как у этого проекта появилась исследовательская программа, достойная этого имени, независимые исследователи читали исходный код враждебно — без просьбы, без каких-либо обещаний — и приватно раскрывали увиденное. Код, который описывает этот документ, — это код, переживший их чтение. Что сообщил каждый из них и что из этого вышло, хранится там, где таким записям и место: в `SECURITY.md` и `SECURITY_HALL_OF_FAME.md` репозитория, рядом с кодом, где каждую запись можно проверить в обоих направлениях. Этот раздел фиксирует, кто они, сколько платит враждебное чтение и какова форма найденного.

**Исследователи.** 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 · и один исследователь, пожелавший остаться анонимным. Отмечены двадцать пять исследователей; ни один отчёт не достиг класса Critical. Атрибуция никогда не округляется вниз: имена публикуются ровно так, как они были указаны.

**Пул: Аирдроп и баунти, 130,000,000 BZPX — 13 % фиксированного предложения** (§20). Награды за безопасность выплачиваются из него по диапазонам ниже, которые не изменились.

| Серьёзность | Награда |
|---|---|
| Critical — прямая кража или перманентная заморозка средств пользователей; достижимое неплатёжеспособное состояние | 2,500,000 – 7,500,000 BZPX |
| High — кража при особых условиях; перераспределение, систематически платящее не той стороне | 625,000 – 2,500,000 BZPX |
| Medium — гриферство, временный отказ в обслуживании, восстановимый остаток | 125,000 – 625,000 BZPX |
| Low — продемонстрированное влияние ниже указанного выше | до 125,000 BZPX |

Награды выплачиваются в BZPX — количество и график обещаны, рыночная стоимость нет, — выплаты начинаются после октября 2026 года, а отчёты разбираются уже сейчас. Одна оговорка сделана намеренно: отчёту не нужно нарушать закон сохранения, чтобы быть Critical, — перераспределение, систематически платящее не той стороне при сходящемся балансе, оценивается как кража, потому что это класс, который ловят только враждебные читатели.

**Что содержит история, по классам.** Каждая подтверждённая находка стала именованным свойством, регрессионным тестом, который падает на коде до исправления, и мутантом, которого тест должен убить; классы, по одному предложению на каждый, со ссылкой на реестр:

- *Два производителя одной величины* — планировщик и исполнитель, превью и доставка, отвечающие на один вопрос в двух фреймах (агрегирование влияния в нижней границе, мост, названный превью, против хопа, на котором закрепляется комиссия, бюджет ног в двух контрактах, замороженный бит моста против живого предиката): закрыто сведением к одному производителю или именованным тестом паритета и перечислено в реестре с правилом CI.
- *Величина от вызывающего там, где существовало измерение* — база комиссии, прочитанная из `route.totalOut`, комиссия и хуки реестра, записанные из calldata, глубина, объявленная, а не измеренная, опубликованный объём, отражавший объявление: закрыто измерением и классификацией полей из §16.4.
- *Отсутствие, прочитанное как разрешение* — последний хоп, который нельзя было прокотировать внутри фрейма, оставлявший нижнюю границу бездействующей, шлюз покрытия, который вызывающий мог отключить, записав ноль, пустой список комиссий, котировавший целое семейство по нулю: закрыто откатом к удостоверенной или измеренной величине, но никогда — к ничему.
- *Фиксация не того объекта* — источник вывода фабрики, который прокси мог сдвинуть, пока её codehash оставался прежним, рантайм хука, зафиксированный там, где его реализация могла меняться: закрыто удостоверением ответа там, где код нельзя зафиксировать, и указанием остаточного риска в том месте, где его нельзя закрыть.
- *Fail-open в котировке* — защита области стабильной кривой, которая сама переполнялась на пыли, найденная N-version-линией: закрыто по принципу «сначала красный тест», причём исправление оказалось меньше заменённого кода.
- *Исправление, применённое к одному из двух симметричных каналов* — сигнатура дефекта, которую эта кодовая база называет первой и которая встречалась неоднократно; теперь каждое закрытие перед слиянием ищет симметричного «брата».

Приглашение — суть этого раздела, и оно постоянно: независимые читатели приглашаются публично, и им платят в валюте с фиксированным количеством. Сообщайте приватно, через канал раскрытия в `SECURITY.md` репозитория.

## §22 Чему вы должны доверять, а чему нет

Каждый протокол заявляет о бездоверительности; почти никто не может перечислить свои исключения. Вот полный список, каждое полномочие — с той строкой, которая его ограничивает.

- **Реестр площадок, список мостов и allow-list хуков V4** — курируются; худший случай враждебного листинга ограничен более строгим из вашего минимума и нижней границы Iron-Law Φ; основную сумму он достать не может (§9.6, §14.6).
- **Управляющий ключ Router до отказа от контроля** — может перенаправить получателя комиссии, приостановить свопы, перенастроить контракты WETH и Permit2, которые вызывают его нативная дверь и дверь Permit2, и вернуть ошибочно отправленные токены после 48-часовой публичной задержки; не может изменить ставку комиссии, распределение или нижние границы (константы времени компиляции, без сеттеров) или достать основную сумму (нет прокси, нет пути обновления, нет `selfdestruct`, ничего не хранится в покое).
- **Управляющий ключ Hub до отказа от контроля** — может перенастроить представление Hub о Router, Solver и Quoter, перенастроить менеджер V4, приостановить обучение реестра и выдавать места операторов; не может неверно оценить исполнение, потому что ничто, оценивающее сделку, не читает реестр.
- **Казначейства** — зафиксированы при конструировании, и у распределения 30 / 70 нет сеттера.
- **Канонические контракты, от которых зависят двери** — Permit2 от Uniswap для двери Permit2, WETH сети для нативной двери, PoolManager для ног V4: каждый вызывается только через свою дверь или свою ногу, а Router оценивает каждую ногу по собственной измеренной дельте баланса, так что нечестный контрагент обваливает выход, и ваша граница отклоняет своп.
- **Токен и соответствие** — что BZPX удовлетворяет своим шести инвариантам развёртывания и что адреса, которые вы вызываете, соответствуют этому тексту: проверяемо с момента публикации записей §19 по процедуре, которую задаёт §19.

Чему доверять не нужно: никому — оценку вашей сделки (маршрут выводится в исполняющем фрейме из публичного состояния или заново измеряется там, если вы его передали); никому — честную маршрутизацию (нижняя граница заново выводится в сети из измеренного выхода, а у комиссии нет члена, растущего с разрывом между котировкой и доставкой); никому — подсчёт комиссии (Router отказывается рассчитываться, не заплатив её); и никому — продолжение обслуживания (если бы сегодня ночью исчезли все серверы, которыми управляет этот проект, завтра контракты котировали бы, маршрутизировали и рассчитывали ровно так же, как сегодня).

И если вы сохраните из этого документа одну привычку, пусть это будет минимум, который вы ставите на свои сделки. Каждая граница в агрегаторе в конечном счёте опирается на это число; это единственная защита в системе, качество которой зависит ни от чьей компетентности, кроме вашей.

## §23 Связанные работы

**Маршрутизация.** Angeris, Evans, Chitra и Boyd формулируют маршрутизацию по маркет-мейкерам с постоянной функцией как выпуклую программу, оптимум которой выравнивает предельные цены по площадкам; Diamandis, Resnick, Chitra и Angeris дают для неё эффективный алгоритм двойственной декомпозиции. Этот дизайн сознательно останавливается в сети не доходя до этого оптимума: распределитель разбиения пропорционален глубине за один проход (§10.4), что не требует ни итераций, ни модели кривизны каждой площадки и устойчиво там, где итеративный солвер — это счёт за газ и поверхность атаки; метаморфная линия измеряет, сколько стоит этот выбор, — разбиение никогда не платит меньше лучшего одиночного пула (MR-R1), а матрица точности помещает маршрут Solver в пределы 0.13 bps от исчерпывающей сетки на Base. Точная замкнутая форма, выравнивающая предельную отдачу, для семейства с постоянным произведением — это две суммы и квадратный корень на площадку, и она — кандидат для следующего релиза; целевая функция плоская вблизи оптимума, поэтому первой выходит устойчивая формула. Офчейн-дизайны солверов переносят оптимизацию на сервер и сводят контракт к проверяющему; Бюджет зависимостей из §3.1 — это утверждение данного документа о том, чего это стоит. Недавние работы расширяют модель газом: *Optimal Routing across Constant Function Market Makers with Gas Fees* показывает, что газ расширяет области без торговли и нетривиально взаимодействует с фрагментацией, а *Routing Frictions and Executable Liquidity in Fragmented Markets* отделяет заявленную ликвидность от исполнимой — различие, которое этот дизайн измеряет внутри фрейма, а не моделирует.

**Робастное агрегирование.** Центр правдоподобия из §10.3 — это медиана, взвешенная по глубине, точка пробоя которой — половина веса. Müller, Moumeni и Messaoudi в *Cost of Manipulation in AMM-Based Oracles* (FC 2026) доказывают соответствующую границу для оракулов, агрегирующих цены AMM: при равных весах самая дешёвая атака двигает самые мелкие пулы, а при весах по ликвидности контроль половины веса требует как минимум половины общей глубины котировки. Ormer предлагает ончейн-оракул, оценивающий медиану, вместо среднего TWAP, а *From Means to Medians* изучает, какими бенчмарками труднее всего манипулировать. E25 применяет тот же принцип к маршрутизации: пулу верят лишь настолько, насколько за ним стоит капитал под риском.

**Площадки.** Uniswap v2 даёт эталон постоянного произведения и соглашение о 30 bps, которое ветка V2 использует по умолчанию; Uniswap v3 — модель концентрированной ликвидности, `slot0` и фиксированную точку Q64.96, которым следует замкнутая форма из §4.2; Uniswap v4 — синглтон, flash accounting и кодирование разрешений хуков в адресе, на которых стоят §5 и §14.2, — здешний дизайн, насколько нам известно, первым выводит, доказывает, оценивает, отсеивает и рассчитывает пулы V4 полностью внутри фрейма. StableSwap Егорова определяет усиленный инвариант семейства Curve, которое этот протокол удалил, вместо того чтобы поддерживать его на неизмеренном допущении о доверии (§4.1); у стабильной кривой Solidly из §6 нет рецензируемой статьи, и она цитируется по своим развёрнутым реализациям.

**MEV.** Daian и соавт. дали имя противнику, управляющему порядком, и показали его масштаб; Zhou и соавт. измерили сэндвич-атаки на децентрализованных биржах и формализовали их прибыльность. Этот документ не заявляет об устранении — порядок решается до запуска кода контракта — а вместо этого измеряет границу: кривую сэндвича из §14.3 со стороны атакующего, с краем отказа и отрицательным круговым результатом атакующего за ним.

**Верификация.** Гиперсвойства Clarkson и Schneider называют класс, к которому относятся находки о перераспределении из §21, — свойства множеств трасс, а не одной трассы, — и объясняют, почему закон сохранения может выполняться, пока платят не той стороне. Абстрактная интерпретация Cousot и Cousot — рамка для замкнутой формы одного тика: абстракция математики площадки, точная в пределах диапазона и корректная в одном направлении за его пределами, с нижней границей в роли проверки конкретизации. Chen, Cheung и Yiu ввели метаморфное тестирование — второго судью из §15.7 там, где не существует оракула, независимого от формулы. DeMillo, Lipton и Sayward ввели мутационный анализ, на гипотезу связи которого опирается курируемая защита из §15.2. N-версионное программирование Avizienis — предок §15.8, применённый к генерации кода, а не к независимым командам. Комбинаторное тестирование Kuhn, Kacker и Lei даёт покрывающим массивам из §15.4 их построение и их утверждение: каждое t-way взаимодействие факторов режима задействовано по построению, а знаменатель напечатан. Результат Littlewood и Strigini о валидации сверхвысокой надёжности — причина, по которой нигде в этом документе не указана вероятность корректности.

**Стандарты.** Транзиентное хранилище EIP-1153 несёт контекст ноги Доктрины единого колбэка, реестр комиссий и блокировку повторного входа; EIP-170 — это стена в 24,576 байта, до которой Router и Hub не дотягивают на несколько сотен байт; EIP-1014 — арифметика CREATE2 для 𝒟; EIP-2612 и Permit2 от Uniswap определяют перевод на основе подписи для второй двери; ERC-7201 задаёт пространства имён хранилища Hub; делегированные аккаунты EIP-7702 вызывают те же двери, что и любой контракт.

## §24 Заключение

Мы задались целью построить агрегатор на одной дисциплине — измеряй, а не верь — и удержать её в каждой точке, где число иначе могло бы быть принято на чьё-то слово: маршрут, цена, нижняя граница, база комиссии, набор площадок, пара, которой пул якобы торгует, и даже собственная модель стоимости протокола.

Результат — небольшая система сильной формы. Фрагментация ликвидности оказалась проблемой языка, а не математики: под разрастанием площадок лежит небольшое семейство инвариантов, и протокол, выражающий каждый из них один раз — диспетчер, вывод, нижняя граница, поле жизнеспособности, — может оценивать весь ландшафт без особых случаев для каждой площадки, выводить маршрут внутри транзакции, которая его исполняет, и перепроверять собственное обещание по измеренному выходу, прежде чем токен дойдёт до вас. Измеренная история — кратчайшее резюме того, что это даёт: котировка без дрейфа, которая исполняется ровно по своему прогнозу 64 раза из 64, на моках и на живом Base; нижняя граница, ограничивающая сэндвич на сделке в 1 % примерно 2.7 % от неё и отказывающая дальше; превью и доставка, разделённые 0–1 базисным пунктом в четырёх сетях; и аппарат, который публикует со знаменателями, как его можно обыграть и чего он не видит.

Цена столь же очевидна. Газ: вывод маршрута внутри фрейма дороже, чем расчёт по ответу, вычисленному где-то ещё, — это цена гарантии, которую можно проверить, вместо той, которую приходится принимать на чьё-то слово, — а курс байт–газ говорит вам для каждой сети, какая дверь делает эту цену наименьшей. Неизменяемость: дефекты раскрываются и вознаграждаются баунти, а не патчатся — это цена контракта, который аудируется один раз, и §21 — то, что эта цена покупает.

Тезис ещё раз: то, что выбирает, — это то, что торгует. Котировка и её исполнение — одно и то же вычисление, поэтому разрыв, где раньше жила честность оператора, закрыт арифметикой; реестр узнаёт ликвидность, торгуя ею, и доказывает каждую пару, которую сохраняет; комиссию, нижнюю границу и поверхность входа никто — включая авторов — не может сдвинуть, потому что они вкомпилированы. Протокол не просит доверия. Он просит проверки и работает над тем, чтобы проверка была дешёвой, публичной и постоянной.

## §25 Staking V4: дизайн

Staking V4 — текущая кодовая база; адрес публикуется при запуске.

Эта глава описывает движок стейкинга в том виде, в каком он существует в исходном коде: `src/BlazePhoenixStaking.sol` в репозитории `Blaze-Phoenix-Staking`, `main` на коммите `cc614f9` (2026-10-14), где объявлено `VERSION = "4.0.0"` (L169). Все номера строк в этой главе (L*n*) относятся к этому файлу на этом коммите; если текст и исходный код расходятся, прав исходный код. В главе нет адресов, дат развёртывания и доходностей: каждое число в ней — константа, скомпилированная в контракт, или выведено из такой константы, и вывод приведён рядом.

К движку применяется та же дисциплина, что и к остальной части этой работы. Он хранит один токен, BZPX, и каждое требование на этот BZPX — статья учёта, сумму которых контракт перепроверяет вокруг каждого изменения состояния (E35). Стейкер блокирует BZPX на выбранное число дней и получает два потока — эмиссию по расписанию (E27–E29) и проценты, которые платят заёмщики (E31–E34), — каждый взвешен бустом, растущим вместе с обязательством (E30). Стейкер может занимать BZPX под залог своего стейка (E41). Когда возникает убыток, он проходит в одном фиксированном порядке (E37), а контракт в публичном view-методе сообщает, держит ли он то, что должен (E36).

### §25.1 Архитектура и роли

Один контракт, `BlazePhoenixStaking`, наследующий `AccessControl`, `Pausable` и `ReentrancyGuard` (L167). Токен, казна и окно эмиссии (`emissionStart`, `emissionEnd`) — immutable-переменные (L266–269); конструктор отклоняет адрес токена, по которому нет кода (L477). Нет ни оракула, ни пути обновления, ни сеттера параметров: каждая константа ниже скомпилирована в контракт.

| Роль | Кто владеет | Может | Строка |
|---|---|---|---|
| `DEFAULT_ADMIN_ROLE` | развёртывающий, при создании | выдавать и отзывать роли (вызова `_setRoleAdmin` нет, поэтому она администрирует все роли) | L485 |
| `ADMIN_ROLE` | развёртывающий, при создании | `fundEmission` (совокупное пополнение ≤ `TOTAL_REWARDS`); `withdrawReserve` (только в immutable-казну, не более `protocolReserve`, закрыт при жёстком нарушении); `sweepUndistributedEmission` (только после `emissionEnd`); `unpause`; `cancelEmergency` (только при платёжеспособной книге) | L486, L516, L533, L563, L580, L618 |
| `GUARDIAN_ROLE` | никто при создании; выдаётся администратором роли | `pause`; `declareEmergency` — ни то, ни другое не перемещает средства | L171, L578, L608 |
| любой | — | `liquidate`; `tripBreaker` (откатывается, если книга не в жёстком нарушении); `absorbBadDebt`; `lockStep` | L1066, L597, L639, L1291 |

Ни одна роль не может переместить основную сумму стейкера, и единственное место, куда любая роль может отправить доход, — immutable-казна.

| Константа | Значение | Смысл | Строка |
|---|---|---|---|
| `TOTAL_REWARDS` | 180,000,000e18 | бюджет эмиссии и потолок совокупного `fundEmission` | L192 |
| `HALVING_PERIOD` | 2 × 365 days | длина одного периода эмиссии | L193 |
| `EMISSION_PERIODS` | 8 | число периодов до закрытия программы (`EMISSION_LENGTH` = 16 лет) | L194–195 |
| `INITIAL_REWARD_PER_SEC` | (`TOTAL_REWARDS` / 2) / `HALVING_PERIOD` | ставка периода 0, $R_{0}$ | L196 |
| `MAX_STAKE_PER_WALLET` | 30,000,000e18 | потолок стейка на позицию | L198 |
| `MIN_EMISSION_WEIGHT` | 1,000e18 | ниже этого суммарного веса оба потока пропорционально урезаются | L208 |
| `MAX_LTV` | 50 | потолок заимствования, % чистого стейка | L210 |
| `LIQ_THRESHOLD` | 95 | ликвидируема при долге ≥ 95 % стейка | L211 |
| `LIQ_BONUS_BPS` | 500 | ликвидационный излишек вызывающему, bps от долга | L212 |
| `RESERVE_FACTOR_BPS` | 300 | доля каждого процентного среза, оставляемая в `protocolReserve` | L213 |
| `MIN_LOCK_DAYS`, `MAX_LOCK_DAYS` | 90, 2,555 | границы блокировки в днях | L221–222 |
| `BOOST_BASE`, `BOOST_LINEAR`, `BOOST_QUAD` | 10,000, 750, 250 | полином буста, bps | L224–226 |
| `MIN_DEPOSIT_BLOCKS` | 10 | сколько блоков должен выдержать депозит до вывода, получения чистой доходности или изменения блокировки | L228 |
| `RATE_R0`, `RATE_RK`, `RATE_UK`, `RATE_S1`, `RATE_S2` | 100, 500, 0.8e18, 500, 72,500 | кривая процентов с изломом, годовые bps | L231–235 |
| `MAX_PROTOCOL_UTIL_WAD` | 0.75e18 | потолок использования книги для нового займа | L242 |
| `CONSERVATION_DUST` | 1e10 | допуск защиты сохранения, в wei | L247 |
| `MAINT_BASE`, `MAINT_DENSITY`, `MAINT_GAP_UNIT`, `MAINT_MAX_SCAN`, `MAINT_MAX_LOCK_ACTIONS` | 1, 50, 15 minutes, 10, 4 | бюджет автономного обслуживания (E42) | L252–255, L263 |

### §25.2 Поток: стейк, анстейк, получение наград, заимствование

Каждая пользовательская точка входа выполняет один конвейер в порядке, заданном `deposit`: снимок тождества сохранения; отказ, если книга в жёстком нарушении; продвижение индекса процентов и окна эмиссии (`_updateGlobal`, L1428); расчёт обоих потоков по весу, который имела позиция; начисление процентов, которые должна позиция (`_accrueInterestFor`, L1627); снятие истёкшей блокировки; выполнение действия; пересчёт весов позиции через единственного писателя (`_resync` → `_applyBoost` → `_checkpoint`, L1726–1756); запуск окна автономного обслуживания (E42); и повторная проверка тождества сохранения (E35). Двери различаются только добавляемыми ими условиями.

| Точка входа | Пауза | Аварийный режим | Жёсткое нарушение | Дополнительные условия |
|---|---|---|---|---|
| `deposit(amount, lockDays)` — стейк | закрыта | закрыта | закрыта (L760) | $90 \le$ `lockDays` $\le \min(2{,}555,\ \text{days left to } \mathtt{emissionEnd})$; стейк позиции ≤ `MAX_STAKE_PER_WALLET`; до `emissionEnd` (L743–760) |
| `lock(lockDays)` | закрыта | закрыта | — | `MIN_DEPOSIT_BLOCKS` с последнего депозита (L964–971) |
| `withdraw(amount)` — анстейк | открыта | закрыта | закрыта (L902) | нет долга; блокировка истекла; `MIN_DEPOSIT_BLOCKS` с последнего депозита (L890–895) |
| `claimRewards()` | открыта | закрыта | закрыта (L932) | рассчитывает эмиссию и чистую доходность (L930) |
| `claimPureYield()` | открыта | закрыта | закрыта (L948) | нет долга; `MIN_DEPOSIT_BLOCKS` с последнего депозита (L944–948) |
| `borrow(amount)` | закрыта | закрыта | закрыта (L826) | E41 (L820–840) |
| `repay(amount)` | открыта | закрыта | открыта | — (L858) |
| `liquidate(user)` | закрыта | закрыта | открыта | E39 (L1066) |
| `emergencyWithdraw()` — выход | — | только | — | E40 (L658) |
| `absorbBadDebt(amount)` | открыта | открыта | открыта | E38 (L639) |

**Стейк.** `deposit` переводит BZPX в позицию и начинает или продлевает её блокировку; блокировка никогда не может выходить за конец программы эмиссии, а купленный ею буст (E30) действует только пока блокировка в силе. **Анстейк.** `withdraw` возвращает основную сумму, когда позиция ничего не должна и её блокировка истекла; он остаётся открытым при паузе гардиана, поэтому пауза останавливает новый риск, никого не запирая. В аварийном режиме единственный выход — `emergencyWithdraw`: он выплачивает чистый капитал позиции, пропорционально уменьшенный, если контракт держит меньше, чем причитается позициям (E40), и позиция теряет невостребованные потоки. **Получение наград.** Сбор урожая — это два claim-метода: `claimRewards` выплачивает оба потока, `claimPureYield` — то же для позиции без долга; отдельной функции сбора нет. **Заимствование, погашение, ликвидация.** Стейкер может занимать BZPX под собственный стейк в пределах двух потолков (E41); проценты списываются со стейка, а не добавляются к долгу (E34); `repay` остаётся открытым при паузе и при жёстком нарушении, потому что закрытие долга только делает книгу безопаснее. Позицию, долг которой достиг 95 % её стейка, может закрыть любой, и закрывающий получает излишек из E39.

![FIG-19 — Точки входа стейкинга и их единый конвейер: условия, добавляемые каждой дверью, затем снимок сохранения, проверка нарушения, глобальное обновление, расчёт, начисление процентов, истечение блокировки, действие, пересинхронизация единственным писателем, автономное обслуживание и проверка сохранения; аварийный выход стоит отдельно.](https://blazephoenix.xyz/whitepaper/figs/ru/fig-19.png)

### §25.3 Учёт наград

**Эмиссия.** Пусть $T$ = `TOTAL_REWARDS`, $P$ = `HALVING_PERIOD`, $R_{0}$ = `INITIAL_REWARD_PER_SEC`, $s$ = `emissionStart`, и для момента $t$ пусть $\tau = t - s$ и $p = \lfloor \tau / P \rfloor$. Совокупная эмиссия по расписанию задаётся замкнутой формулой (`_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

*Вывод.* В точной арифметике ставка периода $q$ равна $T/(2^{q+1}P)$, поэтому полный период $q$ эмитирует $T/2^{q+1}$, а эмиссия до периода $p$ равна $\sum_{q<p} T/2^{q+1} = T(1 - 2^{-p})$. $T = 1.8\times 10^{26}$ делится на $2^{8}$, поэтому $T \gg p = T/2^{p}$ точно при $p \le 8$, и первое слагаемое — эта сумма без округления. Слагаемое ставки округляется вниз: $R_{0} \gg p = \lfloor\lfloor T/(2P)\rfloor / 2^{p}\rfloor = \lfloor T/(2^{p+1}P)\rfloor$, что меньше точной ставки менее чем на один wei в секунду. Поэтому внутри периода $E$ растёт линейно и остаётся ниже точной кривой; на каждой смене периода она поднимается скачком на $T/2^{p+1} - (R_{0} \gg p)\,P$ — неотрицательный скачок меньше $P$ wei, возвращающий её к точному ряду. $E$ монотонно не убывает и закрывается на $T - T/2^{8} = \tfrac{255}{256}\,T$: последние $T/256$ никогда не включаются в расписание и вместе со всем, что осталось от пустых или непополненных окон, могут быть возвращены в казну только через `sweepUndistributedEmission` после закрытия (L563). Вычисление в точных целых числах по восьми сменам периода подтверждает, что каждый скачок положителен и меньше $P$.

![FIG-20 — Эмиссия по расписанию E27 во времени: ставка уменьшается вдвое каждые два года, а совокупная кривая закрывается на 255/256 от TOTAL_REWARDS через шестнадцать лет; по расписанию, а не выплачено — окно без веса ничего не эмитирует.](https://blazephoenix.xyz/whitepaper/figs/ru/fig-20.png)

**Окно.** В каждой точке входа, при $W$ = `totalBoostedEffective`, $W_{\min}$ = `MIN_EMISSION_WEIGHT`, $t_{\ell}$ = `lastRewardTime` и $t' = \min(t, \mathtt{emissionEnd})$, прошедшее окно зачисляется в аккумулятор на единицу веса $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` уменьшается на $r$, а `totalRewardDistributed` увеличивается на $r$. При $W = 0$ часы идут, но ничего не захватывается, так что никто не наследует накопленный хвост; ниже $W_{\min}$ окно пропорционально урезается, так что пылевая позиция не может забрать расписание целиком; а поскольку каждое окно ограничено резервом, `fundEmission` рассчитывает часы до поступления нового пополнения (L523), так что пополнение никогда не оплачивает окно задним числом.

**Расчёт.** Позиции с весом эмиссии $w_{u}$ и контрольной точкой $c_{u}$ (`rewardDebt`) причитается (`_settlePendingRewards`, L1487–1497; двойник для чистой доходности, L1499–1517, использует $w^{\mathrm{p}}_{u}$, $A^{\mathrm{p}}$ и `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

а после каждого изменения веса единственный писатель заново фиксирует контрольную точку $c_{u} = \lfloor w_{u}A/10^{18}\rfloor$ (`_checkpoint`, L1736), так что позиция зарабатывает только на окнах, закрывающихся после того, как её вес установлен.

**Буст.** Вес — это стейк позиции, умноженный на буст, который является полиномом от блокировки, оплачиваемой *в данный момент* (`boostByDays`, L496–505; `_effectiveLockDays`, L1687; `_computeBoost`, L1695–1700). При $\ell$ — блокировке в днях (ноль, как только время разблокировки прошло, независимо от того, нормализована ли позиция), ограниченной 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

где $s_{u}$ — стейк позиции, а $d_{u}$ — её долг. Точные значения: $b(90) = 10{,}199$, $b(365) = 11{,}000$, $b(730) = 12{,}500$, $b(1{,}825) = 20{,}000$, $b(2{,}555) = 27{,}500$ bps. Вес эмиссии считается от чистого стейка; вес чистой доходности существует только у позиции без долга — заёмщик не зарабатывает проценты, которые сам платит.

### §25.4 Проценты: кривая и индекс

**Кривая.** Использование равно $U = \min(\lfloor D \cdot 10^{18} / S \rfloor, 10^{18})$, где $D$ = `totalDebt` и $S$ = `totalStaked` ($U = 10^{18}$, если $S = 0 < D$; базовая ставка, если оба равны нулю), а годовая ставка в bps имеет излом на 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

Две ветви сходятся в точке излома — $100 + 0.8 \times 500 = 500$ — и кривая достигает $500 + 0.2 \times 72{,}500 = 15{,}000$ bps при полном использовании. Заём не может поднять книгу выше 75 % (E41), поэтому ни один заём сам по себе не входит в крутую ветвь.

**Индекс.** Каждый прошедший срез $\Delta t$ помечается ставкой, действовавшей на его протяжении, до любой записи в $D$ или $S$ (`_interestSlice`, L1589–1619; `_updateInterestIndex`, L1539–1583), при $Y$ = 365 дней:

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

Если $\sigma$ превысила бы $S$, приращение индекса заново выводится как $\delta = \lfloor S \cdot 10^{18}/D \rfloor$, и $\sigma$ пересчитывается из него, так что начисления по позициям в сумме никогда не превышают глобального списания. Арифметика насыщается до нуля вместо отката, поэтому точки входа, работающие поверх неё, не могут зависнуть, а в аварийном режиме часы сдвигаются без начисления (L1547). Одна функция вычисляет срез и для исполнения, и для каждой котировки, поэтому они не могут округлять по-разному.

**Разделение.** Срез уходит из стейка и делится на том же шаге (L1560–1580; `_dampPure`, L1623–1625), где $W_{\mathrm{p}}$ = `totalBoostedPure` и $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

так что $\sigma = \kappa_{r} + \eta + (\pi - \eta)$ точно; при отсутствии веса чистой доходности весь $\pi$ уходит в резерв. Урезание то же, что и у эмиссии (E28), с той же константой.

**Начисление.** Глобальная сторона была рассчитана, когда проценты накопились; собственная доля позиции начисляется при следующем обращении к ней (`_accrueInterestFor`, L1627–1671), где $I_{u}$ — её контрольная точка индекса:

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

Проценты выплачиваются из стейка; сам долг не растёт. Позиция без долга лишь заново фиксирует контрольную точку, поэтому никогда не наследует рост индекса за время, когда она ничего не была должна. Проценты, которые позиция не может заплатить, всё равно были распределены по E33; их возврат в $S$ сохраняет $S = \sum_{u} s_{u}$, а убыток проводится по E37 в момент, когда он становится известен.

### §25.5 Резерв, платёжеспособность и каскад убытков

**Сохранение.** Пусть $B$ — баланс BZPX контракта, $D$ = `totalDebt`, $\mathit{BD}$ = `totalBadDebt`, $S$ = `totalStaked`, $\mathit{RR}$ = `rewardReserve`, $\mathit{PR}$ = `protocolReserve` и $\Pi$ = `totalRewardDistributed − totalRewardsPaid` (распределено, но ещё не выплачено); каждая функция с модификатором `conserves` делает снимок обеих сторон до своего тела и после него откатывается, если не выполнено (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

Каждое слагаемое — одна неотрицательная переменная состояния, суммируемая без нижней границы, поэтому тождество линейно в любом режиме — включая терминальное бедствие, когда инструменты восстановления (`repay`, `liquidate`, `absorbBadDebt`) должны продолжать работать, — и сравнивается только изменение за вызов, так что история не может накопиться в ложную тревогу.

**Платёжеспособность.** Что контракт должен и публичная проверка того, держит ли он это (`_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

*Вывод связи с безнадёжным долгом.* При $S + \mathit{RR} + \mathit{PR} \ge D$ E35 даёт $\Delta(B - \mathrm{owed} + \mathit{BD}) = 0$ (с точностью до $\varepsilon$) на каждом защищённом вызове, поэтому $B - \mathrm{owed} = c - \mathit{BD}$, где $c$ — баланс сверх учёта. Следовательно, записанный безнадёжный долг — это ровно то слагаемое, которое толкает книгу к жёсткому нарушению, и книга становится неплатёжеспособной, как только $\mathit{BD} > c + \varepsilon$. При жёстком нарушении откатываются двери, которые выплатили бы по номиналу или добавили бы новые деньги в недостающий котёл, — `deposit`, `borrow`, `withdraw`, `claimRewards`, `claimPureYield` и расчёт чистой доходности внутри них, `withdrawReserve`, `cancelEmergency` (L760, L826, L902, L932, L948, L1510, L539, L620), — тогда как `repay`, `liquidate` и `absorbBadDebt` остаются открытыми, и любой может вызвать `tripBreaker`, чтобы перевести контракт в аварийный режим (L597).

**Каскад.** Убыток $\lambda$ возникает только в трёх местах: ликвидация, при которой долг превышает стейк (L1101–1106, L1128), аварийный выход позиции «под водой» (L704–709) и проценты, которые позиция не может заплатить (L1657–1663). Все три применяют один и тот же порядок:

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

Резерв — сформированный из резервного коэффициента каждого процентного среза (E33) и из бонусов самоликвидации — поглощает первым; только непокрытый остаток становится записанным безнадёжным долгом. Рекапитализация — публичная дверь, которая сначала берёт резерв, затем не более того, что предлагает вызывающий, и никогда не больше, чем ещё не покрыто (`absorbBadDebt`, L639–649), где $a$ — сумма вызывающего:

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

Она откатывается при $\rho + \gamma = 0$, ничего не выводит наружу и ничего не платит вызывающему; E35 выполняется по построению ($\Delta \mathrm{LHS} = \gamma - \rho - \gamma = -\rho = \Delta \mathrm{RHS}$). Как только $\mathit{BD}$ возвращается в пределы пыли, `isSolvent()` возвращает true, и администратор может отменить аварийный режим.

**Ликвидация.** Позиция ликвидируема тогда и только тогда, когда $d_{u} > 0$ и ($s_{u} = 0$ или $100\,d_{u} \ge 95\,s_{u}$) (`_isLiquidatable`, L1133–1137). Её закрытие (`_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

Бонус получает вызывающий, кроме случая, когда заёмщик закрывает собственную позицию: тогда бонус остаётся в `protocolReserve`. Заработанные позицией потоки выплачиваются до её закрытия, а оставшийся стейк остаётся позицией без долга, сохраняющей свою блокировку.

**Аварийный выход.** При $\mathrm{pot} = B$ и $E_{+}$ = `totalPositiveEquity` $= \sum_{u} \max(s_{u} - d_{u}, 0)$ в отслеживаемом виде выход выплачивает (`emergencyWithdraw`, L658–725; масштабирование, 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

где $e_{u}$ — отслеживаемое слагаемое выходящего, заменённое его точным капиталом после начисления. В платёжеспособной книге масштабирование никогда не включается, и чистый капитал выплачивается полностью; в недостаточной книге каждый выход получает одну и ту же долю своего требования, а выход позиции «под водой» проводит свою недостачу через E37 (L704–709).

![FIG-21 — Каскад убытков E37–E38: protocolReserve поглощает первым, остаток записывается как totalBadDebt и считывается через isSolvent(), а absorbBadDebt рекапитализирует из резерва, затем из средств вызывающего, никогда сверх непокрытого; пропорции условные, без сумм в токенах.](https://blazephoenix.xyz/whitepaper/figs/ru/fig-21.png)

**Потолки заимствования.** Заём $a$ должен удовлетворять обоим условиям (`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

первое — по чистому стейку позиции, второе — по всей книге, удерживаемой ниже излома 80 % из E31.

**Обслуживание.** Каждая пользовательская транзакция, если контракт не на паузе и не в аварийном режиме, также сканирует окно реестра заёмщиков (ликвидируя то, что «под водой») и реестра блокировок (нормализуя истёкшие блокировки), каждое размером (`_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

где $n$ — размер реестра, а $t_{m}$ — время последнего прохода; за транзакцию выполняется не более четырёх нормализаций блокировок (L263). Расписание — чистая функция ончейн-состояния, без кипера и без управления, и его газ ограничен в любом случае.

**Инварианты.** Дизайн добавляет девять инвариантов, от INV-23 до INV-31 (Приложение B): тождество сохранения, потолок эмиссии, порядок убытков, закрытые двери при нарушении, границу рекапитализации, единственного писателя весов, потолки заимствования, ограниченный аварийный выход и правило выдержки депозита.

### §25.6 Усиление в V4

Три слитых изменения сформировали исходный код V4 после дизайна, развёрнутого на Base; каждое описано здесь на уровне дизайна, который оно дало.

- **#22 (`b44e238`, 2026-10-11).** Проценты помечаются по срезам одной функцией, общей для исполнения и каждой котировки (E32), так что предпросмотр и транзакция вычисляют одно и то же списание по ставке, действовавшей на протяжении среза. Канал чистой доходности урезается той же нижней границей веса, что и эмиссия (E33), так что остаточный знаменатель не может захватить поток. Заёмщик, закрывающий собственную позицию, не получает ликвидационного бонуса. Добавлены view-методы риска только для чтения (`healthFactor`, `daysToLiquidation`, `maxBorrowOf`).
- **#26 (`7fc0c2a`, 2026-10-13).** Один порядок убытков для всех трёх мест возникновения убытка: сначала резерв, затем записанный безнадёжный долг (E37); публичная дверь рекапитализации (E38); двери, перемещающие стоимость, закрыты при жёстком нарушении (E36); конструктор, отклоняющий адрес токена без кода; проверка сохранения вынесена в два внутренних хелпера вместо встраивания в каждое защищённое место, при неизменном тождестве; и набор тестов гарантии.
- **#25 (`cc614f9`, 2026-10-14).** Только документация: короткие сводные строки в руководствах и заголовках тестов. Исходный код контракта не изменился.

### §25.7 Тестовые свидетельства

Подсчитано на `origin/main` на `cc614f9` по дереву исходников, а не по прогону: 37 файлов `*.t.sol` (`git ls-tree -r --name-only origin/main | grep -c '\.t\.sol$'`), в которых объявлено 256 функций `test*` и 14 функций `invariant*` (`git grep -E '^\s*function (test|invariant)' origin/main -- '*.t.sol'`, с разбиением по префиксу). README репозитория (строка 84) указывает рядом с ними Node-обвязку из девяти наборов. Это подсчёт объявленного; эта работа не сообщает долю пройденных тестов для набора стейкинга.

---

## История изменений

### Версия 2.3.0 (2026-10-14)

2.3.0 — добавляет главу о дизайне Staking V4 (§25) с уравнениями E27–E42, инвариантами INV-23–INV-31 и рисунками FIG-19–FIG-21; она отменяет приведённое ниже утверждение о том, что механизм стейкинга в этой работе больше не описывается. В остальном меняются только метки версий, диапазоны нумерации, вводные части приложений и строки DOI; всё прочее — текст 2.3.

### Версия 2.3 (2026-09-24)

Версия 2.3 продолжает линию 2.2; нумерация 3.0, недолго использовавшаяся для черновика этого текста, отозвана, как до неё и нумерация 3.0 от 2026-09-01. Что изменилось по сравнению с 2.2: восемнадцать инноваций названы, пронумерованы от I-1 до I-18 и изложены вместе со своей математикой в начале работы, с табло измеренных результатов; каждое число заново измерено по `main` на `8916545`; механизм стейкинга здесь больше не описывается (его несёт собственная спецификация); раздел о токене фиксирует единый пул Airdrop & Bounties в 130,000,000 BZPX; а исправления к 2.2, приведённые ниже, внесены в тот текст, который они исправляли.

### Исправления к 2.2 (2026-09-07)

Внесены после враждебного прочтения текста от 2026-09-06 по `main 8949a9d`; строка версии осталась 2.2. Каждый пункт называет, что говорил текст и что показывает рабочая копия.

1. §4.2 — граничный зажим `sqrtBoundary` определён в Core, но ещё не подключён ни к ранжированию, ни к обещанию внутри фрейма; текст утверждал, что он применяется на стороне обещания. Открытый пункт: подключить его (`Router._v4LegQuote`, ветвь ранжирования в Core) с тестом red-first, который доставляет меньше чистой котировки сверх буфера E21, когда нога V4 выходит из своего диапазона.
2. §10.4 — ограничение по ёмкости не распространяется на ноги V4 (Solver пропускает чтение баланса для видов 4 и 8); текст утверждал, что ограничивается каждая концентрированная нога. Открытый пункт: физическая граница для синглтона.
3. §5.4 — статический ключ комиссии V4 игнорирует протокольную комиссию менеджера (нулевую во всех обслуживаемых сегодня сетях); текст называл комиссию ключа однозначной.
4. §9.3 — запас вытеснения опускается примерно до 25 % (действующий 51, новичок 64), а не до 3 %; прежнее число нарушало E13 и было скопировано из комментария в исходном коде.
5. §15 и аннотация — 43 функции `invariant_*` (приведённая команда возвращает 43), 217 файлов `.t.sol`.
6. §14.1 — `HubE(6)` не существует; исчерпание фабрики — это `HubE(4)`, исчерпание слотов — `_canInsert`.
7. §14.2 — утверждение «достаточно любого одного слоя» отозвано: слои ограничивают изменение дельт; остаток от переопределения комиссии из §5.4 ограничен нижней границей.
8. §11.4, INV-21 — правило формы маршрута — канонический порядок по всему маршруту (`sawHooked`), а не «ноги с хуком только в последнем хопе».
9. §11.3, E24 — допуск агрегатной нижней границы хопа делится на BPS: 20 % от средней аттестованной ноги.
10. §17.3 и Приложение C — обвязка `FactoryCensus` находится в приватном дереве развёртывания; таблица приводится как отчёт и не воспроизводится из этой рабочей копии.
11. §17.5 — эталон по газу назван (KyberSwap `MetaAggregationRouterV2`, форк Base, 2026-08-22, Paraswap как второй ориентир), а отношение на транзакцию (2.7×) напечатано рядом с отношением на ногу; обвязка и записанные calldata приватны, поэтому числа приводятся как отчёт и не воспроизводятся из этой рабочей копии.
12. §17.2 — утверждение «первая в сети агрегация с маршрутизацией через V4» отозвано; «точно» заменено измеренной полосой (0–1 bps).
13. INV-8, INV-10, INV-15, INV-20, INV-22 — ссылки на механизмы обеспечения исправлены на тесты и коды, которые их несут; INV-22 называет четыре развёрнутых контракта.
14. §1 — обзор агрегаторов ограничен семью названными, по состоянию на 2026-09-07.
15. Литература 16 и 20 — авторы EIP-1153 — A. Akhunov и M. Salem; EIP-7702 — V. Buterin, S. Wilson, A. Dietrichs и M. Garnett.
16. §2.3, §15.11, §21 — подсчёты калибровки инструментов и шаблонов дефектов заменены самим правилом калибровки.
- §14.2, слой 2 (2026-09-07, допуск хуков): список разрешённых курирует автоматический вход, а привязка к codehash на любом входе приостанавливает внесённый в список хук, чей код изменился. Хук, в битах адреса которого нет разрешения на своп, никогда не выполняется в свопе и не нуждается ни в списке, ни в привязке. Хук, который выполняется в свопе и который никто не внёс в список, принимается на явном входе как подписанный выбор вызывающего и ограничен, как и любая площадка, обещанием внутри фрейма, шлюзом аттестации и `userMinOut`; автоматический вход никогда его не предлагает. Регистрация пула с хуком допускает и привязывает его хук, так что пул с хуком — это один шаг оператора. Три слоя остаются в силе; «закрыто по умолчанию» описывает автоматический вход.

### Исправления к 2.2 (2026-09-24)

Внесены после девятой волны баунти; строка версии осталась 2.2.

1. §4.2 и пункт 1 исправлений от 2026-09-07 — закрыто. Пул V4 котируется обходом его собственной книги: цены, ликвидности, битовой карты тиков и самих тиков, прочитанных через `extsload`, с собственной пошаговой арифметикой PoolManager — одинаково для ранжирования и для обещания (`Core.v4WalkOut`). Граничный зажим `sqrtBoundary` выведен из употребления. Утверждение, что диспетчер не воспроизводит арифметику пересечения тиков, теперь верно только для семейства V3.
2. §10.4 и пункт 2 исправлений от 2026-09-07 — закрыто для той цели, которой он служил: обход учитывает только ликвидность, которую книга держит в пересекаемых тиках, поэтому нога V4 ограничена тем, что держит её собственный пул (`test_AOneTickRangeWithNothingBeyondPaysWhatItHolds`).
3. Пункт 3 исправлений от 2026-09-07 — закрыто: статический ключ комиссии объединяет протокольную комиссию менеджера с LP-комиссией ключа в направлении свопа (`Core.effV4Fee`; `test_StaticKey_ProtocolFee_ReachesThePromise`).
4. §4 — каждое чтение пула на пути котировки ограничено по газу и копирует фиксированное число слов; два чтения, которые были высокоуровневыми вызовами (`getAmountOut` пары Solidly, а также `factory()` и затем `getFee`), выполняются через `Core._askWord` (`test/APoolsAnswerIsBoundedByTheReader.t.sol`).

## Приложение A · Базовые уравнения

Каждое количественное утверждение этой работы сводится к одному из следующих уравнений. Каждое реализовано один раз, в названном контракте, и импортируется во все остальные места. Нумерация E1–E22 сохранена с предыдущего издания; E23–E26 новые и добавлены в конец. E27–E42 (2.3.0) относятся к механизму стейкинга и определены в `src/BlazePhoenixStaking.sol` репозитория стейкинга на `cc614f9` (§25); «Staking» в четвёртом столбце обозначает этот контракт вместе с номерами его строк.

| # | Название | Уравнение | Где определено | § |
|---|---|---|---|---|
| E1 | Мета-уравнение | $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)$ | конвейер Solver | §3 |
| E2 | Выход постоянного произведения | $y = \lfloor x(\mathrm{BPS}-f)\,r_{\mathrm{out}} / (r_{\mathrm{in}}\,\mathrm{BPS} + x(\mathrm{BPS}-f)) \rfloor$ | Core `outV2` | §4.2 |
| E3 | Выход концентрированной ликвидности (внутри тика) | $\sqrt{P}' = L\sqrt{P}/(L + x_{f}\sqrt{P}/Q_{96})$, $y = L(\sqrt{P}-\sqrt{P}')/Q_{96}$; двойственная форма для обратного направления | Core `outV3` | §4.2 |
| E4 | Базовый слот хранилища V4 | $\mathrm{slot} = \mathrm{keccak256}(\mathrm{abi.encode}(\mathrm{poolId}, 6))$; $\sqrt{P}$ в битах [0,160), LP-комиссия в [208,232) слова 0; $L$ в слове 3 | Core `v4SqrtAndLiq` | §5 |
| E5 | Стабильный инвариант Solidly | $k(x,y) = x^{3}y + xy^{3}$ | Core `_solK` | §6 |
| E6 | Шаг Ньютона Solidly | начальное значение $y_{0} = Y$; шаг вверх $\max(1, \lfloor (K-k)/f' \rfloor)$; шаг вниз $\max(1, \lfloor (k-K)/f' \rfloor)$, заменяется на $\lfloor y/2 \rfloor$, если пересёк бы ноль; выход при $\lvert y_{n+1}-y_{n}\rvert \le 1$ со сдвигом на одну единицу против тейкера; предел 64 → начальное значение → котировка 0 | Core `_solY` | §6 |
| E7 | Нормализация десятичных знаков | $X = r_{\mathrm{in}}10^{18-d_{\mathrm{in}}}$, $Y = r_{\mathrm{out}}10^{18-d_{\mathrm{out}}}$, обратное масштабирование после решения | Core `_solidlyStable` | §6 |
| E8 | Вывод CREATE2 | $\mathrm{pool} = \mathrm{addr}_{20}(\mathrm{keccak256}(\mathtt{0xff} \Vert \mathrm{origin} \Vert \mathrm{salt} \Vert \mathrm{initCodeHash}))$ | Core `create2Address`, `deriveAddress` | §7 |
| E9 | Упаковка Моно-слота | $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 | Поле жизнеспособности | $\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 | Затухание активности | $V = \mathrm{swapCount} \gg \lfloor (t - t_{\mathrm{last}})/24{,}576 \rfloor$; 0, как только сдвиг превышает 31 | Core `_decayedSwapCount` | §9.2 |
| E12 | Корзина глубины | $b = \min(15, \lfloor \log_{10}(d/10^{15}) \rfloor)$ | Core `depthBucket` | §9.2 |
| E13 | Порог вытеснения | $2^{b(d_{\mathrm{new}})} > \Psi_{\mathrm{worst}} + \lfloor \Psi_{\mathrm{worst}}/4 \rfloor$ | Hub `_canInsert` | §9.3 |
| E14 | Шлюз разбиения | разбиение сохраняется тогда и только тогда, когда $\mathrm{out}_{\mathrm{split}} \ge \mathrm{out}_{\mathrm{single}}\cdot(1 + 25/10^{6})$, $\mathrm{out}_{\mathrm{single}}$ — лучший из самого глубокого кандидата и кандидата с лучшим курсом при полном размере | Solver `_buildHop` | §10.4 |
| E15 | Нижняя граница Iron-Law | $\mathtt{floorBps} = \max(9{,}600 - \mathtt{legShv} - \min(\delta, 10^{4}) - \sigma_{\ln}/10^{14},\ 8{,}000)$ | Core `ironFloorBpsShv` | §11.5 |
| E16 | Эффективный минимум | $\mathtt{effMin} = \max(\mathtt{userMinOut},\ \mathtt{route.singleOutFloor},\ \lceil \mathtt{finalHopQuote}\cdot\mathtt{floorBps}/10^{4}\rceil)$; при обнаружении комиссии при переводе (fee-on-transfer) второй член отбрасывается, а третий перемасштабируется измеренным чистым отношением | Router `_execute` | §11.5 |
| E17 | Протокольная комиссия | $\mathrm{fee} = \lceil 28 \cdot \mathrm{base} / 10^{4} \rceil$, база = измеренный вход комиссионного хопа (якорный режим), валовой выход (прямой маршрут в мостовую монету) или измеренный вход каждого хопа (режим исчерпания); $t_{1} = \lfloor 3{,}000\cdot\mathrm{fee}/10^{4} \rfloor$, $t_{2} = \mathrm{fee} - t_{1}$ | Router `_chargeHopFee`, `_payFee` | §13 |
| E18 | Шлюз хуков | $(\mathrm{uint160}(\mathrm{hook}) \,\&\, \mathtt{0x3FFF}) \,\&\, ((1\ll 3)\mid(1\ll 2)) \ne 0 \Rightarrow$ нога отклонена, вызов не выполняется | Core `hookAltersDeltas`; Router | §14.2 |
| E19 | Шлюз динамической комиссии | $f_{\mathrm{eff}} = f_{\mathrm{key}}$, если комиссия статическая; котировка невозможна, если сентинел сочетается с ненулевой протокольной комиссией; иначе — текущая LP-комиссия из `slot0` / `globalState` / `fee()` пула | Core `effV4Fee`, `quoteV3Fee` | §5.4 |
| E20 | Потолок комиссии V2 | $f_{\mathrm{eff}} = f_{\mathrm{decl}}$, если $0 < f_{\mathrm{decl}} \le 100$ bps, иначе 30 | Core `effV2Fee`; то же правило для объявленной резервной комиссии Solidly | §4.2 |
| E21 | Защитный буфер предпросмотра | $s = \min(\max(0, n-2) + 5a,\ 10)$ bps | Quoter `_pack` | §12.1 |
| E22 | Выбор поверхности | передавать маршрут в calldata тогда и только тогда, когда $p_{\mathrm{byte}} < \rho^{*}$; $\rho^{*}$ измерен как 66.9–105.5 gas/byte в зависимости от формы маршрута | `test/EquationBench.t.sol` | §17.6 |
| E23 | Срез ноги от концентрации | $\mathtt{legShv} = \sum_{\mathrm{hops}} \lfloor 200\,((\sum a)^{2} - \sum a^{2})/\sum a^{2} \rfloor$ | Core `legShaveBps` | §11.5 |
| E24 | Агрегатная нижняя граница хопа | $\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 | Центр правдоподобия | взвешенная по глубине медиана: отсортировать $(r_{i}, d_{i})$ по $r_{i}$ и вернуть первое $r_{i}$, при котором $\sum_{j \le i} d_{j} \ge \lceil \sum d / 2 \rceil$; полоса $[0.95, 1.05] \times$ центр | Solver `_depthWeightedMedian` | §10.3 |
| E26 | Предикат режима комиссии | $\mathrm{feeHop} = \min\{h : \mathrm{isBridge}(\mathrm{hops}[h].\mathrm{tokenIn})\}$; взимать на $h$ тогда и только тогда, когда $\neg\mathrm{feeOnOut} \wedge (\mathrm{feeHop} = \infty \vee h = \mathrm{feeHop})$; $\mathrm{feeOnOut} \iff$ один хоп и $\mathrm{isBridge}(\mathrm{tokenOut})$ | Router `_execute` | §13.1 |
| E27 | Замкнутая форма эмиссии | $E(t) = (T - (T \gg p)) + (R_{0} \gg p)(t - s - pP)$, $p = \lfloor (t - s)/P \rfloor$; $0$ до $s$; $T - (T \gg 8)$ начиная с $s + 8P$ | Staking `_emittedAt` (L1458–1467) | §25.3 |
| E28 | Окно эмиссии | $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 | Расчёт | $\mathrm{due}_{u} = (\lfloor w_{u}A/10^{18} \rfloor - c_{u})^{+}$, затем $c_{u} \leftarrow \lfloor w_{u}A/10^{18} \rfloor$ | Staking `_settlePendingRewards`, `_settlePendingPureYield` (L1487–1517) | §25.3 |
| E30 | Бонус блокировки и веса | $b(\ell) = 10^{4} + \lfloor 750\ell/365 \rfloor + \lfloor 250\ell^{2}/365^{2} \rfloor$ bps, $\ell$ — действующая блокировка; $w_{u} = \lfloor (s_{u} - d_{u})^{+} b/10^{4} \rfloor$; $w^{\mathrm{p}}_{u} = \lfloor s_{u} b/10^{4} \rfloor$ тогда и только тогда, когда $d_{u} = 0$ | Staking `boostByDays`, `_computeBoost` (L496–505, L1695–1700) | §25.3 |
| E31 | Кривая процентов | $r = 100 + \lfloor 500U/10^{18} \rfloor$ при $U \le 0.8 \cdot 10^{18}$, иначе $500 + \lfloor 72{,}500(U - 0.8 \cdot 10^{18})/10^{18} \rfloor$ bps в год | Staking `_interestRate` (L1768–1782) | §25.4 |
| E32 | Индекс процентов и срез | $\delta = \lfloor \lfloor 10^{18} r/10^{4} \rfloor \Delta t / Y \rfloor$, $\sigma = \lfloor D\delta/10^{18} \rfloor$; если $\sigma > S$, $\delta = \lfloor S \cdot 10^{18}/D \rfloor$, а $\sigma$ пересчитывается | Staking `_interestSlice`, `_updateInterestIndex` (L1539–1619) | §25.4 |
| E33 | Разделение среза | $\kappa_{r} = \lfloor 300\sigma/10^{4} \rfloor$ в резерв; $\eta = \lfloor (\sigma - \kappa_{r})\min(W_{\mathrm{p}}, W_{\min})/W_{\min} \rfloor$ чистым стейкерам; остаток в резерв | Staking `_updateInterestIndex`, `_dampPure` (L1560–1580, L1623–1625) | §25.4 |
| E34 | Начисление процентов | $i_{u} = \lfloor d_{u}(I - I_{u})/10^{18} \rfloor$, списывается с $s_{u}$; любое превышение над $s_{u}$ — убыток для E37 | Staking `_accrueInterestFor` (L1627–1671) | §25.4 |
| E35 | Страж сохранения | $\lvert \Delta(B + D + \mathit{BD}) - \Delta(S + \mathit{RR} + \mathit{PR} + \Pi) \rvert \le 10^{10}$ | Staking `conserves` (L419–457) | §25.5 |
| E36 | Обязательства и платёжеспособность | $\mathrm{owed} = (S + \mathit{RR} + \mathit{PR} - D)^{+} + \Pi$; жёсткое нарушение тогда и только тогда, когда $B + 10^{10} < \mathrm{owed}$ | Staking `_owed`, `_hardBreach`, `isSolvent` (L1794–1824) | §25.5 |
| E37 | Каскад убытков | $\kappa = \min(\lambda, \mathit{PR})$; $\mathit{PR} \mathrel{-}= \kappa$; $\mathit{BD} \mathrel{+}= \lambda - \kappa$ | Staking L1101–1106, L704–709, L1657–1663 | §25.5 |
| E38 | Рекапитализация | $\rho = \min(\mathit{BD}, \mathit{PR})$, $\gamma = \min(a, \mathit{BD} - \rho)$; $\mathit{BD} \mathrel{-}= \rho + \gamma$ | Staking `absorbBadDebt` (L639–649) | §25.5 |
| E39 | Ликвидация | ликвидируема тогда и только тогда, когда $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})$; убыток $(d_{u} - s_{u})^{+}$ | Staking `_executeLiquidation`, `_isLiquidatable` (L1079–1137) | §25.5 |
| E40 | Аварийное урезание | $x_{0} = (s_{u} - d_{u})^{+}$, $E' = E_{+} - e_{u} + x_{0}$; $x = \lfloor x_{0}\,\mathrm{pot}/E' \rfloor$, если $\mathrm{pot} < E'$, иначе $x_{0}$; $x \le \mathrm{pot}$ | Staking `emergencyWithdraw` (L689–696) | §25.5 |
| E41 | Потолки заимствования | $d_{u} + a \le \lfloor 50(s_{u} - d_{u})^{+}/100 \rfloor$ и $\lfloor (D + a) 10^{18}/S \rfloor \le 0.75 \cdot 10^{18}$ | Staking `borrow`, `_ltvCap` (L834–840, L1908–1909) | §25.5 |
| E42 | Бюджет обслуживания | $c(n) = \min(1 + \lfloor n/50 \rfloor + \lfloor (t - t_{m})/900\ \mathrm{s} \rfloor,\ 10,\ n)$ на реестр | Staking `_windowBudget` (L1148–1157) | §25.5 |

## Приложение B · Инварианты

Это свойства, которые выполняются в каждом достижимом состоянии: каждое обеспечивается формой кода в названном месте, и транзакция, которая нарушила бы одно из них, не исполняется. INV-1–INV-18 сохраняют номера из предыдущего издания, а столбец обеспечения приведён в соответствие с кодом; INV-19–INV-22 новые. INV-23–INV-31 (2.3.0) относятся к механизму стейкинга (§25) и обеспечиваются в `src/BlazePhoenixStaking.sol` репозитория стейкинга на `cc614f9`.

| # | Инвариант | Обеспечение |
|---|---|---|
| INV-1 | Router не держит средств между транзакциями; его баланс в покое равен нулю. | Очистки и выплата в пределах базовой линии (§11); `invariant_RouterHoldsNothing`, `HoldsNothingBeyondTheSeed` |
| INV-2 | Каждый своп атомарен: не существует состояния, в котором рассчитана лишь часть сделки. | Семантика транзакций EVM; один фрейм |
| INV-3 | Никакого оракула: ни один внешний ценовой фид не служит входом для допуска, упорядочивания, разбиения, нижних границ или комиссий. | Ни в одном из пяти контрактов нет вызова фида |
| INV-4 | У ставки комиссии, распределения комиссии и констант нижней границы нет сеттеров в развёрнутом байткоде. | Константы времени компиляции в Core и Router (§13) |
| INV-5 | Поля маршрута, переданные вызывающим, могут ужесточить защиту, но никогда не ослабить её. | Тройной максимум E16; шлюз покрытия поднимает аттестацию, но никогда не понижает её (§11) |
| INV-6 | Своп с нулевым минимумом не исполняется. | `RouterE(10)` на каждом входе |
| INV-7 | Ψ ранжирует и усекает кандидатов; ни котировка, ни нижняя граница, ни распределение его не читают. | Поток данных: ничто после ранжирования не потребляет Ψ (§9.6) |
| INV-8 | Реестр пары содержит не более шестнадцати пулов, и вытеснение сохраняет наиболее приспособленные по полному Ψ с запасом E13. | Hub `_canInsert`; `invariant_neverExceedsMaxSlots` (предел); `test_RecordSwap_EvictsWeakestWhenFullAndNewcomerClearsMargin` (вытеснение) |
| INV-9 | Ни один адрес без runtime-байткода не попадает в маршрут. | `hasCode` при каждом выводе (§7) |
| INV-10 | Универсальный колбэк платит только пулу, зафиксированному для текущей ноги, только пока нога выполняется, и никогда больше записанного бюджета ноги. | Транзиентный контекст ноги; `RouterE(6)` для пула, `RouterE(8)` для бюджета (§11.4) |
| INV-11 | Хук V4, чей адрес объявляет разрешения на изменение дельт, отклоняется до движения какого-либо токена и без его вызова. | E18; `RouterE(9)`; Solver `_topKPools` |
| INV-12 | Ни одно ценовое произведение не может переполниться: каждое опасное умножение раскладывается через 512-битный `mulDiv`, а состояние, которое невозможно ограничить, даёт нулевую котировку. | Core (§4.2, §6) |
| INV-13 | Округление работает против завышенного обещания: котировки и выплаты округляются вниз, защитные нижние границы и комиссия — вверх. | Core `mulDiv` / `mulDivUp` в каждом месте |
| INV-14 | Где границу невозможно доказать, вычисление возвращает ноль и от площадки отказываются — никогда не догадка, никогда не управляемый извне revert внутри котировки. | Позиция fail-closed во всём Core; `_solKFits` (§6) |
| INV-15 | База комиссии — измеренное списание, ограниченное зафиксированным входом маршрута, и никогда не объявленный выход; `route.totalOut` ничем не читается. | `_chargeHopFee`; `test_FeeBase_IgnoresLiedAboutTotalOut` |
| INV-16 | Отказ от контроля односторонний: флаг записывается в `true` в одном месте и в `false` — ни в одном; после него остаётся только пополняемый уровень куратора, а переехавшую фабрику или хук нельзя вновь активировать. | `renounceControl` в Hub и Router (§14.7) |
| INV-17 | Объявленной резервной комиссии пула постоянного произведения или Solidly верят только в пределах потолка 100 bps; за его пределами пул оценивается по канонической комиссии 30 bps. | E20 |
| INV-18 | Ни один хоп не несёт больше четырёх ног и ни один маршрут — больше одиннадцати в плане Solver; Router принимает не более трёх хопов и пяти ног на хоп. | Бюджеты Solver; Router `_execute` → `RouterE(3)` |
| INV-19 | Пул попадает в реестр, только если доказывает, что торгует парой: `token0()`/`token1()` для видов в форме пары, повторный вывод id пула для V4; запрошенный пул доказывает то же, прежде чем обнаружение включит его в список. | Hub `recordSwap`, `_probe`; `invariant_ActiveEntriesTradeThePair` |
| INV-20 | Расчёт платит протокольную комиссию ровно один раз на якорном маршруте и по одному разу на хоп на маршруте исчерпания; расчёта, который ничего не заплатил, не существует. | Журнал комиссий, `RouterE(15)`/`RouterE(16)`; `invariant_SettledSwapEmitsExactlyOneFee` (якорный режим); `test/FeeSeals.t.sol`, `test/ExhaustionRegimePreviewParity.t.sol` (режим исчерпания) |
| INV-21 | Хопы образуют цепочку — вход каждого хопа есть выход предыдущего, — и ни одна нога без хука не исполняется после ноги с хуком ни в этом, ни в любом последующем хопе (`sawHooked` действует на весь маршрут). | Router `_execute` → `RouterE(3)`; `sawHooked` |
| INV-22 | Четыре развёрнутых контракта — Hub, Solver, Router, Quoter — укладываются в проектный порог 24,000 байт на релизном профиле, библиотека Core измеряется отдельно (6,477 байт из 24,576), и набор тестов выполняется на этом профиле. | `DeployedSizeGate`; `profile_parity.py` |
| INV-23 | В каждой точке входа стейкинга, помеченной `conserves`, баланс плюс долг плюс учтённый безнадёжный долг меняются на ту же величину, что и стейк плюс оба резерва плюс распределённые, но не выплаченные награды, с точностью до 1e10 wei (E35). | Staking `conserves`, L419–457 → `Staking__InvariantBreached` |
| INV-24 | Запланированная эмиссия никогда не превышает `TOTAL_REWARDS − (TOTAL_REWARDS >> 8)`; совокупное пополнение никогда не превышает `TOTAL_REWARDS`; ни одно окно эмиссии не выплачивает больше, чем содержит `rewardReserve`. | `_emittedAt` L1458–1467; `fundEmission` L518 → `Staking__CapExceeded`; `_updateGlobal` L1442 |
| INV-25 | Каждый реализованный убыток — недостача при ликвидации, аварийный выход «под водой», несобираемые проценты — сначала покрывается из `protocolReserve`; только непокрытый остаток добавляется к `totalBadDebt` (E37). | Staking L1101–1106, L704–709, L1657–1663 |
| INV-26 | Пока баланс плюс 1e10 меньше `owed`, не исполняется ни депозит, ни заимствование, ни вывод, ни получение наград или чистой доходности, ни вывод резерва, ни аварийная отмена (E36). | Проверки `_hardBreach` в L760, L826, L902, L932, L948, L1510, L539, L620 → `Staking__InvariantBreached` |
| INV-27 | `absorbBadDebt` не выводит BZPX, ничего не платит вызывающему и уменьшает `totalBadDebt` не более чем на его текущее значение: сначала резерв, а сумма вызывающего ограничена непокрытым остатком (E38). | `absorbBadDebt` L639–649 |
| INV-28 | Итоги веса эмиссии, веса чистой доходности и положительного капитала записываются только функцией `_applyBoost`, которая заменяет учтённый член позиции пересчитанным; блокировка приносит бонус, только пока она действует (E30). | `_applyBoost` L1726–1734; `_effectiveLockDays` L1687–1690 |
| INV-29 | Заимствование исполняется, только если новый долг не превышает половины чистого стейка позиции до него, а использование книги после него не превышает 75 % (E41). | `borrow` L834 → `Staking__LTVExceeded`; L838–840 → `Staking__UtilTooHigh` |
| INV-30 | Аварийный выход выплачивает не более чистого капитала позиции и не более баланса контракта, с масштабированием на pot/E′, когда баланс меньше учтённого капитала (E40). | `emergencyWithdraw` L689–696 |
| INV-31 | Ни вывод, ни получение чистой доходности, ни изменение блокировки не исполняются в пределах `MIN_DEPOSIT_BLOCKS` = 10 блоков после последнего депозита позиции. | L894, L947, L971 → `Staking__FlashLoanProtection` |

## Приложение C · Команды воспроизведения

Каждое число этой работы, взятое из репозитория, воспроизводится одной из следующих команд из чистой рабочей копии `Blaze-Phoenix-Dex` на `8949a9d` с установленными Foundry и Python 3; наборам форк-тестов нужен ключ архивного RPC в `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
```

Хеши Разобранного примера 4 воспроизводятся любой реализацией keccak-256 над `abi.encode(currency0, currency1, fee, tickSpacing, hooks)`; числа в работе получены чистой реализацией Keccak-f[1600] на Python, проверенной на тестовых векторах пустой строки и `"abc"`, а также на селекторе `extsload(bytes32)`, жёстко закодированном в Core.

## Глоссарий

**Операторы.**

| Символ | Название | Одной строкой |
|---|---|---|
| Ω | Восьмеричный диспетчер | оценивает каждую ногу — замкнутой формой там, где замкнутая форма точна, и запросом к площадке там, где собственный байткод площадки — единственный честный источник; «восьмеричный» — по истории, сегодня действуют шесть видов |
| 𝒟 | Детерминированный вывод | вычисляет адреса пулов арифметикой CREATE2, где развёртывание воспроизводимо, запрашивает и затем доказывает пару, где нет, выводит и доказывает id пулов для синглтона V4; `hasCode` отбрасывает всё, у чего нет байткода |
| Φ | Нижняя граница Iron-Law | собственная нижняя граница удержания протокола: базово 96 %, ослабляется с измеренным влиянием на цену и измеренной концентрацией, жёстко ограничена снизу 80 %, применяется к котировке последнего хопа внутри фрейма |
| Ψ | Поле жизнеспособности | качество площадки, вычисленное только из Моно-слота; ранжирует и усекает множество кандидатов — может скрыть площадку, но никогда не оценит её неверно |
| Ξ | антимошеннический проектор | удаляет из максимизации любой маршрут, затрагивающий хук, изменяющий дельты, или недопустимый хук, — до ранжирования и без вызова хука |

**Термины.** **Якорный режим** — комиссия взимается один раз, на первой мостовой монете, которую держит Router (или на выходе прямого маршрута в мостовую монету). **Полоса правдоподобия** — ±5 % вокруг взвешенной по глубине медианы предельных курсов кандидатов; не действует на множестве из одного кандидата. **Шлюз покрытия** — аттестованная граница ноги поднимается до измеренной котировки, когда аттестация покрывает меньше её половины. **Exact Pass** — точный по пулу предпросмотр Quoter: собственный своп площадки выполняется и откатывается с извлечением результата из revert; за вычетом комиссии с 2026-09-03. **Режим исчерпания** — ни одна мостовая монета не служит входом какого-либо хопа: каждый хоп платит 28 bps со своего измеренного входа; иммунен к префиксу без ценности по построению. **Журнал комиссий** — транзиентный счётчик выплат комиссии за расчёт, отвергающий ноль выплат и отвергающий две выплаты на якорном маршруте. **Метрологический дизайн** — строить контракт как измерительный прибор: свести разнообразие к координатам, измерить каждую координату, которую можно измерить, остальное сделать монотонным. **Мета-уравнение** — весь агрегатор как одна максимизация, произведение, так что единственный несостоятельный множитель обнуляет маршрут. **Моно-слот (Monoslot)** — всё маршрутизационное состояние пула в одном 256-битном слове, без цены внутри. **Доктрина единого колбэка** — один `fallback` отвечает на колбэк flash-учёта каждой площадки в форме V3, с тройной защитой. **Дверь в одну сторону** — отказ от контроля как байткод: уровень контроля умирает, пополняемый уровень куратора остаётся. **Доказательство пары** — пул доказывает свои `token0`/`token1` (или свой повторно выведенный id), прежде чем реестр его сохранит или обнаружение включит в список. **Проекционное расстояние** — насколько далеко наблюдаемый объект отказа отстоит от объекта, который принимает решение. **Самовосстанавливающийся реестр** — протокол узнаёт ликвидность, торгуя ею: шестнадцать слотов на пару, вытеснение по рангу приспособленности, затухание арифметикой. **Правило излишка** — доля протокола составляет 28 bps от одной измеренной базы и ничего, что растёт с разрывом между котировкой и доставкой. **Третий путь** — любой исход сценария, который не является ни расчётом в пределах нижних границ без остатка на Router, ни отказом с нашим селектором.

## Литература

1. G. Angeris, A. Evans, T. Chitra, S. Boyd. *Optimal Routing for Constant Function Market Makers.* arXiv:2204.05238, 2022 (ACM EC '22). — Маршрутизация через CFMM как выпуклая программа; оптимум, до которого не дотягивает пропорциональный распределитель из §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). — Алгоритм двойственной декомпозиции для той же задачи (§23).
3. H. Adams, N. Zinsmeister, D. Robinson. *Uniswap v2 Core.* 2020. https://uniswap.org/whitepaper.pdf — Эталон постоянного произведения и комиссия 30 bps по умолчанию из §4.2.
4. H. Adams, N. Zinsmeister, M. Salem, R. Keefer, D. Robinson. *Uniswap v3 Core.* 2021. https://uniswap.org/whitepaper-v3.pdf — Концентрированная ликвидность, `slot0`, фиксированная точка Q64.96 (§4.2, §15.6).
5. Uniswap Labs (H. Adams et al.). *Uniswap v4 Core.* 2024. https://uniswap.org/whitepaper-v4.pdf — Синглтон PoolManager, flash-учёт и кодирование разрешений хуков в адресе (§5, §14.2).
6. M. Egorov. *StableSwap — Efficient Mechanism for Stablecoin Liquidity.* 2019. https://curve.fi/files/stableswap-paper.pdf — Усиленный инвариант семейства, исключённого в §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. — Противник, управляющий порядком, которого §14.3 ограничивает, а не претендует устранить.
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. — Сэндвич-атаки измерены и формализованы (§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. — Рамка для замкнутой формы в пределах одного тика как корректной абстракции (§23).
10. M. R. Clarkson, F. B. Schneider. *Hyperproperties.* Journal of Computer Security 18(6), 2010. DOI 10.3233/JCS-2009-0393. — Свойства множеств трасс; класс, к которому относятся находки о перераспределении (§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. — Второй судья из §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. — Мутационный анализ и гипотеза связанности (§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. — Предок §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. — Покрывающие массивы и покрытие t-кратных взаимодействий (§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. — Почему вероятность корректности не указывается (§15.11).
16. A. Akhunov, M. Salem. *EIP-1153: Transient Storage Opcodes.* https://eips.ethereum.org/EIPS/eip-1153 — Контекст ноги, журнал комиссий и блокировка повторного входа (§11.4, §13.2).
17. V. Buterin. *EIP-170: Contract Code Size Limit.* https://eips.ethereum.org/EIPS/eip-170 — Стена в 24,576 байт (§16.3).
18. V. Buterin. *EIP-1014: Skinny CREATE2.* https://eips.ethereum.org/EIPS/eip-1014 — Адресная арифметика 𝒟 (§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 — Вход 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 — Делегированные аккаунты вызывают те же входы (§11.1).
21. *ERC-7201: Namespaced Storage Layout.* https://eips.ethereum.org/EIPS/eip-7201 — Пространство имён хранилища Hub (§16.1).
22. MariaDB Corporation Ab. *Business Source License 1.1.* https://mariadb.com/bsl11/ — Лицензия кода.
23. Fable & Mitra. *BlazePhoenix-Dex: an on-chain DEX aggregator with measured routing.* Repository, `main` 8949a9d, 2026-09-05. https://github.com/blazephoenixxyz-crypto/Blaze-Phoenix-Dex — Канонический источник для каждого утверждения этой работы; Приложение C воспроизводит из него числа.
24. S. Müller, N. Moumeni, A. Messaoudi. *Cost of Manipulation in AMM-Based Oracles.* arXiv:2606.03548, 2026 (Financial Cryptography 2026, DeFi workshop). — Граница взвешенной по ликвидности медианы, лежащая в основе E25 (§10.3, §23).
25. *Ormer: A Manipulation-resistant and Gas-efficient Blockchain Pricing Oracle for DeFi.* arXiv:2410.07893. — Ончейн-оракул, оценивающий медиану (§23).
26. *From Means to Medians: Optimal Benchmark Design.* arXiv:2506.22142. — Какими эталонами труднее всего манипулировать (§23).
27. *Optimal Routing across Constant Function Market Makers with Gas Fees.* arXiv:2603.02844. — Маршрутизация с учётом газа (§23).
28. *Routing Frictions and Executable Liquidity in Fragmented Markets.* arXiv:2609.19013. — Заявленная и исполнимая ликвидность (§23).

## Как цитировать

Mitra. *BlazePhoenix Technical Whitepaper: an on-chain liquidity aggregator whose quote and execution are one computation.* Версия 2.3.0, 2026-10-14. Zenodo. DOI [10.5281/zenodo.23084091](https://doi.org/10.5281/zenodo.23084091) (депонированная v2.3). Все версии: [10.5281/zenodo.23084090](https://doi.org/10.5281/zenodo.23084090). Концептуальный DOI первой серии депозитов (v2.2): [10.5281/zenodo.22526574](https://doi.org/10.5281/zenodo.22526574). BibTeX и другие форматы: https://blazephoenix.xyz/research/whitepaper
