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,或遇到具体的问题,欢迎继续提问。

相关推荐
SomeB1oody8 小时前
【RustyML入门】4.2. 标准化与归一化
开发语言·后端·机器学习·rust·教程
leoZ23116 小时前
Vue3 还原一个企业级后台-14-项目总结
开发语言·人工智能·后端·opencv·计算机视觉·数据挖掘·rust
特立独行的猫a18 小时前
Tauri v2的Rust应用 → HarmonyOS(鸿蒙 PC)移植30分钟速成指南
开发语言·rust·harmonyos·tauri·移植·鸿蒙pc
程序员爱钓鱼18 小时前
Rust 方法 Method详解:self、方法调用与API设计
后端·面试·rust
Patrick_Wilson1 天前
sccache 用在 Rust 上为什么常「不省编译」:原理、限制与 Windows 接入
ci/cd·rust·编译器
k4m7v2pz2 天前
rvs 26.8.43 → 26.8.53 更新概览:MCP 落地、审计增强与谬误澄清
rust·repl·mcp server·rust-verb-shell·rvs·shell 工具
程序员爱钓鱼2 天前
Rust impl详解:为Struct定义方法与关联函数
前端·后端·rust
k4m7v2pz2 天前
Rust 程序在 ReactOS 上“加载即崩“:从崩溃地址 0x6107104d 到 CRT 依赖的深度排查
rust·交叉编译·逆向工程·reactos·pe格式·windows兼容
Thneonl2 天前
一个人从 Rust 内核做到 React 前端:我做了一个云原生 SRE 巡检图谱桌面工具
rust
程序员爱钓鱼2 天前
Rust Struct结构体详解:定义自己的复杂数据类型
后端·面试·rust