Anchor 中的跨程序调用

  • 0xE
  • 发布于 19小时前
  • 阅读 144

本文将详细讲解如何在 Anchor 框架中实现 CPI,重点展示调用另一个 Anchor 程序的过程,并以 Alice 调用 Bob 的示例说明。

跨程序调用(CPI)是指在 Solana 中一个程序调用另一个程序的公共函数。本文将详细讲解如何在 Anchor 框架中实现 CPI,重点展示调用另一个 Anchor 程序的过程,并以 Alice 调用 Bob 的示例说明。注意,本教程适用于 Anchor 程序,不适用于纯 Rust 开发的程序。


CPI 基础回顾

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);
    }
}

CpiContext 表示跨程序调用,调用其他程序的逻辑与之类似。


Bob 程序

首先创建 Bob 程序:

anchor init bob

将以下代码写入 bob/lib.rs:

use anchor_lang::prelude::*;
use std::mem::size_of;

declare_id!("BN2aLyQ8UJozgebuDVdwhY47VZA2RbS3HDRDz5zyYQqw");

#[program]
pub mod bob {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        msg!("Data Account Initialized: {}", 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>,
}

Bob 提供两个函数:initialize 初始化存储账户,add_and_store 将两个 u64 相加并存储。


Alice 程序

在 bob 项目中创建 Alice 程序:

anchor new alice

编辑 programs/alice/Cargo.toml,添加依赖:

[dependencies]
bob = { path = "../bob", features = ["cpi"] }

此依赖使 Alice 能访问 Bob 的函数和结构体,类似 Solidity 中的接口导入。

Alice 的完整代码如下:

use anchor_lang::prelude::*;
use bob::cpi::accounts::BobAddOp;
use bob::program::Bob;
use bob::BobData;

declare_id!("GaKz6s9cpFBNMTM56TE3uQLMJ3YefZp4VFiUnCkQT8fU");

#[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);

        if res.is_ok() {
            return Ok(());
        } else {
            return err!(Errors::CPIToBobFailed);
        }
    }
}

#[error_code]
pub enum Errors {
    #[msg("cpi to bob failed")]
    CPIToBobFailed,
}

#[derive(Accounts)]
pub struct AliceOp<'info> {
    #[account(mut)]
    pub bob_data_account: Account<'info, BobData>,
    pub bob_program: Program<'info, Bob>,
}

CPI 结构分析

与转账 SOL 的代码对比:

  • 目标程序:bob_program 或 system_program。
  • 账户列表:BobAddOp 或 Transfer 结构体。
  • 参数:a, b 或 amount。

CPI 需提供目标程序引用、所需账户和调用参数。


测试 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("CPI from Alice to Bob", () => {
  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("Is initialized!", async () => {
    const tx = await bobProgram.methods
      .initialize()
      .accounts({
        bobDataAccount: dataAccountKeypair.publicKey,
        signer: provider.wallet.publicKey,
        systemProgram: anchor.web3.SystemProgram.programId,
      })
      .signers([dataAccountKeypair])
      .rpc();
  });

  it("Can add numbers!", async () => {
    const tx = await aliceProgram.methods
      .askBobToAdd(new anchor.BN(4), new anchor.BN(2))
      .accounts({
        bobDataAccount: dataAccountKeypair.publicKey,
        bobProgram: bobProgram.programId,
      })
      .rpc();
  });

  it("Can assert value in Bob's data account equals 4 + 2", async () => {
    const BobAccountValue = (
      await bobProgram.account.bobData.fetch(dataAccountKeypair.publicKey)
    ).result.toNumber();
    expect(BobAccountValue).to.equal(6);
  });
});

测试流程

  1. 初始化 Bob 的数据账户。
  2. Alice 调用 add_and_store 计算 4 + 2。
  3. 验证结果为 6。

优化为一行 CPI

通过在 AliceOp 上实现 impl,可简化 CPI:

use anchor_lang::prelude::*;
use bob::cpi::accounts::BobAddOp;
use bob::program::Bob;
use bob::BobData;

declare_id!("GaKz6s9cpFBNMTM56TE3uQLMJ3YefZp4VFiUnCkQT8fU");

#[program]
pub mod alice {
    use super::*;

    pub fn ask_bob_to_add(ctx: Context<AliceOp>, a: u64, b: u64) -> Result<()> {
        let res = bob::cpi::add_and_store(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>> {
        let cpi_program = self.bob_program.to_account_info();
        let cpi_account = BobAddOp {
            bob_data_account: self.bob_data_account.to_account_info(),
        };
        CpiContext::new(cpi_program, cpi_account)
    }
}

#[error_code]
pub enum Errors {
    #[msg("cpi to bob failed")]
    CPIToBobFailed,
}

#[derive(Accounts)]
pub struct AliceOp<'info> {
    #[account(mut)]
    pub bob_data_account: Account<'info, BobData>,
    pub bob_program: Program<'info, Bob>,
}

此方法将 CPI 封装为一行,避免重复代码,提升可维护性。


总结

  • CPI 核心:提供目标程序、账户上下文和参数。
  • Anchor 集成:通过依赖导入目标程序的定义。
  • 优化方式:利用 impl 简化调用。

此机制适用于 Anchor 程序间的交互,确保高效的链上操作。


【笔记配套代码】 https://github.com/0xE1337/rareskills_evm_to_solana 【参考资料】 https://learnblockchain.cn/column/119 https://www.rareskills.io/solana-tutorial

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
0xE
0xE
0x59f6...a17e
17年进入币圈,Web3 开发者。刨根问底探链上真相,品味坎坷悟 Web3 人生。