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

Инвариант

CPMM поддерживает классический инвариант константного произведения на двух своих хранилищах: xy=kx \cdot y = k где x — это баланс vault0 после всех комиссий передачи Token-2022 при получении, и аналогично для y. Каждый свап должен оставить k' ≥ k после учёта торговых комиссий, зачисленных LP (протокол, фонд и комиссии создателя не учитываются при расчёте k — они находятся в хранилище, но исключаются из представления кривой, см. Комиссии на кривой ниже). Таким образом, k монотонно растёт с течением времени по мере начисления комиссий LP. Цена LP-доли определяется резервами пула, а не k: цена LP в token0=xlpSupply,цена LP в token1=ylpSupply\text{цена LP в token0} = \frac{x}{\text{lpSupply}}, \qquad \text{цена LP в token1} = \frac{y}{\text{lpSupply}} Сжигание ΔLP LP-токенов возвращает ровно ΔLP × x / lpSupply token0 и ΔLP × y / lpSupply token1. При депозите или выводе ни кривая, ни k не движутся — цена меняется только при свопах.

Модель комиссий на пути свопа

CPMM применяет две независимо установленные комиссии на каждом свопе:
  • Торговая комиссия берётся на входе с коэффициентом AmmConfig.trade_fee_rate. Затем она разделяется на доли LP, протокола и фонда (доля LP остаётся в хранилище и увеличивает k; доли протокола и фонда извлекаются из учёта хранилища).
  • Комиссия создателя (активна только когда enable_creator_fee == true) берётся с коэффициентом AmmConfig.creator_fee_rate. Она берётся на входе или выходе в зависимости от PoolState.creator_fee_on и направления свопа (см. /ru/products/cpmm/fees#с-какой-стороны-торговли-берутся-комиссии). Это отдельная корзина — никогда не доля торговой комиссии.
Пусть:
  • FEE_RATE_DENOMINATOR = 1_000_000
  • trade_fee_rate — из AmmConfig, например 2500 = 0,25% от соответствующего объёма
  • creator_fee_rate — из AmmConfig, например 1000 = 0,10% от соответствующего объёма
  • protocol_fee_rate, fund_fee_rate — в единицах 1/FEE_RATE_DENOMINATOR торговой комиссии, а не объёма
Когда комиссия создателя берётся на входе:
Когда комиссия создателя берётся на выходе:
В обоих случаях торговая комиссия разделяется одинаково:
Сумма protocol_fee + fund_fee + creator_fee хранится в хранилищах, но отслеживается отдельно в состоянии пула (protocol_fees_token*, fund_fees_token*, creator_fees_token*). Когда проверка инварианта константного произведения проверяет k' ≥ k, она использует балансы хранилища минус все три накопленные, но неизвлечённые комиссии — таким образом LP захватывают только lp_fee. См. /ru/products/cpmm/fees для инструкций по сбору и выполненных численных примеров.

SwapBaseInput (вход точен)

“Пользователь дает нам ровно amount_in входного токена и получает как минимум minimum_amount_out выходного токена.” Пока игнорируем Token-2022:
По алгебре: amount_out=yΔxnetx+Δxnet\text{amount\_out} = \frac{y \cdot \Delta x_{\text{net}}}{x + \Delta x_{\text{net}}} где Δx_net = amount_in_after_trade_fee. Затем программа обновляет учет хранилища так, чтобы часть trade_fee, полагающаяся протоколу/фонду/создателю, находилась в корзинах “накопленных” комиссий (не включаются в следующее x кривой), тогда как доля LP присоединяется к x для следующего свопа.

Token-2022 на входе

Если входной токен имеет расширение transfer-fee, монета вычитает свою комиссию при передаче от пользователя → хранилище. Поэтому хранилище фактически получает amount_in − transfer_fee_in(amount_in). Программа CPMM поэтому вычисляет:
и запускает кривую против amount_in_after_trade_fee. Это важно, потому что цена кривой вычисляется исходя из чистой суммы, поступившей в хранилище, а не из объявленной суммы пользователя.

Token-2022 на выходе

Если выходной токен имеет комиссию передачи, пул отправляет amount_out из своего хранилища пользователю. Монета затем снимает свою комиссию в пути, поэтому пользователь получает amount_out − transfer_fee_out(amount_out). Программа вычисляет amount_out из кривой как обычно, но задача интегратора — преобразовать число “отправка из хранилища” пула в число “получение пользователем” при показе котировок.

Проверка скольжения

После вычисления amount_out:
Если выходной токен берёт комиссию передачи, SDK применяет комиссию передачи перед установкой minimum_amount_out, так чтобы константа скольжения была номинирована в том, что пользователь фактически получит, а не в том, что отправит хранилище.

SwapBaseOutput (выход точен)

“Пользователь получит ровно amount_out выходного токена и готов заплатить до maximum_amount_in входного токена.” Инвертируя кривую для Δx_net: Δxnet=xamount_outyamount_out\Delta x_{\text{net}} = \left\lceil \frac{x \cdot \text{amount\_out}}{y - \text{amount\_out}} \right\rceil Потолок важен — он гарантирует k' ≥ k после целочисленного усечения. Затем:
На Token-2022 входе оборачиваем:
так чтобы пользователь заплатил достаточно, чтобы после вычета комиссии передачи монеты пул получил gross_needed.

Проверка скольжения

Выполненный пример

Состояние пула, игнорируя Token-2022:
  • x = 1_000_000_000_000 (1 000 000,000000 token0, 6 знаков)
  • y = 2_000_000_000_000 (2 000 000,000000 token1, 6 знаков)
  • AmmConfig: trade_fee_rate = 2500, protocol_fee_rate = 120_000, fund_fee_rate = 40_000, creator_fee_rate = 0
Пользователь: SwapBaseInput с amount_in = 1_000_000_000 (1 000,000000 token0). Комиссия создателя отключена (enable_creator_fee = false).
Если бы у того же пула было enable_creator_fee = true с creator_fee_rate = 1000 (0,10%) на входе, программа взимала бы total_input_fee = ceil(1_000_000_000 * 3500 / 1_000_000) = 3_500_000, затем разделила бы это как creator_fee = 1_000_000 и trade_fee = 2_500_000. Арифметика протокол/фонд/LP на trade_fee не изменяется по сравнению с примером выше — комиссия создателя — это отдельная корзина, накопленная в creator_fees_token0 и исключённая из curve_x вместе с корзинами протокола и фонда. Если входной токен имеет комиссию передачи Token-2022 в размере 1%, хранилище получает 990_000_000 токенов вместо 1_000_000_000, и все последующие вычисления используют эту чистую сумму.

Правило обновления наблюдений

При каждом свопе программа оценивает, нужно ли добавить новое наблюдение в кольцевой буфер:
Два свойства:
  • Кумулятивная цена, не спотовая цена. Одно наблюдение — это не цена. Чтобы получить TWAP за период от t0 до t1, прочитайте наблюдения, ближайшие к каждому концу, и вычислите (cumulative(t1) − cumulative(t0)) / (t1 − t0).
  • Образцы ограничены по частоте. Последовательные свопы в одном слоте могут делить одно наблюдение. Чтение наблюдения сразу после свопа может поэтому выглядеть устаревшим на один слот — это нормально.
Подробнее в /ru/products/clmm/accounts.

Комиссии на кривой

Это тонкая часть, стоящая того, чтобы её выделить. Арифметика кривой работает с чистыми балансами хранилища — то есть с опубликованным балансом SPL минус накопленные комиссии протокола, фонда и создателя (все три — независимые корзины — см. /ru/products/cpmm/fees). Конкретная картина:
Последствия для интеграторов:
  • Не котируйте из сырых балансов. Сначала вычтите поля накопленных комиссий, или вызовите SwapBaseInput как имитацию и возьмите его результат.
  • CollectProtocolFee перемещает токены из хранилища. После сбора raw_vault_balance падает, но curve_balance остаётся неизменным; цена пула не движется. Это намеренно.

Точность и переполнение

  • Вся арифметика кривой использует промежуточные значения u128 для предотвращения переполнения при x * y.
  • Деление округляется к нулю, за исключением SwapBaseOutput’s Δx_net, который округляется вверх, и вычисления комиссии, которое округляется вверх на trade_fee и вниз на подразделения. Эти направления округления выбраны так, чтобы инвариант никогда не уменьшался из-за целочисленного усечения.
  • Пулы с экстремальными соотношениями хранилищ (миллиарды : 1) могут попадать в полы точности при малых торговлях; программа возвращает ZeroTradingTokens в этом случае. См. /ru/reference/error-codes.

Куда дальше

Источники: