10分钟Solana-性能web3-4.3 跨合约调用

欢迎订阅专栏10分钟Solana-性能web3

在 Solana 生态中,跨合约调用 通常被称为 跨程序调用(CPI,Cross-Program Invocation) 。它允许一个 Solana 程序(智能合约)调用另一个程序的公开指令,从而实现可组合性------这是 Solana 和 DeFi 的核心特性之一。


🌐 为什么需要 CPI?

Solana 的程序是无状态 的(逻辑与数据分离),任何程序都无法直接修改另一个程序的数据。但通过 CPI,一个程序可以代表其自身或由其派生的 PDA,向另一个程序发起一个完整的指令调用,并传递必要的账户和参数。

CPI 让开发者能够:

  • 重用已有的协议:例如,从你自己的程序中调用 SPL Token 程序进行转账。
  • 组合多个操作:在一个交易中原子性地完成多个步骤(例如,在 DEX 中交换代币,并在同一个程序中更新用户状态)。
  • 实现代理签名:利用 PDA 代表程序进行授权操作(如转移 NFT)。

⚙️ CPI 的两种模式

根据被调用程序返回结果的方式,CPI 分为两种:

类型 描述 使用场景
同步 CPI 当前程序暂停执行,等待被调用程序执行完毕并返回结果后,再继续执行后续代码。 绝大多数情况,如转账、代币操作等。
异步 CPI 当前程序继续执行 ,不等待被调用程序完成。目前 Solana 不支持异步 CPI。 --

🧩 如何在程序内发起 CPI

在 Solana 中,发起 CPI 可以通过原生 Rust SDK 或使用 Anchor 框架 提供的封装。

1. 使用原生 solana_program SDK

原生方式直接使用 invokeinvoke_signed 函数。

rust 复制代码
use solana_program::{
    account_info::AccountInfo,
    program::invoke,
    system_instruction,
    pubkey::Pubkey,
};

// 调用系统程序转账 SOL
pub fn transfer_sol(
    from: &AccountInfo,
    to: &AccountInfo,
    amount: u64,
) -> ProgramResult {
    let ix = system_instruction::transfer(from.key, to.key, amount);
    invoke(&ix, &[from.clone(), to.clone()])
}
  • invoke:用于普通 CPI,账户签名由当前程序外的签名者(如用户)提供。
  • invoke_signed :用于 PDA 签名,需要提供 PDA 的种子和 bump
rust 复制代码
// 使用 PDA 作为签名者调用 SPL Token 程序的 mint_to 指令
let seeds = &[b"mint_authority".as_ref(), &[bump]];
invoke_signed(
    &spl_token::instruction::mint_to(
        token_program.key,
        mint_account.key,
        destination.key,
        authority.key,
        &[], // 无额外 signers
        amount,
    )?,
    &[mint_account.clone(), destination.clone(), authority.clone()],
    &[seeds],
)?;

2. 使用 Anchor 框架

Anchor 提供了更高级的封装,自动处理账户序列化和安全检查

  • cpi 模块:Anchor 为每个依赖的程序生成一个 CPI 模块,其中包含了该程序所有指令的辅助函数。
  • 调用方式 :在 Anchor 程序内部,通过 CpiContextctx.accounts 发起调用。

示例:调用 SPL Token 程序 (需添加 anchor-spl 依赖)

rust 复制代码
use anchor_spl::token::{Token, MintTo, mint_to};

#[program]
pub mod my_project {
    use super::*;
    pub fn mint_tokens(ctx: Context<MintTokens>, amount: u64) -> Result<()> {
        let cpi_program = ctx.accounts.token_program.to_account_info();
        let cpi_accounts = MintTo {
            mint: ctx.accounts.mint.to_account_info(),
            to: ctx.accounts.destination.to_account_info(),
            authority: ctx.accounts.mint_authority.to_account_info(),
        };
        let seeds = &[b"mint_auth".as_ref(), &[ctx.bumps.mint_authority]];
        let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, &[seeds]);
        mint_to(cpi_ctx, amount)?;
        Ok(())
    }
}

关键点

  • CpiContext::new_with_signer 用于需要 PDA 签名的场景。
  • Anchor 会自动验证 ctx.bumps 是否有效。

🔐 安全性要点

CPI 是 Solana 程序中最容易引入漏洞的环节之一,务必注意以下几点:

  1. 目标程序地址验证 :确保你调用的是预期的程序(例如,检查其 key 是否等于已知的可靠程序 ID)。否则可能被重定向到恶意程序。

    rust 复制代码
    if ctx.accounts.token_program.key != &spl_token::ID {
        return Err(ProgramError::InvalidArgument);
    }
  2. 账户所有权验证:在 CPI 之前,确保传入的账户是调用方程序拥有的,避免被攻击者伪造。

  3. PDA 签名验证 :使用 invoke_signed 或 Anchor 的 CpiContext::new_with_signer 时,务必传入正确的种子和 bump。不正确的种子可能导致程序非法使用他人的 PDA。

  4. 重入防护 :虽然 Solana 的 CPI 是同步的(当前程序会等待被调用程序返回),但仍需注意状态一致性。例如,确保在调用外部程序之前已完成所有内部状态更新,防止重入攻击(尽管 Solana 的账户模型天然降低了部分风险,但并非绝对)。

  5. 计算单元 (CU) 消耗:CPI 会增加交易的计算成本。过多的嵌套调用可能导致交易超出 CU 上限,提前做好预估。


📦 常用 CPI 场景与库

  • anchor-spl:与 SPL Token、Associated Token Account 等官方程序交互的标准库。
  • pump-fun-sdk:与 pump.fun 协议交互的社区库。
  • 自定义程序:只要另一个程序公开了 IDL,你就能通过 Anchor 生成对应的 CPI 模块。

🧪 测试 CPI

在 Anchor 测试中,你可以通过 program 对象的 cpi 方法进行模拟调用,或者利用 solana-program-test 框架进行集成测试。通常,推荐使用 主网分叉(fork) 测试来验证 CPI 的行为与主网一致。


💎 总结

跨程序调用(CPI)是 Solana 可组合性的基石。它让开发者能够:

  • 重用现有的协议逻辑
  • 通过 PDA 实现安全授权
  • 组合多个操作完成复杂业务

最佳实践

  1. 使用 Anchor 提供的 CpiContextcpi 模块,减少手动序列化错误。
  2. 验证目标程序地址,防止重定向攻击。
  3. 仔细管理 PDA 签名,确保种子正确。
  4. 在测试网进行充分的 CPI 测试,涵盖正常路径和错误路径。

CPI 是一个相对进阶的话题,但一旦掌握,它将为你打开构建复杂 Solana 应用的大门。如果想了解如何与特定协议进行 CPI,或遇到具体的问题,欢迎继续提问。

相关推荐
磐链科技1 小时前
链游技术服务全景透视:从基建到工具链的实战指南
架构·web3·区块链
程序员爱钓鱼3 小时前
第一个 Rust 程序 Hello World
后端·rust
花褪残红青杏小11 小时前
Rust图像处理第14节-图片缩放与斜切畸变:齐次坐标 + 共享仿射变换
rust·webassembly·图形学
梦帮科技13 小时前
GRAVIS v4.0:基于Web的极速套利架构设计与实时数据流实现
前端·人工智能·rust·自动化·区块链·智能合约·数字货币
花褪残红青杏小20 小时前
Rust图像处理第13节-任意角度旋转:旋转矩阵 + 双线性插值 + 自动补白边
rust·webassembly·图形学
小杍随笔1 天前
【Rust + Argon2id 生产级密码管理模块设计与实现】
网络·后端·算法·rust
梦帮科技1 天前
GRAVIS v5.5:向硬核桌面端进化与云端多节点容灾部署实践
windows·架构·rust·自动化·区块链·智能合约·数字货币
梦醒沉醉1 天前
2、Rust程序设计语言——常见编程概念
rust
程序员爱钓鱼1 天前
Rust 开发环境安装(Windows|macOS|Linux)
后端·rust