Skip to main content
Bu sayfa yapay zekâ tarafından otomatik olarak çevrilmiştir. İngilizce sürüm esas alınır.İngilizce sürümü görüntüle →
Bu sayfa işlevseldir: CLMM programı tarafından kullanılan formülleri, sabit-nokta kurallarını ve adım-adım işlemleri verir. Yoğunlaştırılmış likidite eğrisinin arkasındaki mantık — neden L = sqrt(x · y) önemli — için bkz. algorithms/clmm-math. Bu sayfa, o sayfayı okuduğunuzu varsayar.

Sqrt-fiyat gösterimi

CLMM, fiyatı sqrt_price_x64 olarak depolar — token1-başına-token0 fiyatının karekökü, Q64.64 sabit-nokta sayısı olarak: sqrt_price_x64=⌊p⋅264⌋\text{sqrt\_price\_x64} = \lfloor \sqrt{p} \cdot 2^{64} \rfloor burada p = token1_amount / token0_amount. sqrt yerine p ile çalışmak swap matematiğini doğrusallaştırır (token-miktar deltaları Δsqrt_price içinde doğrusal hale gelir) ve x64 sabit-nokta, çok-tick swaplar boyunca kesinliği korur. Tick ↔ sqrt-fiyat dönüşümü, bit-by-bit log-yaklaşımı aracılığıyla önceden hesaplanır: sqrt_price_x64(t)≈264⋅(1.0001)t/2\text{sqrt\_price\_x64}(t) \approx 2^{64} \cdot (1.0001)^{t/2} tick_math::get_sqrt_price_at_tick içinde arama tabanlı üstel alma olarak uygulanır.

Likidite kanonik bir birim olarak

[sqrt_a, sqrt_b] aralığında (sqrt_a < sqrt_b ile) likidite L konumu, token miktarlarına aşağıdaki gibi eşlenir. sqrt_c = sqrt_price_x64 havuzun mevcut fiyatı olsun. Üç kimlik de yoğunlaştırılmış likiditenin bir aralık içinde sağladığı x = L / sqrt_p, y = L · sqrt_p değişmezinden gelir. Entegratörler tipik olarak tersi isterler: amount0 / amount1 yatırımı verildiğinde, aralığa sığan maksimum L hesaplayın. SDK’nın LiquidityMath.getLiquidityFromTokenAmounts bunu yapar. Aralık içi durum için formül: L0=amount0⋅sqrt_c⋅sqrt_bsqrt_b−sqrt_c,L1=amount1sqrt_c−sqrt_a,L=min⁡(L0,L1)L_0 = \text{amount0} \cdot \frac{\text{sqrt\_c} \cdot \text{sqrt\_b}}{\text{sqrt\_b} - \text{sqrt\_c}}, \qquad L_1 = \frac{\text{amount1}}{\text{sqrt\_c} - \text{sqrt\_a}}, \qquad L = \min(L_0, L_1) Hangi taraf bağlı olursa, gerçekten tüketilen oranı belirler; diğer taraf artabilir.

Tek-tick swap adımı

Bir swap adımlar halinde ilerler. Her adım ya (a) mevcut tick aralığında tüm mevcut girişi tick geçmeden tüketir, ya da (b) fiyatı tam olarak sonraki başlatılmış tike taşır. Mevcut durum (sqrt_c, L) ve aşağı swap (token0 giriş, token1 çıkış, sqrt_price azalır — fiyat token1/token0 olarak kote edilir, bu nedenle token0 eklemek onu düşürür) verildiğinde, aşağıdaki sonraki başlatılmış tick sqrt_t < sqrt_c konumunda oturur. Bu mikro-aralık içinde giriş ve fiyat arasındaki ilişki: Δamount0=L⋅(1sqrt_t−1sqrt_c)=L⋅(sqrt_c−sqrt_t)sqrt_c⋅sqrt_t\Delta\text{amount0} = L \cdot \left( \frac{1}{\text{sqrt\_t}} - \frac{1}{\text{sqrt\_c}} \right) = \frac{L \cdot (\text{sqrt\_c} - \text{sqrt\_t})}{\text{sqrt\_c} \cdot \text{sqrt\_t}} ve Δamount1=L⋅(sqrt_c−sqrt_t)\Delta\text{amount1} = L \cdot (\text{sqrt\_c} - \text{sqrt\_t}) Bir token1-giriş swapi ayna görüntüsüdür: sqrt_price sonraki üst tike doğru yükselir ve iki formül hangi uç noktanın çıkarıldığını değiştirir. Program yönü zero_for_one olarak kodlar (true giriş mint’i token_mint_0 olduğunda) ve bir zero_for_one swapi MIN_SQRT_PRICE_X64 doğru sınırlar. Program iki şeyden birini yapar:
  • Tüm giriş sığar mı? Kalan giriş (ücret sonrası) sqrt_t ulaşmak için Δamount0 değerinden azsa, yeni sqrt_c' tam olarak çözün: sqrt_c′=L⋅sqrt_cL+Δinput⋅sqrt_c\text{sqrt\_c}' = \frac{L \cdot \text{sqrt\_c}}{L + \Delta\text{input} \cdot \text{sqrt\_c}} (tam-giriş token0 → token1 swapi için). Swap bu adımda tick geçmeden tamamlanır.
  • Giriş Δamount0 aşar mı? sqrt_c' = sqrt_t ayarlayın, tiki geçin (liquidity_net uygulayın), kalan girişi Δamount0 kadar azaltın, çıkışı Δamount1 kadar artırın ve tekrarlayın.
Ters yön için (token1 → token0, fiyat düşüyor), formüllerin sqrt_c ve sqrt_t değiştirilmiş ve ters diğer yuvada. Tam Rust uygulaması raydium-clmm/programs/amm/src/libraries/swap_math.rs içinde yaşar. Oradaki mantık Uniswap v3’ün SwapMath.computeSwapStep ile bire bir eşleşir.

Her adımda ücretler

İşlem ücretleri her adımda giriş miktarından alınır, CPMM ile aynı kural:
LP kısmı, genel ücret-büyüme biriktiricisini güncelleyerek mevcut aralık içi likidite arasında bölünür: fee_growth_globalin+=lp_portion⋅264L\text{fee\_growth\_global}_{\text{in}} \mathrel{+}= \text{lp\_portion} \cdot \frac{2^{64}}{L} — yani, likidite birimi başına ücretler olarak gösterilir, Q64.64, böylece bu swap boyunca aralık içinde kalan L_i boyutundaki bir konum daha sonra L_i · Δfee_growth_global / 2^{64} borçlu token okuyacaktır. Protocol ve fund kısımları sırasıyla PoolState.protocol_fees_token_{0,1} ve PoolState.fund_fees_token_{0,1} birikir, CPMM ile aynı. CollectProtocolFee / CollectFundFee tarafından temizlenir.

Aralık dışında ve içinde ücret büyümesi

CLMM ücret muhasebesi zor kısım: bir konum yalnızca havuzun fiyatı aralığı içinde olduğu sürece ücret kazanır. Havuz kümülatif ücretleri genel olarak izler; konum, kümülatif ücretleri belirli aralığı içinde bilmesi gerekir. Çözüm bir tick tabanlı biriktiricdir. Her tick depolar:
Tick başlatma anında:
  • Havuzun fiyatı bu tik üstünde ise (tick_current >= this_tick), fee_growth_outside = fee_growth_global. (Şimdiye kadar kazanılan her şey bu tike göre “dışında” — yani aşağıda — mevcut fiyata göre.)
  • Aksi takdirde fee_growth_outside = 0.
Fiyat bir tiki geçtiğinde, program o tik’in fee_growth_outside değerini çevirir: fee_growth_outside←fee_growth_global−fee_growth_outside\text{fee\_growth\_outside} \gets \text{fee\_growth\_global} - \text{fee\_growth\_outside} Bu korunan değişmez: herhangi bir tick t için, fee_growth_outside(t) tick_current t nin ters tarafında olduğu sürece tahakkuk eden ücretlere eşittir. [tick_lower, tick_upper] aralığında ücret büyümesi daha sonra türetilir:
Bu Uniswap-v3 ücret-büyüme formülüdür, değişmez.

Bir konum ne depolar ve ne okur

Bir PersonalPositionState fee_growth_inside_0_last_x64 ve fee_growth_inside_1_last_x64 depolar: konumun son dokunuşunun zamanındaki fee_growth_inside değerleri. Sonraki herhangi bir dokunuşta (artırma, azaltma, toplama), program:
  1. Yukarıdaki formülü kullanarak mevcut fee_growth_inside_{0,1}_x64 hesaplar.
  2. Δ = fee_growth_inside_now − fee_growth_inside_last hesaplar (u128 üzerinde modüler-çıkarma).
  3. Δ × position.liquidity / 2^{64} değerini tokens_fees_owed_{0,1} öğesine ekler.
  4. fee_growth_inside_last öğesini yeni değere günceller.
Token’lar aslında kasalardan yalnızca CollectFees / DecreaseLiquidity üzerinde tokens_fees_owed karşılığında çıkar.

Ödüller

Havuzun 3’e kadar ödül akışının her biri, kendi reward_growth_global_x64 biriktiricisinde aynı büyüme-içi mekanikasını kullanır. Emisyon zamanında: reward_growth_global+=emission_per_second⋅Δt⋅264L\text{reward\_growth\_global} \mathrel{+}= \text{emission\_per\_second} \cdot \Delta t \cdot \frac{2^{64}}{L} — emisyonlar aktif likidite ile ters orantılı ölçeklenir, bu nedenle daha yoğun bir havuz her konuma saniye başına orantılı olarak daha az öder, ancak toplamda daha fazla konuma. Konum başına ödül borçlu reward_owed=(reward_growth_insidenow−reward_growth_insidelast)⋅L/264\text{reward\_owed} = (\text{reward\_growth\_inside}_{\text{now}} - \text{reward\_growth\_inside}_{\text{last}}) \cdot L / 2^{64} ve DecreaseLiquidity / DecreaseLiquidityV2 tarafından ödenir — bağımsız bir toplama-ödül talimatı yoktur. Bkz. products/clmm/fees.

Çalışılmış örnek: tam-giriş swapi

Varsayalım:
  • tick_spacing = 60
  • sqrt_price_x64 = 1 × 2^{64} — fiyat = 1.0, bu nedenle tick_current = 0.
  • Aktif likidite L = 1_000_000 × 2^{64}.
  • Aşağıdaki sonraki başlatılmış tick: t = −60 (sqrt_price_b ≈ 0.997005 × 2^{64}). Bir token0-giriş swapi aşağı hareket eder, bu nedenle üst tick burada ilgisizdir.
  • İşlem ücreti oranı: 500 (0.05%).
Kullanıcı: SwapBaseInput tam-giriş 1.000 token0. Adım 1 — ücretler:
Adım 2 — 999 mevcut tick aralığına sığar mı?
999 < 3004.4, bu nedenle tüm giriş tiki geçmeden sığar. Adım 3 — yeni fiyat:
yani sqrt_c' sqrt_c altında iner, bu doğru yöndür: bir token0 → token1 swapi havuza token0 ekler ve bu nedenle token1/token0 fiyatını düşürür. Adım 4 — çıkış miktarı:
Yuvarlama hesabından sonra, kullanıcı ≈ 998 token1 alır. Ücret (1 token0) trade_fee_rate × protocol_fee_rate / 1e6 tarafından LP, protocol ve fund arasında bölünür (fund için de benzer); LP kısmı fee_growth_global_0_x64 içine akar.

Swap sırasında limit-order eşleştirmesi

Bir swap adımı açık limit emirleri tutan bir tiki geçtiğinde, bu emirler swap girişini LP eğrisinden önce tik’in tam fiyatında tüketir. Eşleştirme, tik içinde order_phase kohortuna göre FIFO’dur.

TickState üzerinde kohort başına durum

İki-kohort düzeni, yeni emirlerin bir tik üzerinde açılabileceği için eski bir kohort hala doldurulurken. Yeni açılan emirler orders_amount öğesine katılır ve sonraki order_phase öğesini devralır; önceki kohort tam olarak tüketilene kadar dolduramaz.

Eşleştirme adımı

Swap sırasında her tick geçişinde gerçekleşen eşleştirme için sözde kod:
Limit-order sahiplerine giden çıkış token’ları swap başına aktarılmaz. Emir sahibi SettleLimitOrder (veya DecreaseLimitOrder) çağırana kadar havuzun çıkış kasasında neredeyse oturur. Havuz, unfilled_ratio_x64 aracılığıyla kohortun ne kadarının doldurulduğunu izler. Her LimitOrderState açılış zamanında kendi (order_phase, unfilled_ratio_x64) anlık görüntüsünü depolar, bu nedenle yerleşim şu şekilde azalır:
Bu O(1) yerleşim, kohort tasarımının tüm noktasıdır — bir tik, emir başına gaz olmadan keyfi sayıda emri doldurabilir.

LP eğrisi ile etkileşim

Bir swap adımında, limit-order eşleştirmesi tik konumunda (sıfır Δsqrt_price) gerçekleşir; LP eğrisi tüketimi tiklerin arasında gerçekleşir. Sıra bu nedenle:
  1. Tiki t_cross geçin (LP liquidity_net değişikliğini ilk olarak uygulayın, çünkü Uniswap-V3 bunu yapar).
  2. t_cross konumunda oturan herhangi bir limit emrini doldurun.
  3. LP eğrisi boyunca sonraki başlatılmış tike veya swap_input tükenişine devam edin.
Limit emirleri bu nedenle tüccarları tam olarak emir’in tick fiyatında daha fazla etkili likidite verir (fiyat iyileştirme etkisi), LP’lerin o swap hacminin kısmında ücret kazanmaması pahasına — limit-order swapi kısmı swapçı için ücretsizdir, çünkü limit-order yerleştirici bir yapıcı olarak hareket eder. Dinamik-ücret ek ücreti (etkinse) aynı swapın LP kısmına hala uygulanır.

Dinamik ücret türetmesi

PoolState.dynamic_fee_info volatilite durumunu taşır. Her swap adımı, adım başına ücret oranını şu şekilde hesaplar: fee_ratetotal=trade_fee_rateconfig+dynamic_fee_control⋅(vol_acc⋅tick_spacing)2Dctrl⋅Svol2⏟dinamik ek u¨cret\text{fee\_rate}_{\text{total}} = \text{trade\_fee\_rate}_{\text{config}} + \underbrace{\frac{\text{dynamic\_fee\_control} \cdot (\text{vol\_acc} \cdot \text{tick\_spacing})^2} {D_{\text{ctrl}} \cdot S_{\text{vol}}^2}}_{\text{dinamik ek ücret}} burada:
  • Dctrl=100,000D_{\text{ctrl}} = 100{,}000 — DYNAMIC_FEE_CONTROL_DENOMINATOR
  • Svol=10,000S_{\text{vol}} = 10{,}000 — VOLATILITY_ACCUMULATOR_SCALE
  • vol_acc aşağıdaki güncelleme kuralından sonra swap başına biriktiricdir
  • tick_spacing PoolState.tick_spacing öğesinden gelir
Sonuç 100,000/106=10%100{,}000 / 10^6 = 10\% konumunda sınırlanır.

Biriktiricinin güncellenmesi

İki kural her swap’ta sırayla uygulanır: Bozunma. Referans taban, son güncelleme zamanından bu yana geçen zamana göre bozunur: vol_ref={0if Δt>decay_periodvol_accprev⋅reduction_factor10,000if filter_period<Δt≤decay_periodvol_refprevif Δt≤filter_period\text{vol\_ref} = \begin{cases} 0 & \text{if } \Delta t > \text{decay\_period} \\ \text{vol\_acc}_{\text{prev}} \cdot \dfrac{\text{reduction\_factor}}{10{,}000} & \text{if } \text{filter\_period} < \Delta t \le \text{decay\_period} \\ \text{vol\_ref}_{\text{prev}} & \text{if } \Delta t \le \text{filter\_period} \end{cases} Birikme. Yeni biriktiriciler referans artı önceki referans dizininden bu yana geçilen tick-mesafesidir: vol_acc=min⁡(vol_ref+∣tref−tnow∣⋅Svol,max_vol_acc)\text{vol\_acc} = \min\left( \text{vol\_ref} + \left| t_{\text{ref}} - t_{\text{now}} \right| \cdot S_{\text{vol}}, \text{max\_vol\_acc} \right) tick_spacing_index_reference (treft_{\text{ref}}) ham tiklerde değil, tick-aralığı birimlerindedir: tref=⌊tick_current/tick_spacing⌋t_{\text{ref}} = \lfloor \text{tick\_current} / \text{tick\_spacing} \rfloor.

Neden tick mesafesinde parabolik

Biriktiricinin karesini almak, ücretin fiyatın referans noktasından ne kadar uzağa yürüdüğünün karesi olarak yükseldiği anlamına gelir. Ampirik olarak bu, rastgele yürüyüş basıncı altında fiyatın varyans ölçeklemesiyle eşleşir: 2× tick sapması 4× ima edilen volatilite anlamına gelir, bu nedenle 4× ek ücret alır. dynamic_fee_control parametresi mutlak seviyeyi kalibre eder. filter_period penceresi, küçük alt-saniye salınımlarının (örn. MEV botları sandviç yapması) biriktiricisini şişirmesini engeller. decay_period penceresi, tek bir geçmiş artışın pazar sakinleştikten sonra ücretleri süresiz olarak almasını engeller.

Sayısal sağlamlık

  • Tüm ara ürünler u128 veya u256 şeklindeki aritmetik aracılığıyla gider. CLMM, U128Sqrt yardımcılarını ve FullMath::mulDiv kalıplarını doğrudan Uniswap v3’ten taşır.
  • Bölme yuvarlama, değişmez k' ≥ k yerel olarak uygulamak için adım başına seçilir. SwapBaseInput çıkışı aşağı yuvarlar; SwapBaseOutput girişi yukarı yuvarlar.
  • PoolState.liquidity öğesini sıfıra düşüren tick geçişlerine izin verilir (fiyat bir “likidite deliği” arasında geçebilir) ancak swap basitçe giriş tüketmeden sonraki başlatılmış tike ilerler, ücret almaz.
  • Taşma koruması: sqrt_price_x64 [MIN_TICK, MAX_TICK] öğesine karşılık gelen [MIN_SQRT_PRICE_X64, MAX_SQRT_PRICE_X64] kapsayıcı aralığında tutulur. Her iki sınırı da geçecek bir swap SqrtPriceLimitOverflow ile geri döner.

Sonra nereye gitmeli

Kaynaklar: