在 Anchor 中的跨程序调用(CPI)
- 原文链接:www.rareskills.io/post...
- 译者:AI翻译官,校对:翻译小组
- 本文链接:learnblockchain.cn/article…
跨程序调用 (CPI) 是 Solana 的术语,用于描述一个程序调用另一个程序的公共函数。
我们之前已经进行过 CPI,当我们向系统程序发送一个 转账 SOL 交易。以下是相关代码片段,以作提醒:
pub fn send_sol(ctx: Context<SendSol>, amount: u64) -> Result<()> {
let cpi_context = CpiContext::new(
ctx.accounts.system_program.to_account_info(),
system_program::Transfer {
from: ctx.accounts.signer.to_account_info(),
to: ctx.accounts.recipient.to_account_info(),
}
);
let res = system_program::transfer(cpi_context, amount);
if res.is_ok() {
return Ok(());
} else {
return err!(Errors::TransferFailed);
}
}
Cpi
在 CpiContext
中字面上的意思是“跨程序调用”。
调用除系统程序外的其他程序的公共函数的工作流程并没有太大的不同——我们将在本教程中教授这一点。
本教程只关注如何调用另一个使用 Anchor 构建的 Solana 程序。如果其他程序是用纯 Rust 开发的,则以下指南将无法使用。
在我们的示例中,Alice
程序将调用 Bob
程序上的一个函数。
我们从使用 Anchor 的 CLI 创建一个新项目开始:
anchor init bob
然后在 bob/lib.rs
中复制并粘贴下面的代码。该账户有两个函数,一个用于初始化存储一个 u64
的账户,另一个函数 add_and_store
接受两个 u64
变量,将它们相加并存储在由结构体 BobData
定义的账户中。
use anchor_lang::prelude::*;
use std::mem::size_of;
// 替换为你的 <PROGRAM_ID>
declare_id!("8GYu5JYsvAYoinbFTvW4AACYB5GxGstz21FmZe3MNFn4");
#[program]
pub mod bob {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
msg!("数据账户已初始化: {}", ctx.accounts.bob_data_account.key());
Ok(())
}
pub fn add_and_store(ctx: Context<BobAddOp>, a: u64, b: u64) -> Result<()> {
let result = a + b;
// 修改/更新数据账户
ctx.accounts.bob_data_account.result = result;
Ok(())
}
}
#[account]
pub struct BobData {
pub result: u64,
}
#[derive(Accounts)]
pub struct BobAddOp<'info> {
#[account(mut)]
pub bob_data_account: Account<'info, BobData>,
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = signer, space = size_of::<BobData>() + 8)]
pub bob_data_account: Account<'info, BobData>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
本教程的目标是创建另一个程序 alice
,调用 bob.add_and_store
。
在项目(bob)内,使用 anchor new
命令创建一个新程序:
anchor new alice
命令行将打印出 创建了新程序
。
在开始编写 Alice 的程序之前,必须将下面的代码片段添加到 Alice 的 Cargo.toml 文件的 [dependencies] 部分,位置为 programs/alice/Cargo.toml
。
[dependencies]
bob = {path = "../bob", features = ["cpi"]}
Anchor 在这里做了大量的后台工作。Alice 现在可以访问 Bob 的公共函数和 Bob 的结构体的定义。你可以将这视为在 Solidity 中导入接口,以便知道如何与另一个合约进行交互。
下面是 Alice
程序。 在代码顶部,Alice 程序正在导入承载 BobAddOp
(用于 add_and_store
)的结构体。请注意代码中的注释:
use anchor_lang::prelude::*;
// account struct for add_and_store
use bob::cpi::accounts::BobAddOp;
// Bob 的程序定义
use bob::program::Bob;
// Bob 存储和的账户
use bob::BobData;
declare_id!("6wZDNWprmb9TAZYMAPpT23kHDPABvBLT8jbWQKLHEmBy");
#[program]
pub mod alice {
use super::*;
pub fn ask_bob_to_add(ctx: Context<AliceOp>, a: u64, b: u64) -> Result<()> {
let cpi_ctx = CpiContext::new(
ctx.accounts.bob_program.to_account_info(),
BobAddOp {
bob_data_account: ctx.accounts.bob_data_account.to_account_info(),
}
);
let res = bob::cpi::add_and_store(cpi_ctx, a, b);
// 如果 CPI 失败则返回错误
if res.is_ok() {
return Ok(());
} else {
return err!(Errors::CPIToBobFailed);
}
}
}
#[error_code]
pub enum Errors {
#[msg("cpi to bob 失败")]
CPIToBobFailed,
}
#[derive(Accounts)]
pub struct AliceOp<'info> {
#[account(mut)]
pub bob_data_account: Account<'info, BobData>,
pub bob_program: Program<'info, Bob>,
}
如果我们将 ask_bob_to_add
与本文顶部展示的转账 SOL 的代码片段进行比较,会发现很多相似之处。
要实现 CPI,以下内容是必需的:
AccountInfo
)(红框)ctx
结构体)(绿框)以下 TypeScript 代码可用于测试 CPI:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { Bob } from "../target/types/bob";
import { Alice } from "../target/types/alice";
import { expect } from "chai";
describe("从 Alice 到 Bob 的 CPI", () => {
const provider = anchor.AnchorProvider.env();
// 配置客户端以使用本地集群。
anchor.setProvider(provider);
const bobProgram = anchor.workspace.Bob as Program<Bob>;
const aliceProgram = anchor.workspace.Alice as Program<Alice>;
const dataAccountKeypair = anchor.web3.Keypair.generate();
it("已初始化!", async () => {
// 在这里添加测试。
const tx = await bobProgram.methods
.initialize()
.accounts({
bobDataAccount: dataAccountKeypair.publicKey,
signer: provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.signers([dataAccountKeypair])
.rpc();
});
it("可以相加然后加倍!", async () => {
// 在这里添加测试。
const tx = await aliceProgram.methods
.askBobToAddThenDouble(new anchor.BN(4), new anchor.BN(2))
.accounts({
bobDataAccount: dataAccountKeypair.publicKey,
bobProgram: bobProgram.programId,
})
.rpc();
});
it("可以断言 Bob 的数据账户中的值等于 4 + 2", async () => {
const BobAccountValue = (
await bobProgram.account.bobData.fetch(dataAccountKeypair.publicKey) ).result.toNumber();
expect(BobAccountValue).to.equal(6);
});
});
因为传递给 Alice 的 ctx 账户包含进行交易所需的所有账户的引用,我们可以在该结构体的impl
内创建一个函数来完成 CPI。请记住,所有impl
将“附加”函数到可以使用结构体中的数据的结构体。由于ctx
结构体AliceOp
已经持有Bob
进行交易所需的所有账户,我们可以将所有 CPI 代码移动:
let cpi_ctx = CpiContext::new(
ctx.accounts.bob_program.to_account_info(),
BobAddOp {
bob_data_account: ctx.accounts.bob_data_account.to_account_info(),
}
);
到一个impl
中,如下所示:
let cpi_ctx = CpiContext::new(
ctx.accounts.bob_program.to_account_info(),
BobAddOp {
bob_data_account: ctx.accounts.bob_data_account.to_account_info(),
}
);
use anchor_lang::prelude::*;
use bob::cpi::accounts::BobAddOp;
use bob::program::Bob;
use bob::BobData;
// 用你的<PROGRAM_ID>替换 declare_id!("B2BNs2GecG8Ux5EchDDFZakRWX2NDfy1RDhPCTJuJtr5");
#[program]
pub mod alice {
use super::*;
pub fn ask_bob_to_add(ctx: Context<AliceOp>, a: u64, b: u64) -> Result<()> {
// 调用 bob 程序中的`bob_add_operation`函数
let res = bob::cpi::bob_add_operation(ctx.accounts.add_function_ctx(), a, b);
if res.is_ok() {
return Ok(());
} else {
return err!(Errors::CPIToBobFailed);
}
}
}
impl<'info> AliceOp<'info> {
pub fn add_function_ctx(&self) -> CpiContext<'_, '_, '_, 'info, BobAddOp<'info>> {
// 我们正在与之交互的 bob 程序
let cpi_program = self.bob_program.to_account_info();
// 将所需账户传递给 Bob 程序中的`BobAddOp`账户结构体
let cpi_account = BobAddOp {
bob_data_account: self.bob_data_account.to_account_info(),
};
// 使用新方法创建`CpiContext`对象
CpiContext::new(cpi_program, cpi_account)
}
}
#[error_code]
pub enum Errors {
#[msg("cpi to bob 失败")]
CPIToBobFailed,
}
#[derive(Accounts)]
pub struct AliceOp<'info> {
#[account(mut)]
pub bob_data_account: Account<'info, BobData>,
pub bob_program: Program<'info, Bob>,
}
我们能够以“一行”的方式调用Bob
的 CPI。这在 Alice 程序的其他部分调用 Bob 的 CPI 时可能很方便——将代码移动到impl
中可以防止我们复制和粘贴代码来创建CpiContext
。
本教程是学习 Solana 开发系列的一部分。
最初发布于 2024 年 5 月 17 日
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!