Zum Hauptinhalt springen

Documentation Index

Fetch the complete documentation index at: https://docs.raydium.io/llms.txt

Use this file to discover all available pages before exploring further.

Diese Seite wurde mit KI automatisch übersetzt. Maßgeblich ist stets die englische Version.Englische Version ansehen →

Warum sqrt-price statt price

CLMMs der Uniswap-v3-Familie stellen den Preis als Quadratwurzel dar, gespeichert im Fixed-Point-Format Q64.64:
sqrt_price_x64 = floor(sqrt(price) · 2^64)
Es gibt drei Gründe dafür:
  1. Lineare Liquiditätsmathematik. Die Menge von Token0 oder Token1 in einer Preisrange ist eine lineare Funktion von sqrt_price, nicht von price. Das Speichern von sqrt_price ermöglicht es dem Swap-Schritt, diese linearen Formeln auszuwerten, ohne eine Quadratwurzel zu berechnen.
  2. Überflutungsschutz. sqrt_price · L passt für alle sinnvollen Parameter in u256; price · L kann viel früher überfluten.
  3. Einheitliche Tick-Arithmetik. Da Ticks als 1.0001^i definiert sind, ist sqrt(price) = 1.00005^i auch eine exakte Potenz-von-1.00005-Leiter. Jeder Tick-Übergang entspricht einer kleinen Multiplikation im sqrt_price_x64-Raum.
Preis und sqrt-price sind eineindeutig; die Umwandlung ist price = (sqrt_price_x64 / 2^64)^2.

Tick-Gitter

Preise werden auf ein Gitter diskretisiert:
price(tick_i) = 1.0001^i
tick_i ist ein i32. Der aktive Bereich ist [MIN_TICK, MAX_TICK] = [−443636, 443636], was einen Preisbereich von etwa [2^−128, 2^128] ergibt. Der tick_spacing jedes Pools wird durch seinen Gebührentarif festgelegt: kleinere Abstände für enge Paare (z. B. Stablecoin-0,01%-Tarif verwendet Abstand 1), größere Abstände für volatile Paare (0,25%-Tarif verwendet 60, 1%-Tarif verwendet 120). Positionen müssen tick_lower und tick_upper am tick_spacing ausgerichtet haben. Die aktiven Ticks eines Pools (diejenigen, bei denen Liquidität beginnt oder endet) sind die einzigen Ticks, die der Swap-Schritt beachtet.

Liquidity-zu-Betrag

Für eine Position mit Liquidität L und Preisrange [sqrt_lo, sqrt_hi] (alle sqrt_price-Werte):
Pool-StatusToken0-BetragToken1-Betrag
Preis über Range (sqrt_p ≥ sqrt_hi)0L · (sqrt_hi − sqrt_lo)
Preis im BereichL · (sqrt_hi − sqrt_p) / (sqrt_p · sqrt_hi)L · (sqrt_p − sqrt_lo)
Preis unter Range (sqrt_p ≤ sqrt_lo)L · (sqrt_hi − sqrt_lo) / (sqrt_lo · sqrt_hi)0
Ableitung: Differenzieren Sie die CPMM-Invariante lokal. Innerhalb eines einzelnen Tick-Bereichs verhält sich die Position wie ein CPMM mit virtuellen Rücklagen (x_v, y_v), die so gewählt werden, dass der aktuelle (sqrt_p, L) des Pools mit L = sqrt(x_v · y_v) übereinstimmt. Die Integration von sqrt_p zur Reichweitenbegrenzung ergibt die obigen Beträge. Inverse Formeln (verwendet beim Prägen einer Position für einen bestimmten amount0 oder amount1):
L_from_amount0(amount0, sqrt_lo, sqrt_hi, sqrt_p) =
    amount0 · sqrt_p · sqrt_hi / (sqrt_hi − sqrt_p)

L_from_amount1(amount1, sqrt_lo, sqrt_hi, sqrt_p) =
    amount1 / (sqrt_p − sqrt_lo)

// Für eine symmetrische Hinterlegung in eine aktive Position nimm das Minimum.
L = min(L_from_amount0, L_from_amount1)

Single-Tick-Swap-Schritt

Innerhalb eines einzelnen Tick-Bereichs verhält sich der Pool wie ein CPMM. Bei gegebenen aktuellen sqrt_p und Ziel sqrt_target:
Δamount0_step = L · (sqrt_target − sqrt_p) / (sqrt_p · sqrt_target)     // beim Tausch für Token0
Δamount1_step = L · (sqrt_target − sqrt_p)                              // beim Tausch für Token1

Exact-Input-Schritt

Bei gegebener Δin_remaining:
// Kandidat neuer sqrt_p, wenn wir bis zur Tick-Grenze aufgefüllt hätten:
sqrt_after_full = sqrt_target
amount_to_full  = Δamount_in_to_reach(sqrt_p → sqrt_target)

if Δin_remaining ≥ amount_to_full:
    // Rest des Eimers verbrauchen
    sqrt_p'         = sqrt_target
    Δin_consumed    = amount_to_full
    Δout            = amount_out_at_boundary
else:
    // wir überqueren nicht; lösen für den End-sqrt_p
    sqrt_p'         = L · sqrt_p / (L + Δin_remaining · sqrt_p)      // für 0→1-Swaps
    Δin_consumed    = Δin_remaining
    Δout            = L · (sqrt_p − sqrt_p')                          // proportional zu Δsqrt
Der 0→1-Swap senkt sqrt_p (Preis sinkt, während wir Token0 verkaufen). Ein 1→0-Swap hebt ihn an. Die Formeln sind symmetrisch, wobei sqrt_p und sqrt_target vertauscht werden.

Exact-Output-Schritt

Gleiche Struktur, lösen für Δin statt.

Multi-Tick-Swap-Schleife

Ein Swap durchläuft Ticks, bis die Eingabe verbraucht ist oder das Preislimit erreicht wird:
while Δin_remaining > 0 and sqrt_p != sqrt_price_limit:
    next_tick = find_next_initialized_tick(pool.tick_current, direction)
    sqrt_target = min(next_tick.sqrt_price, sqrt_price_limit)       // direktional

    (Δin, Δout, sqrt_p') = single_step(sqrt_p, sqrt_target, L, Δin_remaining)

    Δin_remaining -= Δin
    accumulated_out += Δout

    if sqrt_p' == next_tick.sqrt_price:
        // Tick überqueren
        L += next_tick.liquidity_net * direction_sign
        flip_fee_growth_outside(next_tick)
        match_limit_orders_at_tick(next_tick, ...)        // siehe products/clmm/math
        pool.tick_current = next_tick.tick_index
    sqrt_p = sqrt_p'
Jeder single_step verwendet die aktuelle L des Pools. L ändert sich nur beim Überqueren eines initialisierten Ticks. Liquidität zwischen Ticks ist konstant, was die geschlossene Form der Schritt-Mathematik ermöglicht. liquidity_net bei einem Tick ist die vorzeichenbehaftete Summe der Positionsliquiditäten, die bei diesem Tick beginnen, minus denen, die dort enden. Ein Aufwärtsübergang addiert liquidity_net; ein Abwärtsübergang subtrahiert es. Wenn der Pool Limit-Orders bei einem Tick offen hat, verbraucht der Tick-Übergang auch opportunistisch einen Teil der Swap-Eingabe, um diese Orders zu erfüllen (FIFO über Kohorten hinweg). Der Matching-Algorithmus und die dynamische Gebührenspanne, die möglicherweise auf dem Basisschritt anfallen, sind in products/clmm/math dokumentiert; sie ändern die obigen geschlossenen Single-Step-Formeln nicht.

Fee-Growth-Akkumulatoren

CLMM verfolgt Gebühren pro Einheit aktiver Liquidität, pro Seite, global und pro Tick:
fee_growth_global_0_x64     // Q64.64, monoton
fee_growth_global_1_x64
tick.fee_growth_outside_0_x64   // „Gebühren aufgelaufen, während dieser Tick außerhalb des aktiven Bereichs war"
tick.fee_growth_outside_1_x64
Bei jedem single_step:
step_lp_fee = (Δin · fee_rate) · (1 − protocol_fraction − fund_fraction)
fee_growth_global += step_lp_fee · 2^64 / L     // nur für die Input-Seite
(Die fee_growth_global der anderen Seite bewegt sich nicht bei diesem Schritt, da kein Token auf dieser Seite als Input bezahlt wurde.) Beim Überqueren eines Ticks dreht das Programm fee_growth_outside um:
tick.fee_growth_outside_0_x64 = fee_growth_global_0_x64 − tick.fee_growth_outside_0_x64
tick.fee_growth_outside_1_x64 = fee_growth_global_1_x64 − tick.fee_growth_outside_1_x64
„Außen” ist relativ zu tick_current. Wenn tick_current über dem Tick liegt, bedeutet außen „darunter”. Wenn tick_current darunter liegt, bedeutet außen „darüber”. Die Umkehrung wechselt die Interpretation.

fee_growth_inside für eine Position

Bei gegebener Position [tick_lower, tick_upper] und dem aktuellen tick_current:
if tick_current >= tick_upper:
    inside = tick_lower.fee_growth_outside − tick_upper.fee_growth_outside
else if tick_current < tick_lower:
    inside = tick_upper.fee_growth_outside − tick_lower.fee_growth_outside
else:     // Position ist im Bereich
    inside = fee_growth_global
           − tick_lower.fee_growth_outside
           − tick_upper.fee_growth_outside
Die nicht eingezogenen Gebühren einer Position für die Token-Seite s sind:
tokens_owed_s += L · (fee_growth_inside_s − fee_growth_inside_last_s) / 2^64
fee_growth_inside_last_s = fee_growth_inside_s
Diese Aktualisierung wird bei jeder Interaktion mit der Position ausgeführt (IncreaseLiquidity, DecreaseLiquidity, CollectFees).

Durchgerechnetes Beispiel — Überqueren eines Ticks

Pool (vereinfacht):
  • sqrt_p_x64 = 2^64 · 1.0 = 2^64 (price = 1.0)
  • L = 1_000_000
  • tick_current = 0
  • Nächster initialisierter Tick darunter: tick = −60, sqrt_price = 1.0001^(−30) ≈ 0.99700, liquidity_net = −400_000 (dieser Tick beendet eine Position, daher entfernt ein Abwärtsübergang 400k)
  • Gebührensatz: 0,25%
Swap: Δin = 10_000 Token0, direction = 0→1. Schritt 1 — bis zu sqrt_target = 0.99700 · 2^64:
amount_in_to_target = L · (1/sqrt_target − 1/sqrt_p)
                    = 1_000_000 · (1/0.99700 − 1/1.0)
                    ≈ 1_000_000 · 0.003009
                    ≈ 3_009
3.009 < 10.000, also füllen wir diesen Schritt vollständig aus:
Δin_step  = 3_009 / (1 − 0.0025)  = 3_017    // brutto inklusive Gebühr
Δout_step = L · (sqrt_p − sqrt_target) ≈ 1_000_000 · 0.00299 ≈ 2_990
sqrt_p    = 0.99700 · 2^64
tick_current = −60
L         = 1_000_000 + (−400_000)  = 600_000         // Tick überquert
fee_growth_outside at tick −60 is flipped
Δin_remaining = 10_000 − 3_017 = 6_983
Schritt 2 — mit neuem L = 600_000: Der nächste initialisierte Tick (sagen wir tick = −120) liegt bei sqrt = 0.99402. Berechne amount_in_to_target neu:
amount_in_to_target = 600_000 · (1/0.99402 − 1/0.99700)
                    ≈ 600_000 · 0.003010
                    ≈ 1_806
Immer noch weniger als Δin_remaining. Überquere erneut. Fortfahren, bis Δin_remaining null erreicht. Die vollständige Abfolge von Δout akkumuliert sich zur endgültigen Swap-Ausgabe.

Initialisierung und Überflutungsschutz

  • MIN_SQRT_PRICE_X64 und MAX_SQRT_PRICE_X64 entsprechen tick = ±443636. Jeder Swap, der sqrt_p außerhalb dieser Range drücken würde, wird rückgängig gemacht.
  • Der Parameter sqrt_price_limit des Benutzers muss in demselben Intervall liegen; das Programm prüft dies.
  • Produkte von L · Δsqrt werden in u256 berechnet, dann zurück zu u128 verschoben, um Überflutung zu vermeiden.

Unterschiede zu Uniswap v3

  • Oracle. Raydiums ObservationState speichert einen Ring-Buffer von (block_timestamp, tick_cumulative, seconds_per_liquidity_cumulative); leicht unterschiedliches Drahtkabel-Format als Uniswap, aber gleiche TWAP-Mathematik.
  • Token-2022. Raydiums CLMM unterstützt Token-2022-Mints; die Transfer-Fee-Variante erfordert zusätzliche Pre/Post-Swap-Betrag-Anpassungen. Siehe algorithms/token-2022-transfer-fees.
  • Tick-Bitmap. Raydium packt die initialisierte Tick-Bitmap in [u64; 16] pro Pool für schnelle find_next_initialized_tick; Uniswap verwendet eine On-Chain-Zuordnung pro Wort. Der Tradeoff ist Miete vs. Lookup-Kosten.
  • Reward-Slots. Raydium unterstützt 3 Pool-übergreifende Reward-Streams mit separaten reward_growth_global_x64-Zählern; gleiche Struktur wie der Fee-Growth-Akkumulator.

Verweise

Quellen:
  • Uniswap v3 Whitepaper (kanonische Ableitung der sqrt-price-Mathematik).
  • Raydium CLMM Programmquelle.