Skip to main content
이 페이지는 AI 자동 번역입니다. 모든 내용은 영문판을 기준으로 합니다.영문판 보기 →

불변식

CPMM은 두 개의 볼트에서 고전적인 상수곱 불변식을 유지합니다: x⋅y=kx \cdot y = k 여기서 x는 수령 시 Token-2022 전송 수수료를 제외한 vault0 잔액이고, y도 마찬가지입니다. 모든 스왑은 LP에 적립된 거래 수수료를 고려한 후 k' ≥ k를 유지해야 합니다(프로토콜, 펀드, 크리에이터 버킷은 k에 포함되지 않습니다 — 볼트에 있지만 곡선 보기에서 제외됩니다. 아래의 곡선의 수수료 참조). 따라서 k는 LP가 수수료를 적립함에 따라 시간이 지남에 따라 단조증가합니다. LP 주식은 풀의 준비금으로 가격이 책정되며, k로는 아닙니다: token0의 LP 가격=xlpSupply,token1의 LP 가격=ylpSupply\text{token0의 LP 가격} = \frac{x}{\text{lpSupply}}, \qquad \text{token1의 LP 가격} = \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입니다. 그런 다음 프로그램은 거래 수수료 중 프로토콜/펀드/크리에이터에 해당하는 부분이 “적립된” 버킷에 앉도록 볼트 회계를 업데이트합니다(다음 스왑의 곡선에 포함되지 않음). 동시에 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 (token0 1,000,000.000000, 6 소수 자리)
  • y = 2_000_000_000_000 (token1 2,000,000.000000, 6 소수 자리)
  • AmmConfig: trade_fee_rate = 2500, protocol_fee_rate = 120_000, fund_fee_rate = 40_000, creator_fee_rate = 0
사용자: amount_in = 1_000_000_000 (token0 1,000.000000)으로 SwapBaseInput. 크리에이터 수수료 비활성화(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 전송 수수료가 있으면, 볼트는 1_000_000_000 대신 990_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는 변하지 않습니다. 풀의 가격은 움직이지 않습니다. 이는 의도적입니다.

정밀도 및 오버플로우

  • 모든 곡선 산술은 x * y에서 오버플로우를 방지하기 위해 u128 중간값을 사용합니다.
  • 나눗셈은 0을 향해 반올림합니다. 단, SwapBaseOutput의 Δx_net은 올림하고, 수수료 계산은 trade_fee에서 올림하고 부분 분할에서 내림합니다. 이러한 반올림 방향은 정수 절단으로 인해 불변식이 감소하지 않도록 선택됩니다.
  • 극단적인 볼트 비율(수십억 : 1)을 가진 풀은 작은 거래에서 정밀도 바닥에 도달할 수 있습니다. 프로그램은 이 경우 ZeroTradingTokens을 반환합니다. reference/error-codes 참조.

다음으로 갈 곳

출처: