Skip to main content
Cette page est traduite automatiquement par IA. La version anglaise fait foi.Voir la version anglaise →
Les PDAs (adresses dérivées de programme) et les CPIs (invocations inter-programme) sont les deux primitives qui rendent Raydium possible. Les PDAs permettent à un programme de « posséder » des adresses déterministes sans clés privées — c’est ainsi que fonctionnent les autorités de pool et les coffres. Les CPIs permettent à un programme d’appeler un autre — c’est ainsi que Raydium échange des tokens via le programme SPL Token et comment les intégrateurs composent Raydium dans leurs propres flux. Il vaut la peine de comprendre les deux avant de lire le code source de Raydium.

PDAs : des adresses sans clés

Une adresse dérivée de programme est une clé publique qui :
  • N’est pas sur la courbe ed25519 (aucune clé privée n’existe pour elle).
  • Est dérivée de manière déterministe à partir d’un ID de programme et d’un ensemble de seeds.
  • Ne peut être signée que par le programme de dérivation, via invoke_signed.
Chaque autorité de pool Raydium, chaque compte d’état de pool, chaque coffre, chaque état de farm — ce sont tous des PDAs.

Dérivation

Un PDA est calculé en hachant l’ID du programme avec les seeds, puis en trouvant un octet « bump » qui force le résultat hors de la courbe. Le premier bump (généralement en commençant par 255 et en décrémentant) qui produit une adresse hors-courbe gagne ; c’est le bump canonique.
Les seeds peuvent être n’importe quoi — des chaînes, d’autres clés publiques, des valeurs u64 en octets little-endian. La convention de Raydium est un préfixe lisible par l’homme suivi d’identifiants uniques.

Motifs PDA de Raydium

Les PDAs courants dans les programmes de Raydium : Les utilisateurs et les intégrateurs peuvent calculer ces PDAs sans rien récupérer — étant donné les entrées publiques (ID du pool, ID de la farm, clé utilisateur), le PDA est déterministe.

Bump canonique

Bien qu’il puisse en principe y avoir plusieurs bumps produisant des adresses hors-courbe, les programmes de Raydium utilisent toujours le bump canonique (trouvé en décrémentant à partir de 255). Celui-ci est stocké dans les données du compte PDA afin que les transactions ultérieures puissent le passer et ignorer la boucle de dérivation (coûteuse) :
(Le PoolState de CLMM stocke bump: [u8; 1] à la place, donc vérifiez la struct du programme spécifique plutôt que d’assumer une forme unique.) Lors des transactions ultérieures, le bump est lu à partir de l’état du pool plutôt que recalculé.

CPIs : appeler d’autres programmes

L’invocation inter-programme permet à un programme d’invoquer les instructions d’un autre programme en ligne dans une seule transaction. Raydium utilise les CPIs de manière extensive :
  • Les instructions de swap appellent le programme SPL Token pour déplacer les tokens.
  • CLMM appelle Metaplex pour frapper le NFT de position.
  • La création de pool appelle le System Program pour allouer des comptes.
  • Farm v6 appelle SPL Token pour transférer les récompenses.
Les intégrateurs utilisent également les CPIs pour appeler dans Raydium — c’est ainsi que fonctionnent les stratégies de coffre, les protocoles de LP à effet de levier et les auto-composeurs. Voir integration-guides/cpi-integration.

invoke vs invoke_signed

Le runtime Solana offre deux primitives CPI :
  • invoke : appeler un autre programme ; le programme appelé hérite des signataires de la transaction externe.
  • invoke_signed : appeler un autre programme au nom d’un PDA ; le runtime vérifie les seeds du PDA et autorise la signature.
invoke_signed est la magie qui permet aux programmes de détenir l’autorité sur les comptes sans gérer les clés privées.

Exemple : Raydium transférant depuis un coffre de pool

Un coffre de pool est un Token Account dont l’autorité est un PDA du programme de pool. Pour transférer des tokens lors d’un swap, le programme de pool doit signer en tant que ce PDA :
Le runtime voit que invoke_signed est appelé par le programme CPMM, vérifie que vault_and_lp_mint_auth_seed + bump dérive vers l’adresse de pool_authority lorsqu’il est haché avec l’ID du programme CPMM, et autorise la signature de l’autorité sur le transfert de token. Aucune clé privée impliquée.

Exemple : intégrateur appelant Raydium CPMM

Un programme intégrateur (par exemple, un escrow) peut invoquer swap_base_input de Raydium via CPI :
C’est le motif d’intégration canonique — voir integration-guides/cpi-integration pour l’exemple complet d’escrow.

Limite de profondeur CPI

Solana plafonne la profondeur CPI à 4 niveaux. L’instruction de niveau supérieur d’une transaction compte comme profondeur 0 ; chaque invocation CPI incrémente la profondeur. Implication pratique : le propre swap de Raydium utilise déjà 1-2 niveaux de CPI (Raydium → SPL Token). Un intégrateur appelant Raydium en utilise 2. Si cet intégrateur est appelé par un autre intégrateur, c’est 3. Le 4e niveau est la limite. La plupart des compositions restent bien en dessous de cela, mais l’imbrication profonde (agrégateur → routeur → Raydium → hook) peut l’atteindre. Concevez de manière plate plutôt que profonde.

Comptes restants

Lorsqu’une instruction Raydium a besoin d’un nombre variable de comptes (par exemple, un swap CLMM traversant un nombre inconnu de tableaux de ticks), les comptes supplémentaires sont passés en tant que comptes restants — ajoutés à la liste des comptes fixes, interprétés par position. Le SwapV2 de CPMM utilise les comptes restants pour les comptes supplémentaires requis des programmes de transfer-hook. Les clients récupèrent les comptes nécessaires et les ajoutent :
Au niveau CPI, les intégrateurs doivent transférer les comptes restants via leur propre instruction :

Pièges des PDAs

Mauvaises seeds → mauvaise adresse

Un bug où les seeds sont dans le mauvais ordre, mauvais encodage, ou incluent/excluent un octet supplémentaire produit silencieusement un PDA différent. La transaction échoue de manière ambiguë (le programme essaie de lire un compte qui n’existe pas). Testez toujours unitairement la dérivation des seeds par rapport à des valeurs de référence connues.

Ne pas stocker le bump

Si vous redérivez le bump à chaque transaction, vous payez du calcul pour la boucle de dérivation. Stockez le bump canonique dans les données du PDA et lisez-le à partir de là.

Confondre bump canonique vs non-canonique

Les bumps non-canoniques (si quelqu’un en trouve un qui produit hors-courbe) sont autorisés par invoke_signed mais rejetés par les programmes de Raydium via assert_eq!(bump, canonical_bump). Si quelqu’un essaie de réclamer un PDA avec un bump non-canonique, la tx échoue.

Passer un PDA en tant que signataire quand vous n’êtes pas le programme propriétaire

Seul le programme dont l’ID est dans la dérivation du PDA peut invoke_signed avec ses seeds. Si vous essayez, le runtime rejette.

Pièges des CPIs

Oublier de transférer remaining_accounts

Si votre instruction externe passe les comptes de transfer-hook dans remaining_accounts mais que le CPI dans Raydium ne les transfère pas, Raydium échoue car il ne peut pas trouver les comptes de hook. Incluez toujours with_remaining_accounts dans les CPIs qui en ont besoin.

Désaccord des drapeaux writable

Un compte que l’instruction externe marque comme writable doit également être writable dans l’appel CPI si le programme appelé a l’intention de l’écrire. Désaccord → rejet du runtime.

Ne pas tenir compte de la rente

CPI vers un programme qui crée un compte (par exemple, création d’ATA) nécessite que le payeur ait suffisamment de SOL pour la rente. Les vérifications de rente échouées apparaissent comme des erreurs obscures.

Exemple travaillé : calcul des PDAs CPMM de Raydium

C’est exactement ce que le SDK Raydium fait sous le capot quand vous appelez getPoolInfoFromRpc({ poolId }) — il dérive les PDAs associés sans aller-retour.

Pointeurs

Sources :