Перейти к основному содержанию
Эта страница переведена с помощью ИИ. За эталон принимается английская версия.Открыть английскую версию →

Кривая таблицы поиска

Stable AMM заменяет формулу x·y=k на разреженную таблицу поиска кортежей (x, y, price). При определении цены свопа программа:
  1. Вычисляет текущее соотношение пула из резервов.
  2. Выполняет бинарный поиск в таблице, чтобы найти две записи, которые охватывают это соотношение.
  3. Линейно интерполирует между ними, чтобы получить промежуточную цену.
  4. Применяет комиссии и возвращает котировку.
Этот подход обменивает детерминизм формулы на гибкость администратора в формировании цены, и он достаточно эффективен, чтобы уместиться в бюджет вычислений Solana.

Макет таблицы и бинарный поиск

ModelDataInfo содержит до 50 000 записей DataElement, индексируемых администратором. Активны только первые valid_data_count. Каждая запись:
DataElement {
  x: u64,      // X координата (масштабированная сумма на стороне монеты)
  y: u64,      // Y координата (масштабированная сумма на стороне ПК)
  price: u64,  // price = x/y, масштабировано множителем
}
Чтобы найти цену при текущих резервах пула (x_real, y_real):
  1. Вычислите соотношение: target_ratio = (x_real * multiplier) / y_real.
  2. Выполните бинарный поиск записей, где (element.x * multiplier) / element.y охватывает target_ratio.
  3. Когда найден диапазон [min_idx, max_idx], выполните интерполяцию.
Код бинарного поиска программы занимает ~150 строк в state.rs::ModelDataInfo::get_mininum_range_by_xy_real. Ключевой инвариант: записи должны быть отсортированы (x по возрастанию, y по убыванию, price по возрастанию) для работы поиска.

Линейная интерполяция

Когда две точки таблицы охватывают соотношение, интерполяция вычисляет промежуточную цену и пару резервов:
target = (x_real * multiplier) / y_real

[x1, y1, p1] = table[min_idx]
[x2, y2, p2] = table[max_idx]

// Интерполяция цены
p = p1 + (p2 - p1) * (target - ratio1) / (ratio2 - ratio1)

// Интерполяция резерва
x = x1 + (x2 - x1) * (target - ratio1) / (ratio2 - ratio1)
y = y1 + (y2 - y1) * (target - ratio1) / (ratio2 - ratio1)
Результат — кусочно-линейная кривая, которая плавно соединяет точки таблицы.

Масштабирование: множитель

Резервы пула и цены хранятся в разных масштабах. Поле multiplier на ModelDataInfo это учитывает. Типичный паттерн:
  • Монета имеет 6 десятичных знаков, ПК имеет 18 десятичных знаков.
  • Множитель = 10^6 (или аналогично).
  • Записи таблицы хранятся в уменьшенном масштабе, чтобы соответствовать границам u64.
Программа перемасштабирует при чтении/записи через:
real_value = table_value * ratio / multiplier
table_value = real_value * multiplier / ratio

Определение цены свопа: SwapBaseIn и SwapBaseOut

SwapBaseIn (точный ввод)

Дан входной объём amount_in:
  1. Получите текущее соотношение из (coin_vault, pc_vault).
  2. Найдите охватывающие записи таблицы и интерполируйте, чтобы получить соотношение в пространстве таблицы.
  3. Преобразуйте ввод в пространство таблицы: dx_table = amount_in * multiplier / ratio.
  4. Запросите таблицу по новой X-координате, чтобы найти новую Y.
  5. dy_table = y_old - y_new.
  6. Преобразуйте обратно: dy_real = dy_table * ratio / multiplier.
  7. Примените торговую комиссию: dy_output = dy_real - (dy_real * trade_fee_numerator / trade_fee_denominator).
  8. Верните dy_output.

SwapBaseOut (точный вывод)

Симметрично: дан желаемый amount_out, решите для требуемого amount_in. Оба пути читают эффективные резервы непосредственно из хранилищ пула. Пул не держал открытые ордера OpenBook годами, поэтому нечего расчищать в первую очередь — балансы хранилища — это вся история. (Обновление 2026-06-22 удалило оставшийся код маркета.)

Применение комиссий

Идентично AMM v4: см. products/amm-v4/math для полного вывода.
gross_fee = amount_in * (swap_fee_numerator / swap_fee_denominator)    // например, 0.25%
lp_portion = gross_fee - (gross_fee * pnl_numerator / pnl_denominator) // например, 0.22%
pnl_portion = gross_fee * (pnl_numerator / pnl_denominator)            // например, 0.03%
pnl_portion идёт в need_take_pnl_* и выводится администратором через WithdrawPnl. lp_portion остаётся в хранилище, увеличивая k и принося пользу держателям токенов LP.

Учёт активов пула

Формула исторически добавляла средства, которые пул держал как открытые ордера в своём счёте OpenOrders OpenBook. Этот термин был нулевым на практике с тех пор, как пул перестал размещать ордера, и обновление 2026-06-22 полностью удалило его из формулы, оставив только расчёт на основе хранилища:
Старое: total assets = vault balances + open-order funds (native_coin_total / native_pc_total) − pending PnL (need_take_pnl)
Новое: total assets = vault balances − pending PnL (need_take_pnl)
Это значение, которое математика кривой рассматривает как эффективные резервы (накопленная, но не выведенная часть need_take_pnl физически находится в хранилище, но исключена из определения цены). Код котировок и индексаторы, которые ранее читали балансы OpenOrders, должны отбросить этот термин.

MonitorStep (удалено)

MonitorStep была инструкцией крана, которая расчищала ожидающие заполнения OpenBook, пересчитывала AmmInfo.target_orders и переразмещала сетку лимитных ордеров, полученную из таблицы поиска. Пул перестал размещать ордера в OpenBook годами назад, поэтому кран больше не имел ничего делать; он был удалён в обновлении 2026-06-22. Интеграторам не нужно запускать кран для пулов Stable.

Резюме: почему это работает

Дизайн таблицы поиска + интерполяция эффективен и гибок:
  • Эффективность: Бинарный поиск — это O(log 50,000) ≈ 16 итераций, каждая ~ 300–500 CU. Интерполяция — это несколько умножений/делений. Общая стоимость котировки ~ 5k–15k CU, намного дешевле, чем пересчёт формулы при каждом свопе.
  • Гибкость: Администратор может закодировать любую кусочно-линейную кривую. Пары стейблкойнов получают высокую плотность около 1:1; обеспеченные пары получают пользовательские кривые.
  • Самодостаточная ликвидность: Все средства находятся в хранилищах пула, и определение цены читает их напрямую — без крана, без внешней книги ордеров, меньше счётов за транзакцию.
Для глубокого погружения в логику интерполяции см. raydium-stable/program/src/state.rs, методы get_data_by_x, get_data_by_y, get_dy_by_dx_base_in и т. д.

Что дальше

  • Accounts — справочник полей ModelDataInfo и DataElement.
  • Instructions — вызываемый набор (swap, deposit, withdraw, WithdrawPnl) и удалённые инструкции.
  • Fees — применение комиссий и WithdrawPnl.
  • products/amm-v4/math — для логики определения цены ордеров OpenBook с учётом комиссий.
Источники:
  • raydium-stable/program/src/state.rs (реализации интерполяции и бинарного поиска)
  • raydium-stable/program/src/math.rs (утилиты калькулятора)