이 페이지는 AI 자동 번역입니다. 모든 내용은 영문판을 기준으로 합니다.영문판 보기 →
이 페이지는 실제 운영 중인 내용입니다. CLMM 프로그램이 사용하는 공식, 고정소수점 규칙, 단계별 절차를 제시합니다. 집중된 유동성 곡선 자체의 배경 —
L = sqrt(x · y)가 중요한 이유 — 에 대해서는 algorithms/clmm-math를 참고하세요. 이 페이지는 해당 내용을 읽었다고 가정합니다.Sqrt-price 표현
CLMM은 가격을sqrt_price_x64로 저장합니다. 이는 token1-per-token0 가격의 제곱근을 Q64.64 고정소수점 수로 나타낸 것입니다:
여기서 p = token1_amount / token0_amount입니다. p 대신 sqrt를 사용하면 스왑 수학이 선형화되고 (토큰 수량 변화가 Δsqrt_price에 대해 선형), x64 고정소수점은 많은 틱을 거치는 스왑에서도 정밀도를 유지합니다.
틱 ↔ sqrt-price 변환은 bit-by-bit 로그 근사를 통해 미리 계산됩니다:
이는 tick_math::get_sqrt_price_at_tick에서 조회 기반 지수 계산으로 구현됩니다.
유동성을 정규 단위로
범위[sqrt_a, sqrt_b] 내에서 (sqrt_a < sqrt_b), 유동성 L 포지션은 다음과 같이 토큰 수량에 매핑됩니다. sqrt_c = sqrt_price_x64를 풀의 현재 가격이라 하겠습니다.
세 항등식 모두 집중된 유동성이 범위 내에서 만족하는 불변식
x = L / sqrt_p, y = L · sqrt_p에서 나옵니다.
통합자는 보통 역을 원합니다: amount0 / amount1 예치가 주어졌을 때, 범위에 맞는 최대 L을 계산합니다. SDK의 LiquidityMath.getLiquidityFromTokenAmounts가 이를 수행합니다. 범위 내 경우의 공식:
어느 쪽이 제약이 되는지에 따라 실제로 소비되는 비율이 결정되고, 다른 쪽은 남은 부분이 있을 수 있습니다.
단일 틱 스왑 스텝
스왑은 스텝으로 진행됩니다. 각 스텝은 (a) 현재 틱 범위 내에서 사용 가능한 모든 입력을 소비하되 틱을 넘지 않거나, (b) 가격을 다음 초기화된 틱으로 정확히 이동합니다. 현재 상태(sqrt_c, L)과 하향 스왑 (token0 입력, token1 출력, sqrt_price 감소 — 가격은 token1/token0으로 표시되므로 token0을 추가하면 낮아짐)이 주어졌을 때, 아래의 다음 초기화된 틱은 sqrt_t < sqrt_c에 있습니다. 이 미세 구간 내에서 입력과 가격 사이의 관계는:
그리고
token1 입력 스왑은 거울상입니다: sqrt_price는 위의 다음 틱을 향해 상승하고, 두 공식은 어느 끝점을 빼는지 바뀝니다. 프로그램은 방향을 zero_for_one으로 인코딩합니다 (입력 민트가 token_mint_0일 때 true), 그리고 zero_for_one 스왑은 MIN_SQRT_PRICE_X64를 향해 제한됩니다.
프로그램은 다음 두 가지 중 하나를 수행합니다:
-
전체 입력이 맞습니까? 남은 입력 (수수료 후)이
sqrt_t에 도달하는Δamount0보다 작으면, 새로운sqrt_c'을 정확히 풉니다: (정확한 입력token0 → token1스왑의 경우). 스왑은 이 스텝에서 틱을 넘지 않고 완료됩니다. -
입력이
Δamount0을 초과합니까?sqrt_c' = sqrt_t로 설정하고, 틱을 넘으며 (liquidity_net적용), 남은 입력을Δamount0만큼 감소시키고, 출력을Δamount1만큼 증가시킨 후 반복합니다.
token1 → token0, 가격 하향)의 경우, 공식은 sqrt_c와 sqrt_t가 바뀌고 반전이 다른 슬롯에 있습니다.
전체 Rust 구현은 raydium-clmm/programs/amm/src/libraries/swap_math.rs에 있습니다. 그 로직은 Uniswap v3의 SwapMath.computeSwapStep과 일대일로 일치합니다.
각 스텝의 수수료
거래 수수료는 각 스텝의 입력 수량에서 차감되며, CPMM과 동일한 규칙입니다:L_i의 포지션은 나중에 L_i · Δfee_growth_global / 2^{64} 토큰을 읽을 수 있습니다.
프로토콜 및 펀드 부분은 각각 PoolState.protocol_fees_token_{0,1}과 PoolState.fund_fees_token_{0,1}에 누적되며, CPMM과 동일합니다. 이들은 CollectProtocolFee / CollectFundFee로 수거됩니다.
외부 및 내부 수수료 성장
CLMM 수수료 회계의 까다로운 부분: 포지션은 풀의 가격이 범위 내에 있을 때만 수수료를 얻습니다. 풀은 누적 수수료를 전역적으로 추적하고, 포지션은 특정 범위 내의 누적 수수료를 알아야 합니다. 해결책은 틱 기반 누적기입니다. 각 틱은 다음을 저장합니다:- 풀의 가격이 이 틱 위에 있으면 (
tick_current >= this_tick),fee_growth_outside = fee_growth_global입니다. (지금까지 얻은 모든 것은 현재 가격을 기준으로 이 틱 “아래”인 “외부”입니다.) - 그렇지 않으면
fee_growth_outside = 0입니다.
fee_growth_outside를 뒤집습니다:
이것이 유지하는 불변식: 모든 틱 t에 대해, fee_growth_outside(t)는 tick_current가 t의 반대편에 있는 동안 누적된 수수료와 같습니다.
범위 [tick_lower, tick_upper] 내의 수수료 성장은 다음과 같이 도출됩니다:
포지션이 저장하는 것과 읽는 것
PersonalPositionState는 fee_growth_inside_0_last_x64와 fee_growth_inside_1_last_x64를 저장합니다: 포지션이 마지막으로 터치된 시점의 fee_growth_inside 값입니다.
이후의 모든 터치 (증가, 감소, 수거)에서 프로그램은:
- 위의 공식을 사용하여 현재
fee_growth_inside_{0,1}_x64를 계산합니다. Δ = fee_growth_inside_now − fee_growth_inside_last(u128에서 모듈로 뺄셈)를 계산합니다.Δ × position.liquidity / 2^{64}를tokens_fees_owed_{0,1}에 추가합니다.fee_growth_inside_last를 새 값으로 업데이트합니다.
CollectFees / DecreaseLiquidity에서만 볼트에서 나가며, tokens_fees_owed에 대해 수행됩니다.
보상
풀의 최대 3개 보상 스트림 각각은 자체reward_growth_global_x64 누적기에서 동일한 성장 내부 메커니즘을 사용합니다. 방출 시간에:
— 방출은 활성 유동성에 반비례하므로, 더 밀집된 풀은 각 포지션에 초당 비례적으로 더 적게 지급하지만, 총 더 많은 포지션에 지급합니다. 포지션당 보상 채무는
이며 DecreaseLiquidity / DecreaseLiquidityV2로 지급됩니다 — 독립형 수거 보상 명령은 없습니다. products/clmm/fees를 참고하세요.
작업 예제: 정확한 입력 스왑
다음을 가정합시다:tick_spacing = 60sqrt_price_x64 = 1 × 2^{64}— 가격 = 1.0, 따라서tick_current = 0.- 활성 유동성
L = 1_000_000 × 2^{64}. - 아래의 다음 초기화된 틱:
t = −60(sqrt_price_b ≈0.997005 × 2^{64}). token0 입력 스왑은 하향으로 이동하므로 위의 틱은 여기서 무관합니다. - 거래 수수료율: 500 (0.05%).
SwapBaseInput 정확한 입력 1,000 token0.
스텝 1 — 수수료:
999 < 3004.4이므로 전체 입력이 틱을 넘지 않고 맞습니다.
스텝 3 — 새로운 가격:
sqrt_c'는 sqrt_c 아래에 착지하며, 이는 올바른 방향입니다: token0 → token1 스왑은 풀에 token0을 추가하므로 token1/token0 가격을 낮춥니다.
스텝 4 — 출력 수량:
trade_fee_rate × protocol_fee_rate / 1e6 (펀드도 유사)로 LP, 프로토콜, 펀드 간에 분할되고, LP 부분은 fee_growth_global_0_x64로 흐릅니다.
스왑 중 리미트 오더 매칭
스왑 스텝이 열린 리미트 오더를 보유한 틱을 넘을 때, 이러한 오더는 LP 곡선 이전에 틱의 정확한 가격에서 스왑 입력을 소비합니다. 매칭은 틱 내에서order_phase 코호트별로 FIFO입니다.
TickState의 코호트별 상태
orders_amount에 참여하고 다음 order_phase를 상속합니다. 이전 코호트가 완전히 소비될 때까지 채울 수 없습니다.
매칭 스텝
스왑 중 각 틱 교차에서 발생하는 매칭의 의사 코드:SettleLimitOrder (또는 DecreaseLimitOrder)를 호출할 때까지 풀의 출력 볼트에 가상으로 앉아 있습니다. 풀은 단순히 unfilled_ratio_x64를 통해 코호트가 얼마나 채워졌는지 추적합니다. 각 LimitOrderState는 열린 시간에 자체 (order_phase, unfilled_ratio_x64) 스냅샷을 저장하므로 정산은 다음과 같이 축소됩니다:
LP 곡선과의 상호작용
스왑 스텝에서, 리미트 오더 매칭은 틱 에서 발생합니다 (0Δsqrt_price); LP 곡선 소비는 틱 사이에서 발생합니다. 따라서 순서는:
- 틱
t_cross교차 (LPliquidity_net변경을 먼저 적용합니다. Uniswap-V3이 이렇게 하기 때문입니다). t_cross에 앉아 있는 리미트 오더를 채웁니다.- LP 곡선을 따라 다음 초기화된 틱 또는
swap_input소진까지 계속합니다.
동적 수수료 도출
PoolState.dynamic_fee_info는 변동성 상태를 전달합니다. 각 스왑 스텝은 단계별 수수료율을 다음과 같이 계산합니다:
여기서:
- —
DYNAMIC_FEE_CONTROL_DENOMINATOR - —
VOLATILITY_ACCUMULATOR_SCALE vol_acc는 아래 업데이트 규칙 후의 스왑당 누적기tick_spacing은PoolState.tick_spacing에서 나옵니다
누적기 업데이트
두 규칙이 각 스왑에서 순서대로 적용됩니다: 감소. 참조 바닥은 마지막 업데이트 이후 시간에 따라 감소합니다: 누적. 새로운 누적기는 참조에 이전 참조 인덱스 이후 순회한 틱 거리를 더한 것입니다:tick_spacing_index_reference ()는 원시 틱이 아닌 틱 간격 단위입니다: .
틱 거리에서 포물선인 이유
누적기를 제곱하면 수수료가 가격이 참조점에서 걸어간 거리의 제곱으로 상승합니다. 경험적으로 이는 무작위 보행 압력 하에서 가격의 분산 스케일링과 일치합니다: 2배 틱 편차는 4배의 내재 변동성을 의미하므로 4배의 할증을 청구합니다.dynamic_fee_control 매개변수는 절대 수준을 보정합니다.
filter_period 윈도우는 작은 서브초 진동 (예: MEV 봇 샌드위칭)이 누적기를 부풀리는 것을 방지합니다. decay_period 윈도우는 단일 과거 스파이크가 시장이 진정된 후 무한정 수수료를 청구하는 것을 방지합니다.
수치 견고성
- 모든 중간 곱은
u128또는u256형태의 산술을 거칩니다. CLMM은U128Sqrt헬퍼와FullMath::mulDiv패턴을 Uniswap v3에서 직접 이식하여 사용합니다. - 나눗셈 반올림은 불변식
k' ≥ k를 로컬에서 적용하기 위해 스텝별로 선택됩니다.SwapBaseInput은 출력을 내림합니다.SwapBaseOutput은 입력을 올림합니다. PoolState.liquidity를 0으로 떨어뜨리는 틱 교차는 허용됩니다 (가격은 “유동성 구멍”을 순회할 수 있음) 하지만 스왑은 단순히 다음 초기화된 틱으로 진행하며 입력을 소비하지 않고 수수료를 청구하지 않습니다.- 오버플로우 가드:
sqrt_price_x64는 포함 범위[MIN_SQRT_PRICE_X64, MAX_SQRT_PRICE_X64]에 유지되며, 이는[MIN_TICK, MAX_TICK]에 해당합니다. 어느 경계를 넘을 스왑은SqrtPriceLimitOverflow로 되돌립니다.
다음으로 갈 곳
products/clmm/ticks-and-positions틱 맵이 보행에 어떻게 참여하는지.products/clmm/fees수수료/보상 측면의 수학을 자세히.algorithms/clmm-mathL = sqrt(x · y)뒤의 도출과 범위 대 유동성 공식.
raydium-io/raydium-clmm—libraries/swap_math.rs,libraries/tick_math.rs- “Uniswap v3 Core” 백서, §6–7

