Skip to main content
本页内容由 AI 自动翻译,所有内容以英文版本为准。查看英文版 →
PDA(程序派生地址)和 CPI(跨程序调用)是使 Raydium 成为可能的两个基础原语。PDA 让程序能够”拥有”确定性地址而无需私钥——这就是池权限和金库的工作原理。CPI 让一个程序调用另一个程序——这就是 Raydium 通过 SPL Token 程序交换代币以及集成者将 Raydium 组合到自己流程中的方式。在阅读 Raydium 源代码之前,值得理解这两者。

PDA:无密钥的地址

程序派生地址是一个公钥,它:
  • 不在 ed25519 曲线上(不存在其私钥)。
  • 从程序 ID 和一组种子确定性地派生。
  • 只能由派生程序通过 invoke_signed 签署。
每个 Raydium 池权限、每个池状态账户、每个金库、每个农场状态——它们都是 PDA。

派生

PDA 通过用程序 ID 和种子进行哈希计算,然后找到一个”bump”字节将结果强制移出曲线来计算。第一个产生离曲线地址的 bump(通常从 255 开始递减)获胜;这就是规范 bump。
种子可以是任何东西——字符串、其他公钥、u64 值作为小端字节。Raydium 的约定是人类可读的前缀后跟唯一标识符。

Raydium PDA 模式

Raydium 程序中的常见 PDA: 用户和集成者可以在不获取任何内容的情况下计算这些——给定公开输入(池 ID、农场 ID、用户密钥),PDA 是确定性的。

规范 bump

虽然原则上可能存在多个产生离曲线地址的 bump,但 Raydium 的程序总是使用规范 bump(通过从 255 递减找到)。这存储在 PDA 的账户数据中,以便后续交易可以传入它并跳过(昂贵的)派生循环:
(CLMM 的 PoolState 改为存储 bump: [u8; 1],所以检查特定程序的结构而不是假设一种形状。) 在后续交易中,bump 从池状态读取而不是重新计算。

CPI:调用其他程序

跨程序调用让一个程序在单个交易中内联调用另一个程序的指令。Raydium 广泛使用 CPI:
  • 交换指令调用 SPL Token 程序来移动代币。
  • CLMM 调用 Metaplex 来铸造头寸 NFT。
  • 池创建调用系统程序来分配账户。
  • Farm v6 调用 SPL Token 来转移奖励。
集成者也使用 CPI 来调用 Raydium——这就是金库策略、杠杆 LP 协议和自动复合器的工作原理。参见 integration-guides/cpi-integration。

invoke 与 invoke_signed

Solana 运行时提供两个 CPI 原语:
  • invoke:调用另一个程序;被调用程序继承外部交易的签署者。
  • invoke_signed:代表 PDA 调用另一个程序;运行时验证 PDA 的种子并授权签名。
invoke_signed 是让程序在不管理私钥的情况下对账户拥有权限的魔法。

示例:Raydium 从池金库转移

池金库是一个 Token 账户,其权限是池程序的 PDA。要在交换期间转移代币,池程序必须作为该 PDA 签署:
运行时看到 invoke_signed 由 CPMM 程序调用,验证 vault_and_lp_mint_auth_seed + bump 在用 CPMM 程序 ID 进行哈希时派生到 pool_authority 的地址,并允许代币转移上的权限签名。不涉及私钥。

示例:集成者调用 Raydium CPMM

集成者程序(例如,托管)可以通过 CPI 调用 Raydium 的 swap_base_input:
这是规范的集成模式——参见 integration-guides/cpi-integration 获取完整的托管示例。

CPI 深度限制

Solana 将 CPI 深度限制在 4 级。交易的顶级指令计为深度 0;每个 CPI 调用增加深度。 实际含义:Raydium 自己的交换已经使用 1-2 级 CPI(Raydium → SPL Token)。集成者调用 Raydium 使用 2。如果该集成者被另一个集成者调用,则为 3。第 4 级是限制。 大多数组合轻松保持在此以下,但深层嵌套(聚合器 → 路由器 → Raydium → 钩子)可能会触及它。设计应该是扁平的而不是深层的。

剩余账户

当 Raydium 指令需要可变数量的账户时(例如,CLMM 交换跨越未知数量的 tick 数组),额外账户作为剩余账户传递——附加到固定账户列表,按位置解释。 CPMM 的 SwapV2 使用剩余账户来处理转移钩子程序的额外必需账户。客户端获取所需账户并附加它们:
在 CPI 级别,集成者必须通过自己的指令转发剩余账户:

PDA 陷阱

错误的种子 → 错误的地址

种子顺序错误、编码错误或包含/排除额外字节的错误会无声地产生不同的 PDA。交易失败得很模糊(程序尝试读取不存在的账户)。始终针对已知的黄金值对种子派生进行单元测试。

不存储 bump

如果你在每个交易上重新派生 bump,你需要为派生循环支付计算费用。在 PDA 的数据中存储规范 bump 并从那里读取它。

混淆规范与非规范 bump

非规范 bump(如果有人找到产生离曲线的)被 invoke_signed 允许但被 Raydium 的程序通过 assert_eq!(bump, canonical_bump) 拒绝。如果有人尝试用非规范 bump 声称 PDA,交易失败。

当你不是拥有程序时将 PDA 作为签署者传递

只有 PDA 派生中的程序 ID 所属的程序可以用其种子 invoke_signed。如果你尝试,运行时拒绝。

CPI 陷阱

忘记转发 remaining_accounts

如果你的外部指令在 remaining_accounts 中传递转移钩子账户,但 CPI 到 Raydium 不转发它们,Raydium 失败因为它找不到钩子账户。始终在需要它们的 CPI 中包含 with_remaining_accounts。

可写标志不匹配

外部指令标记为可写的账户在 CPI 调用中也必须是可写的,如果被调用程序打算写入它。不匹配 → 运行时拒绝。

不考虑租金

CPI 到创建账户的程序(例如,ATA 创建)需要支付者有足够的 SOL 来支付租金。失败的租金检查显示为模糊的错误。

实际示例:计算 Raydium CPMM PDA

这正是 Raydium SDK 在你调用 getPoolInfoFromRpc({ poolId }) 时在幕后所做的——它派生关联的 PDA 而无需往返。

指针

来源: