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

不变量

CPMM 在其两个金库上维护经典的恒定乘积不变量: x⋅y=kx \cdot y = k 其中 x 是 vault0 的余额在收到任何 Token-2022 转账费后,y 亦然。每次交换必须在计入分配给 LP 的交易费后保持 k' ≥ k(协议、基金和创建者桶不计入 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。trade_fee 上的协议/基金/LP 算术与上面的示例相同 — 创建者费是自己的桶,累积到 creator_fees_token0 并与协议和基金桶一起从 curve_x 中排除。 如果输入代币有 1% 的 Token-2022 转账费,金库接收 990_000_000 个代币而不是 1_000_000_000,所有后续计算都使用该净金额。

观察更新规则

在每次交换时,程序评估是否应将新观察推入环形缓冲区:
两个特性:
  • 累积价格,不是现货价格。 单个观察不是价格。要从时间 t0 到 t1 获得 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。

接下来去哪里

来源: