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

Инвариант

CPMM поддерживает классический инвариант постоянного произведения на двух хранилищах: x⋅y=kx \cdot y = k где x — баланс хранилища 0 после любых комиссий передачи 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 и направления своп (см. products/cpmm/fees). Это отдельный бакет — никогда не часть торговой комиссии.
Доля протокола в комиссии создателя не появляется нигде на этой странице, и это намеренно. Она применяется, когда CollectCreatorFee перемещает накопленные комиссии между двумя счётчиками, которые кривая уже исключает — не во время своп. Математика своп, котировки и k идентичны с ней и без неё. См. 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. См. 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 на входной стороне

Если входной токен имеет расширение комиссии передачи, токен вычитает свою комиссию при передаче от пользователя → хранилище. Таким образом, хранилище фактически получает 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=⌈x⋅amount_outy−amount_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).
  • Образцы ограничены по скорости. Последовательные своп в одном слоте могут использовать одно наблюдение. Чтение наблюдения сразу после своп может поэтому выглядеть устаревшим на один слот — это нормально.
Подробнее в products/clmm/accounts.

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

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

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

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

Куда дальше

Источники: