Skip to main content
本页内容由 AI 自动翻译,所有内容以英文版本为准。查看英文版 →

不变量

CPMM 在其两个金库上维持经典的常数乘积不变量: xy=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)。它有自己的分桶 —— 从不是交易费的一部分。
设定:
  • FEE_RATE_DENOMINATOR = 1_000_000
  • trade_fee_rate —— 来自 AmmConfig,例如 2500 = 相关交易量的 0.25%
  • creator_fee_rate —— 来自 AmmConfig,例如 1000 = 相关交易量的 0.10%
  • protocol_fee_ratefund_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 程序随后更新金库记账,使得交易费中欠协议/基金/创建者的部分存在”应收”分桶(不包括在下次交换的 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=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 位小数)
  • AmmConfigtrade_fee_rate = 2500protocol_fee_rate = 120_000fund_fee_rate = 40_000creator_fee_rate = 0
用户:SwapBaseInputamount_in = 1_000_000_000(1,000.000000 个 token0)。创建者费禁用(enable_creator_fee = false)。
如果相同的池设定 enable_creator_fee = truecreator_fee_rate = 1000(0.10%)在输入端,程序会收取 total_input_fee = ceil(1_000_000_000 * 3500 / 1_000_000) = 3_500_000,然后分割为 creator_fee = 1_000_000trade_fee = 2_500_000trade_fee 上的协议/基金/LP 计算与上例相同 —— 创建者费有自己的分桶,应收到 creator_fees_token0,并像协议和基金分桶一样从 curve_x 中排除。 如果输入代币有 1% 的 Token-2022 转账费,金库接收 990_000_000 个代币而非 1_000_000_000,所有后续计算都使用这个净金额。

观察更新规则

在每次交换时,程序评估是否推送新的观察到环形缓冲区:
两个特性:
  • 累积价格,而非现货价格。 单个观察不是价格。要从时间 t0t1 获得时间加权平均价格 (TWAP),读取每端最接近的观察并计算 (cumulative(t1) − cumulative(t0)) / (t1 − t0)
  • 采样频率受限。 同一槽内连续的交换可能共享一个观察。在交换后立即读取观察可能看起来延迟一个槽 —— 这是正常的。
更多内容见 products/clmm/accounts

曲线费用

这是微妙的部分,值得特别说明。曲线算术针对金库余额运作 —— 即原始 SPL 余额减去应收协议、基金和创建者费(三者都是独立分桶 —— 详见 products/cpmm/fees)。具体图景:
对集成方的影响:
  • 不要从原始余额报价。 先减去应收费字段,或将 SwapBaseInput 作为模拟调用并取其返回值。
  • CollectProtocolFee 从金库中移动代币。 提取后,raw_vault_balance 下降但 curve_balance 不变;池的价格不动。这是刻意设计的。

精度与溢出

  • 所有曲线算术都使用 u128 中间值以防止 x * y 溢出。
  • 除法向零舍入,除了 SwapBaseOutputΔx_net 向上舍入,以及费用计算向上舍入 trade_fee 并向下舍入子分割。这些舍入方向被选择以确保不变量由于整数截断而不会减少。
  • 金库比例极端的池(十亿:1)在小交换时可能触及精度下限;程序在这种情况下返回 ZeroTradingTokens。详见 reference/error-codes

后续去处

来源: