<h1 align="center">Pinocchio:适用于 Solana 程序的零依赖库</h1>

  • Anza
  • 发布于 2025-03-16 20:21
  • 阅读 6

本文介绍了 Pinocchio,一个用于在 Rust 中创建 Solana 程序的零依赖库。

<h1 align="center"> <code>pinocchio</code> </h1> <p align="center"> <img width="400" alt="Limestone" src="https://github.com/user-attachments/assets/3a1894b4-403f-4c35-90aa-548e7672fe90" /> </p> <p align="center"> 创建没有附加依赖的 Solana 程序。 </p>

<p align="center"> <a href="https://github.com/anza-xyz/pinocchio/actions/workflows/main.yml">&lt;img src="https://img.shields.io/github/actions/workflow/status/anza-xyz/pinocchio/main.yml?logo=GitHub" /></a> <a href="https://crates.io/crates/pinocchio">&lt;img src="https://img.shields.io/crates/v/pinocchio?logo=rust" /></a> </p>

<p align="right"> 我没有依赖<br /> 阻碍我<br /> 让我烦恼<br /> 或让我皱眉<br /> 我曾经有依赖<br /> 但现在我自由了<br /> 我没有任何依赖 </p>

概述

Pinocchio 是一个零依赖库,用于在 Rust 中创建 Solana 程序。它利用了 SVM 加载器将程序输入参数序列化为字节数组的方式,然后将其传递给程序的入口点,以定义零拷贝类型来读取输入。由于程序和 SVM 加载器之间的通信——无论是在第一次调用程序时,还是当一个程序调用另一个程序的指令时——都是通过字节数组完成的,因此程序可以定义自己的类型。这完全消除了对 solana-program crate 的依赖,从而通过专门设计用于创建链上程序的 crate 来缓解依赖问题。

因此,Pinocchio 可以用作 solana-program 的替代品来编写链上程序,从而在计算单元消耗和二进制文件大小方面进行优化。

该库定义了:

  • 程序入口点
  • 核心数据类型
  • 日志宏
  • syscall 函数
  • 访问系统账户 (sysvars)
  • 跨程序调用

特性

  • 零依赖和 no_std crate
  • 高效的 entrypoint! 宏 – 无需拷贝或分配
  • 改进的跨程序调用的 CU 消耗

开始使用

从你的项目文件夹中:

cargo add pinocchio

这会将 pinocchio 作为依赖项添加到你的项目中。

定义程序入口点

Solana 程序需要定义一个入口点,该入口点将由运行时调用以开始程序执行。entrypoint! 宏会发出常见的样板代码来设置程序入口点。该宏还将使用 default_allocator!default_panic_handler! 宏设置 全局分配器自定义 panic hook

entrypoint! 是一个便捷宏,它调用其他三个宏来设置程序执行所需的所有符号:

如果所有依赖项都是 no_std,则应追加 nostd_panic_handler! 以声明 rust 运行时 panic handler。如果任何依赖项是 std,则无需执行此操作,因为 rust 编译器将发出 std panic handler。

要使用 entrypoint! 宏,请在你的入口点定义中使用以下内容:

use pinocchio::{
  account_info::AccountInfo,
  entrypoint,
  msg,
  ProgramResult,
  pubkey::Pubkey
};

entrypoint!(process_instruction);

pub fn process_instruction(
  program_id: &Pubkey,
  accounts: &[AccountInfo],
  instruction_data: &[u8],
) -> ProgramResult {
  msg!("Hello from my program!"); // 来自我程序的问候!
  Ok(())
}

来自输入的信息被解析为它们自己的实体:

  • program_id:被调用程序的 ID
  • accounts:接收到的账户
  • instruction_data:指令的数据

pinocchio 还提供了程序入口点 (lazy_program_allocator) 和全局分配器 (no_allocator) 的变体。为了使用它们,程序需要单独指定程序入口点、全局分配器和 panic handler。entrypoint! 宏等效于编写:

program_entrypoint!(process_instruction);
default_allocator!();
default_panic_handler!();

这些宏中的任何一个都可以被其他实现替换,并且 pinocchio 为此提供了几个变体。

📌 lazy_program_entrypoint!

entrypoint! 宏看起来类似于 solana-program 中找到的“标准”宏。它解析整个输入并单独提供 program_idaccountsinstruction_data。这会在程序开始执行之前消耗计算单元。在某些情况下,程序可以更好地控制输入解析的发生时间,甚至控制是否需要解析——这就是 lazy_program_entrypoint! 宏的目的。此宏仅包装程序输入并提供按需解析输入的方法。

lazy_entrypoint 适用于具有单个或非常少的指令的程序,因为它需要程序处理解析,这可能会随着指令数量的增加而变得复杂。对于较大的程序,program_entrypoint! 可能会更容易且更有效。

要使用 lazy_program_entrypoint! 宏,请在你的入口点定义中使用以下内容:

use pinocchio::{
  default_allocator,
  default_panic_handler,
  entrypoint::InstructionContext,
  lazy_program_entrypoint,
  msg,
  ProgramResult
};

lazy_program_entrypoint!(process_instruction);
default_allocator!();
default_panic_handler!();

pub fn process_instruction(
  mut context: InstructionContext
) -> ProgramResult {
    msg!("Hello from my lazy program!"); // 来自我的 lazy 程序的问候!
    Ok(())
}

InstructionContext 提供对输入信息的按需访问:

  • available():可用账户数量
  • next_account():解析下一个可用账户(可以使用与可用账户一样多的次数)
  • instruction_data():解析指令数据
  • program_id():解析程序 id

⚠️ 注意: lazy_program_entrypoint! 不会设置全局分配器或 panic handler。程序应显式使用提供的宏之一来设置它们或包含其自己的实现。

📌 no_allocator!

在编写程序时,确保程序不尝试进行任何分配可能会很有用。对于这种情况,pinocchio 包含一个 no_allocator! 宏,该宏设置一个全局分配器,该分配器仅在任何尝试分配内存时发生 panic。

要使用 no_allocator! 宏,请在你的入口点定义中使用以下内容:

use pinocchio::{
  account_info::AccountInfo,
  default_panic_handler,
  msg,
  no_allocator,
  program_entrypoint,
  ProgramResult,
  pubkey::Pubkey
};

program_entrypoint!(process_instruction);
default_panic_handler!();
no_allocator!();

pub fn process_instruction(
  program_id: &Pubkey,
  accounts: &[AccountInfo],
  instruction_data: &[u8],
) -> ProgramResult {
  msg!("Hello from `no_std` program!"); // 来自 `no_std` 程序的问候!
  Ok(())
}

⚠️ 注意: no_allocator! 宏也可以与 lazy_program_entrypoint! 结合使用。

Crate feature: std

默认情况下,pinocchio 是一个 no_std crate。这意味着它不使用标准 (std) 库中的任何代码。虽然这不会影响 pinocchio 的使用方式,但有一个特别明显的区别。在 no_std 环境中,msg! 宏不提供任何格式化选项,因为 format! 宏需要 std 库。为了使用带有格式化的 msg!,应在添加 pinocchio 作为依赖项时启用 std 功能:

pinocchio = { version = "0.7.0", features = ["std"] }

建议使用 pinocchio-log crate,而不是启用 std 功能以使用 msg! 格式化日志消息。此 crate 提供了一个轻量级的 log! 宏,与标准 format! 宏相比,它具有更好的计算单元消耗,而无需 std 库。

高级入口点配置

入口点宏发出的符号——程序入口点、全局分配器和默认 panic handler——只能全局定义一次。如果程序 crate 也打算用作库,则通常的做法是在你的程序 crate 中定义一个 Cargo feature 以有条件地启用包含 entrypoint! 宏调用的模块。约定是将该 feature 命名为 bpf-entrypoint

#[cfg(feature = "bpf-entrypoint")]
mod entrypoint {
  use pinocchio::{
    account_info::AccountInfo,
    entrypoint,
    msg,
    ProgramResult,
    pubkey::Pubkey
  };

  entrypoint!(process_instruction);

  pub fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
  ) -> ProgramResult {
    msg!("Hello from my program!"); // 来自我程序的问候!
    Ok(())
  }
}

构建程序二进制文件时,必须启用 bpf-entrypoint feature:

cargo build-sbf --features bpf-entrypoint

许可证

该代码已获得 Apache License Version 2.0 的许可

此存储库中的库基于/包括来自以下位置的代码:

  • 原文链接: github.com/anza-xyz/pino...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Anza
Anza
江湖只有他的大名,没有他的介绍。