LogoAnchor 中文文档

跨程序调用

了解如何在 Anchor 程序中实现跨程序调用(CPI),以实现不同 Solana 程序之间的可组合性。

跨程序调用(CPI:Cross Program Invocation)指的是一个程序调用另一个程序的指令的过程,这使得 Solana 程序之间具备了可组合性。

本节将介绍在 Anchor 程序中实现 CPI 的基础知识,使用一个简单的 SOL 转账指令作为实际示例。一旦你理解了如何实现 CPI 的基础知识,你就可以将这些概念应用到任何指令中。

跨程序调用

让我们来看一个调用系统程序转账指令的 CPI 程序。以下是 Solana Playground 上的示例程序。

lib.rs 文件包含了一个 sol_transfer 指令。当调用 Anchor 程序上的 sol_transfer 指令时,程序内部会调用系统程序的转账指令。

lib.rs
use anchor_lang::prelude::*;
use anchor_lang::system_program::{transfer, Transfer};
 
declare_id!("9AvUNHjxscdkiKQ8tUn12QCMXtcnbR9BVGq3ULNzFMRi");
 
#[program]
pub mod cpi {
    use super::*;
 


    pub fn sol_transfer(ctx: Context<SolTransfer>, amount: u64) -> Result<()> {
        let from_pubkey = ctx.accounts.sender.to_account_info();
        let to_pubkey = ctx.accounts.recipient.to_account_info();
        let program_id = ctx.accounts.system_program.to_account_info();
 
        let cpi_context = CpiContext::new(
            program_id,
            Transfer {
                from: from_pubkey,
                to: to_pubkey,
            },
        );


        transfer(cpi_context, amount)?;
        Ok(())
    }
}
 
#[derive(Accounts)]
pub struct SolTransfer<'info> {
    #[account(mut)]
    sender: Signer<'info>,
    #[account(mut)]
    recipient: SystemAccount<'info>,
    system_program: Program<'info, System>,
}

cpi.test.ts 文件展示了如何调用 Anchor 程序的 sol_transfer 指令,并在 SolanaFM 上记录交易详情的链接。

cpi.test.ts
it("SOL Transfer Anchor", async () => {
  const transactionSignature = await program.methods
    .solTransfer(new BN(transferAmount))
    .accounts({
      sender: sender.publicKey,
      recipient: recipient.publicKey,
    })
    .rpc();
 
  console.log(
    `\nTransaction Signature:` +
      `https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`,
  );
});

你可以在 Playground 上构建、部署并运行此示例的测试,以查看 SolanaFM 浏览器 上的交易详情。

交易详情将显示首先调用了 Anchor 程序(指令 1),然后调用了系统程序(指令 1.1),最终完成了 SOL 转账。

交易详情

示例解析

跨程序调用(CPI)允许一个程序调用另一个程序的指令。实现 CPI 的过程与创建指令的过程相同,你必须指定:

  1. 被调用程序的程序 ID
  2. 指令所需的账户
  3. 作为参数所需的任何指令数据

这种模式确保 CPI 具备调用目标程序指令所需的所有信息。

系统程序的转账指令需要两个账户:

  • from:发送 SOL 的账户。
  • to:接收 SOL 的账户。

在示例程序中,SolTransfer 结构体指定了转账指令所需的账户。系统程序也被包括在内,因为 CPI 调用了系统程序。




#[derive(Accounts)]
pub struct SolTransfer<'info> {
    #[account(mut)]
    sender: Signer<'info>, // 发送账户
    #[account(mut)]
    recipient: SystemAccount<'info>, // 接收账户
    system_program: Program<'info, System>, // 程序 ID
}

以下选项卡展示了三种实现跨程序调用(CPI)的方法,每种方法的抽象程度不同。所有示例在功能上都是等价的。其主要目的是展示 CPI 的实现细节。

示例代码中的 sol_transfer 指令展示了使用 Anchor 框架构建 CPI 的典型方法。

此方法涉及创建一个 CpiContext,其中包括被调用指令的程序 ID 和所需账户。然后将 CpiContext 传递给一个 Anchor 辅助函数 transfer,以调用特定指令。

use anchor_lang::system_program::{transfer, Transfer};
pub fn sol_transfer(ctx: Context<SolTransfer>, amount: u64) -> Result<()> {
    let from_pubkey = ctx.accounts.sender.to_account_info();
    let to_pubkey = ctx.accounts.recipient.to_account_info();
    let program_id = ctx.accounts.system_program.to_account_info();
 

    let cpi_context = CpiContext::new(
        program_id,
        Transfer {
            from: from_pubkey,
            to: to_pubkey,
            },
    );
 

    transfer(cpi_context, amount)?;
    Ok(())
}

cpi_context 变量指定了转账指令所需的程序 ID(系统程序)和账户(发送者和接收者)。

let cpi_context = CpiContext::new(
    program_id,
    Transfer {
        from: from_pubkey,
        to: to_pubkey,
            },
);

然后将 cpi_contextamount 传递给 transfer 函数,以执行调用系统程序转账指令的 CPI。

transfer(cpi_context, amount)?;

这里是一个包含所有 3 个示例的参考程序,位于 Solana Playground

PDA 签名者的跨程序调用

接下来,我们来看一个实现了 CPI 的程序,该程序调用了系统程序的转账指令,其中发送者是一个程序派生地址(PDA),程序必须为其签名。在该示例中,PDA 的地址是使用硬编码字符串 "pda" 和 recipient 账户的地址派生的,因此在示例中,该地址对于每个 recipient 都是唯一的。整个示例位于 Solana Playground

PDA 签名者的跨程序调用

接下来,我们来看一个实现了 CPI 的程序,该程序调用了系统程序的转账指令,其中发送者是一个程序派生地址(PDA),程序必须为其签名。以下是 Solana Playground 上的示例程序。

lib.rs 文件包含以下程序,其中有一个 sol_transfer 指令。

lib.rs
use anchor_lang::prelude::*;
use anchor_lang::system_program::{transfer, Transfer};
 
declare_id!("3455LkCS85a4aYmSeNbRrJsduNQfYRY82A7eCD3yQfyR");
 
#[program]
pub mod cpi {
    use super::*;
 


    pub fn sol_transfer(ctx: Context<SolTransfer>, amount: u64)

On this page

在GitHub上编辑