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

L’invariant

Le CPMM maintient l’invariant constant-product classique sur ses deux vaults : xy=kx \cdot y = k x est le solde de vault0 après les frais de transfert Token-2022 à la réception, et de même pour y. Chaque swap doit laisser k' ≥ k après comptabilisation des frais de trading crédités aux LP (les buckets protocol, fund et creator ne sont pas comptés vers k — ils restent dans le vault mais sont exclus de la vue de la courbe, voir Frais sur la courbe ci-dessous). k croît donc de façon monotone au fil du temps à mesure que les LP accumulent les frais. Les parts LP sont tarifées selon les réserves du pool, pas selon k : Prix LP en token0=xlpSupply,Prix LP en token1=ylpSupply\text{Prix LP en token0} = \frac{x}{\text{lpSupply}}, \qquad \text{Prix LP en token1} = \frac{y}{\text{lpSupply}} Brûler ΔLP tokens LP retourne exactement ΔLP × x / lpSupply de token0 et ΔLP × y / lpSupply de token1. Ni la courbe ni k ne bougent au dépôt ou au retrait — seuls les swaps changent le prix.

Modèle de frais sur le chemin de swap

Le CPMM applique deux frais indépendamment cotés sur chaque swap :
  • Le frais de trading est prélevé côté input, facturé au taux AmmConfig.trade_fee_rate. Il est ensuite divisé en parts LP, protocol et fund (la part LP reste dans le vault et augmente k ; les parts protocol et fund sont extraites de la comptabilité du vault).
  • Le frais de creator (actif seulement si enable_creator_fee == true) est facturé au taux AmmConfig.creator_fee_rate. Il est prélevé côté input ou côté output selon PoolState.creator_fee_on et la direction du swap (voir products/cpmm/fees). C’est son propre bucket — jamais une tranche du frais de trading.
Soit :
  • FEE_RATE_DENOMINATOR = 1_000_000
  • trade_fee_rate — de AmmConfig, ex. 2500 = 0.25% du volume côté pertinent
  • creator_fee_rate — de AmmConfig, ex. 1000 = 0.10% du volume côté pertinent
  • protocol_fee_rate, fund_fee_rate — libellés en unités de 1/FEE_RATE_DENOMINATOR du frais de trading, pas du volume
Quand le frais de creator est côté input :
Quand le frais de creator est côté output :
Dans les deux cas, le frais de trading est divisé de la même façon :
Le montant protocol_fee + fund_fee + creator_fee est tenu dans les vaults mais suivi séparément sur l’état du pool (protocol_fees_token*, fund_fees_token*, creator_fees_token*). Quand la vérification de l’invariant constant-product contrôle k' ≥ k, elle utilise les soldes du vault moins les trois frais accumulés mais non prélevés — donc les LP ne capturent que lp_fee. Voir products/cpmm/fees pour les instructions de collecte et les exemples numériques détaillés.

SwapBaseInput (input-exact)

« L’utilisateur nous donne exactement amount_in du mint input et reçoit au moins minimum_amount_out du mint output. » En ignorant Token-2022 pour l’instant :
Par algèbre : 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. Le programme met ensuite à jour la comptabilité du vault de sorte que la portion du trade_fee due au protocol/fund/creator reste dans des buckets « accrués » (non inclus dans le x suivant de la courbe), tandis que la part LP rejoint x pour le prochain swap.

Token-2022 côté input

Si le mint input a une extension de frais de transfert, le mint déduit son frais lors du transfert de utilisateur → vault. Le vault reçoit donc en fait amount_in − transfer_fee_in(amount_in). Le programme CPMM calcule donc :
et exécute la courbe contre amount_in_after_trade_fee. Ceci est important car le prix de la courbe est calculé sur le montant net qui a atterri dans le vault, pas sur le montant affiché par l’utilisateur.

Token-2022 côté output

Si le mint output a un frais de transfert, le pool envoie amount_out de son vault à l’utilisateur. Le mint déduit alors son frais en sortie, donc l’utilisateur reçoit amount_out − transfer_fee_out(amount_out). Le programme calcule amount_out à partir de la courbe comme d’habitude, mais c’est la responsabilité de l’intégrateur de convertir le nombre « vault send » du pool en nombre « user receive » lors de l’affichage des cotations.

Vérification du slippage

Après le calcul de amount_out :
Si le mint output facture un frais de transfert, le SDK applique le frais de transfert avant de fixer minimum_amount_out pour que la constante de slippage soit libellée en ce que l’utilisateur recevra réellement, pas en ce que le vault envoie.

SwapBaseOutput (output-exact)

« L’utilisateur recevra exactement amount_out du mint output et est disposé à payer jusqu’à maximum_amount_in du mint input. » En inversant la courbe pour Δx_net : Δxnet=xamount_outyamount_out\Delta x_{\text{net}} = \left\lceil \frac{x \cdot \text{amount\_out}}{y - \text{amount\_out}} \right\rceil Le plafond est important — il garantit k' ≥ k après la troncature d’entiers. Ensuite :
Sur Token-2022 input, enveloppez avec :
de sorte que l’utilisateur paie assez pour qu’après la déduction du frais de transfert du mint, le pool reçoive toujours gross_needed.

Vérification du slippage

Exemple détaillé

État du pool, en ignorant Token-2022 :
  • x = 1_000_000_000_000 (1 000 000,000000 de token0, 6 décimales)
  • y = 2_000_000_000_000 (2 000 000,000000 de token1, 6 décimales)
  • AmmConfig : trade_fee_rate = 2500, protocol_fee_rate = 120_000, fund_fee_rate = 40_000, creator_fee_rate = 0
Utilisateur : SwapBaseInput avec amount_in = 1_000_000_000 (1 000,000000 de token0). Le frais de creator est désactivé (enable_creator_fee = false).
Si le même pool avait enable_creator_fee = true avec creator_fee_rate = 1000 (0.10%) côté input, le programme chargerait total_input_fee = ceil(1_000_000_000 * 3500 / 1_000_000) = 3_500_000, puis le diviserait comme creator_fee = 1_000_000 et trade_fee = 2_500_000. L’arithmétique protocol/fund/LP sur trade_fee est inchangée par rapport à l’exemple ci-dessus — le frais de creator est son propre bucket, accrué à creator_fees_token0 et exclu de curve_x avec les buckets protocol et fund. Si le mint input a un frais de transfert Token-2022 de 1%, le vault reçoit 990_000_000 tokens au lieu de 1_000_000_000, et chaque calcul ultérieur utilise ce montant net.

Règle de mise à jour de l’observation

À chaque swap, le programme évalue s’il faut pousser une nouvelle observation dans le buffer circulaire :
Deux propriétés :
  • Prix cumulatif, pas prix spot. Une seule observation n’est pas un prix. Pour obtenir un TWAP de t0 à t1, lisez les observations les plus proches de chaque extrémité et calculez (cumulative(t1) − cumulative(t0)) / (t1 − t0).
  • Les échantillons sont limités en débit. Les swaps consécutifs dans le même slot peuvent partager une observation. Lire une observation immédiatement après un swap peut donc sembler obsolète d’un slot — c’est normal.
Plus dans products/clmm/accounts.

Frais sur la courbe

C’est la partie subtile et elle mérite d’être signalée. L’arithmétique de la courbe fonctionne contre les soldes du vault nets — c.-à-d. le solde SPL brut moins les frais protocol, fund et creator accumulés (les trois sont des buckets indépendants — voir products/cpmm/fees). Une image concrète :
Conséquences pour les intégrateurs :
  • Ne cotez pas sur les soldes bruts. Soustrayez d’abord les champs de frais accumulés, ou appelez SwapBaseInput comme une simulation et prenez son retour.
  • CollectProtocolFee déplace les tokens hors du vault. Après la collecte, raw_vault_balance baisse mais curve_balance ne change pas ; le prix du pool ne bouge pas. C’est volontaire.

Précision et débordement

  • Toute l’arithmétique de courbe utilise des intermédiaires u128 pour éviter le débordement sur x * y.
  • La division arrondit vers zéro sauf pour le Δx_net de SwapBaseOutput, qui arrondit vers le haut, et le calcul des frais, qui arrondit vers le haut sur trade_fee et vers le bas sur les sous-divisions. Ces directions d’arrondi sont choisies pour que l’invariant ne diminue jamais à cause de la troncature d’entiers.
  • Les pools avec des ratios de vault extrêmes (milliards : 1) peuvent atteindre des planchers de précision sur les petits trades ; le programme retourne ZeroTradingTokens dans ce cas. Voir reference/error-codes.

Où aller ensuite

Sources :