Skip to main content
Cette page est traduite automatiquement par IA. La version anglaise fait foi.Voir la version anglaise →

Pourquoi sqrt-price et non price

Les CLMMs de la famille Uniswap-v3 représentent le prix comme sa racine carrée, stockée en virgule fixe Q64.64 :
Trois raisons :
  1. Mathématiques linéaires du liquidity. La quantité de token0 ou token1 dans une plage de prix s’avère être une fonction linéaire de sqrt_price, non de price. Stocker sqrt_price permet à l’étape de swap d’évaluer ces formules linéaires sans calculer une racine carrée.
  2. Contrôle du débordement. sqrt_price · L tient dans u256 pour tous les paramètres raisonnables ; price · L peut déborder beaucoup plus tôt.
  3. Les mathématiques des ticks sont uniformes. Parce que les ticks sont définis comme 1.0001^i, sqrt(price) = (√1.0001)^i ≈ 1.00005^i est aussi une échelle géométrique exacte. (√1.0001 = 1.00004999875…, donc le traiter comme exactement 1.00005 dérive d’environ 0,6 % au maximum à MAX_TICK.) Chaque franchissement de tick se traduit par une petite multiplication dans l’espace sqrt_price_x64.
Le prix et le sqrt-price sont en bijection ; la conversion est price = (sqrt_price_x64 / 2^64)^2.

Grille de ticks

Les prix sont discrétisés sur une grille :
tick_i est un i32. La plage active est [MIN_TICK, MAX_TICK] = [−443636, 443636], donnant une plage de prix d’environ [2^−64, 2^64] — environ 5.4e-20 à 1.8e19. Le tick_spacing de chaque pool est défini par son niveau de frais : des espacements plus petits pour les paires serrées (par exemple, le niveau stablecoin 0,01 % utilise un espacement de 1), des espacements plus grands pour les paires volatiles (le niveau 0,25 % utilise 60, le niveau 1 % utilise 120). Les positions doivent avoir tick_lower et tick_upper alignés sur tick_spacing. Les ticks actifs d’un pool (ceux avec du liquidity qui commence ou se termine là) sont les seuls ticks dont l’étape de swap se soucie.

Liquidity-to-amount

Pour une position avec liquidity L et plage de prix [sqrt_lo, sqrt_hi] (toutes les valeurs sqrt_price) : Dérivation : différencier localement l’invariant CPMM. À l’intérieur de n’importe quelle plage de tick unique, la position se comporte comme un CPMM avec des réserves virtuelles (x_v, y_v) choisies de sorte que le (sqrt_p, L) actuel du pool soit cohérent avec L = sqrt(x_v · y_v). L’intégration de sqrt_p à la limite de la plage donne les quantités ci-dessus. Formules inverses (utilisées lors de la création d’une position pour un amount0 ou amount1 donné) :

Étape de swap single-tick

À l’intérieur d’une plage de tick unique, le pool se comporte comme un CPMM. Étant donné le sqrt_p actuel et le sqrt_target cible :

Étape exact-input

Étant donné Δin_remaining :
Le swap 0→1 abaisse sqrt_p (le prix baisse à mesure qu’on vend du token0). Un swap 1→0 l’élève. Les formules sont symétriques avec sqrt_p et sqrt_target échangés.

Étape exact-output

Même structure, en résolvant pour Δin à la place.

Boucle de swap multi-tick

Un swap itère sur les ticks jusqu’à ce que l’entrée soit épuisée ou que la limite de prix soit atteinte :
Chaque single_step utilise le L actuel du pool. L change uniquement lors du franchissement d’un tick initialisé. Le liquidity entre les ticks est constant, ce qui rend les mathématiques de l’étape en forme fermée. liquidity_net à un tick est la somme signée des liquidités de position qui commencent à ce tick moins celles qui s’y terminent. Franchir vers le haut ajoute liquidity_net ; franchir vers le bas le soustrait. Lorsque le pool a des ordres limites ouverts à un tick, l’étape de franchissement de tick consomme également opportunément une partie de l’entrée de swap pour remplir ces ordres (FIFO entre les cohortes). L’algorithme d’appariement et la surcharge de frais dynamiques qui peut s’appliquer en plus de l’étape de base sont documentés dans products/clmm/math ; ils ne modifient pas les formules en forme fermée de single-step ci-dessus.

Accumulateurs de croissance des frais

Le CLMM suit les frais par unité de liquidity actif, par côté, globalement et par tick :
À chaque single_step :
(Le fee_growth_global de l’autre côté ne bouge pas à cette étape, puisqu’aucun token de ce côté n’a été payé en entrée.) Lors du franchissement d’un tick, le programme bascule fee_growth_outside :
« Outside » est relatif à tick_current. Lorsque tick_current est au-dessus du tick, outside signifie « en dessous ». Lorsque tick_current est en dessous, outside signifie « au-dessus ». La bascule échange l’interprétation.

fee_growth_inside pour une position

Étant donné une position [tick_lower, tick_upper] et le tick_current actuel :
Les frais non collectés d’une position pour le côté token s sont :
Cette mise à jour s’exécute à chaque interaction avec la position (IncreaseLiquidity, DecreaseLiquidity, CollectFees).

Exemple travaillé — franchissement d’un tick

Pool (simplifié) :
  • sqrt_p_x64 = 2^64 · 1.0 = 2^64 (price = 1.0)
  • L = 1_000_000
  • tick_current = 0
  • Prochain tick initialisé en dessous : tick = −60, sqrt_price = 1.0001^(−30) ≈ 0.99700, liquidity_net = −400_000 (ce tick termine une position, donc un franchissement vers le bas supprime 400k)
  • Taux de frais : 0,25 %
Swap : Δin = 10_000 token0, direction = 0→1. Étape 1 — jusqu’à sqrt_target = 0.99700 · 2^64 :
3 009 < 10 000, donc on remplit cette étape complètement :
Étape 2 — avec nouveau L = 600_000 : Le prochain tick initialisé (disons tick = −120) est à sqrt = 0.99402. Recalculer amount_in_to_target :
Toujours moins que Δin_remaining. Franchir à nouveau. Continuer jusqu’à ce que Δin_remaining atteigne zéro. La séquence complète de Δout s’accumule à la sortie de swap finale.

Initialisation et gardes contre le débordement

  • MIN_SQRT_PRICE_X64 et MAX_SQRT_PRICE_X64 correspondent à tick = ±443636. Tout swap qui pousserait sqrt_p en dehors de cette plage revient.
  • Le paramètre sqrt_price_limit de l’utilisateur doit se situer dans le même intervalle ; le programme le vérifie.
  • Les produits de L · Δsqrt sont calculés en u256 puis décalés vers u128 pour éviter le débordement.

Différences par rapport à Uniswap v3

  • Oracle. Le ObservationState ring buffer de Raydium stocke uniquement (block_timestamp: u32, tick_cumulative: i64) par slot plus [u64; 4] de remplissage réservé — il n’y a pas de champ seconds_per_liquidity_cumulative, donc les dérivations pondérées par liquidity d’Uniswap ne sont pas disponibles on-chain.
  • Token-2022. Le CLMM de Raydium supporte les mints Token-2022 ; la variante avec frais de transfert nécessite des ajustements supplémentaires de montant pré/post-swap. Voir algorithms/token-2022-transfer-fees.
  • Bitmap de ticks. Raydium compacte le bitmap des ticks initialisés dans [u64; 16] par pool pour un find_next_initialized_tick rapide ; Uniswap utilise un mapping on-chain par mot. Le compromis est entre le loyer et le coût de recherche.
  • Slots de récompenses. Raydium supporte 3 flux de récompenses par pool avec des compteurs reward_growth_global_x64 séparés ; même structure que l’accumulateur de croissance des frais.

Pointeurs

Sources :
  • Uniswap v3 whitepaper (dérivation canonique des mathématiques sqrt-price).
  • Code source du programme CLMM de Raydium.