极致轻量:SolanaPinocchiov0.10演进实战,0.15s极速开启托管合约新篇章在Solana极简开发领域,Pinocchio框架一直以其“零抽象”带来的性能统治力著称。然而,随着v0.10.1版本的发布,这场关于极致性能的实验迎来了一个关键分水岭。本文将带你深度复
在 Solana 极简开发领域,Pinocchio 框架一直以其“零抽象”带来的性能统治力著称。然而,随着 v0.10.1 版本的发布,这场关于极致性能的实验迎来了一个关键分水岭。
本文将带你深度复盘一个 Escrow(托管合约) 的进化之旅。我们将从 v0.9.2 的底层指针操作出发,一路跨越到 v0.10.1 全面拥抱 AccountView 与 Address 新架构的现代实战。你将看到,Pinocchio 如何在不损失任何性能的前提下,通过对 Helper 库的重构和指令逻辑的封装,将安全性和开发体验提升到一个新高度。最后,那惊人的 0.15s 编译速度,将告诉你为什么 Pinocchio 是 Solana 开发者追求物理极限的终极选择。
本文聚焦 Solana 轻量级框架 Pinocchio v0.10.1 的重大进化。通过 Escrow 托管合约实战,深度解析 AccountView 架构、Address 类型迁移及安全回收账户等核心特性。带你见证 Pinocchio 如何在保持 0.15s 极速编译的同时,通过重构指令逻辑实现性能与安全的双重突破。
在深入代码前,我们先明确 Escrow 程序的逻辑流:
cargo new --lib solana_pinocchio_escrow
cd solana_pinocchio_escrow
pinocchio-escrow-workspace/programs/solana_pinocchio_escrow on main is 📦 0.1.0 via 🦀 1.92.0
➜ tree . -L 6 -I ".gitignore|.github|.git|target"
.
├── Cargo.toml
├── Makefile
└── src
├── errors.rs
├── instructions
│ ├── helpers.rs
│ ├── make.rs
│ ├── mod.rs
│ ├── refund.rs
│ └── take.rs
├── lib.rs
└── state.rs
3 directories, 10 files
Cargo.toml 文件Cargo.toml: 依赖管理。
cargo-features = ["edition2024"]
[package]
name = "solana_pinocchio_escrow"
version = "0.1.0"
edition = "2024"
license = "MIT"
[lib]
crate-type = ["lib", "cdylib"]
name = "solana_pinocchio_escrow"
[dependencies]
pinocchio = "0.9.2"
pinocchio-associated-token-account = "0.2.0"
pinocchio-system = "0.4.0"
pinocchio-token = "0.4.0"
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(target_os, values("solana"))',
] }
这段 Cargo.toml 配置展示了基于 Pinocchio v0.9.2 的轻量化 Solana 项目基础,它率先采用了 Rust 2024 Edition(需开启 cargo-features)以利用最新的语言特性,并通过 cdylib 输出格式确保程序能正确编译为链上 BPF 字节码;同时,配置中集成了一系列专门针对底层开发优化的 Pinocchio 扩展库,并特别通过 [lints.rust] 解决了跨平台编译时针对 solana 目标系统的配置警告,为追求极致性能和现代语法的开发者提供了一个整洁、高效的工程起点。
.so 文件)。unexpected_cfgs 这种讨厌的警告。src/lib.rs 文件入口与指令分发
use pinocchio::{
ProgramResult, account_info::AccountInfo, entrypoint, program_error::ProgramError,
pubkey::Pubkey,
};
entrypoint!(process_instruction);
pub mod instructions;
pub use instructions::*;
pub mod state;
pub use state::*;
// 22222222222222222222222222222222222222222222
pub const ID: Pubkey = [
0x0f, 0x1e, 0x6b, 0x14, 0x21, 0xc0, 0x4a, 0x07, 0x04, 0x31, 0x26, 0x5c, 0x19, 0xc5, 0xbb, 0xee,
0x19, 0x92, 0xba, 0xe8, 0xaf, 0xd1, 0xcd, 0x07, 0x8e, 0xf8, 0xaf, 0x70, 0x47, 0xdc, 0x11, 0xf7,
];
fn process_instruction(
_program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
match instruction_data.split_first() {
Some((Make::DISCRIMINATOR, data)) => Make::try_from((data, accounts))?.process(),
Some((Take::DISCRIMINATOR, _)) => Take::try_from(accounts)?.process(),
Some((Refund::DISCRIMINATOR, _)) => Refund::try_from(accounts)?.process(),
_ => Err(ProgramError::InvalidInstructionData),
}
}
这段代码构成了 Pinocchio v0.9.2 程序的核心入口与指令分发器:它通过 entrypoint! 宏接入 Solana 运行时,手动定义了程序的唯一 ID,并利用 match 模式匹配对输入指令数据的首字节进行鉴别(Discriminator),从而将链上请求精准路由至 Make、Take 或 Refund 模块,完成从原始 AccountInfo 账户切片到具体业务逻辑的转换与执行。
src/state.rs 文件数据结构定义
use core::mem::size_of;
use pinocchio::{program_error::ProgramError, pubkey::Pubkey};
// --- 定义常量种子 ---
pub const ESCROW_SEED: &[u8] = b"escrow";
#[repr(C)]
pub struct Escrow {
pub seed: u64, // Random seed for PDA derivation
pub maker: Pubkey, // Creator of the escrow
pub mint_a: Pubkey, // Token being deposited
pub mint_b: Pubkey, // Token being requested
pub receive: u64, // Amount of token B wanted
pub bump: [u8; 1], // PDA bump seed
}
impl Escrow {
pub const LEN: usize = size_of::<u64>()
+ size_of::<Pubkey>()
+ size_of::<Pubkey>()
+ size_of::<Pubkey>()
+ size_of::<u64>()
+ size_of::<[u8; 1]>();
#[inline(always)]
pub fn load_mut(bytes: &mut [u8]) -> Result<&mut Self, ProgramError> {
if bytes.len() != Escrow::LEN {
return Err(ProgramError::InvalidAccountData);
}
Ok(unsafe { &mut *core::mem::transmute::<*mut u8, *mut Self>(bytes.as_mut_ptr()) })
}
#[inline(always)]
pub fn load(bytes: &[u8]) -> Result<&Self, ProgramError> {
if bytes.len() != Escrow::LEN {
return Err(ProgramError::InvalidAccountData);
}
Ok(unsafe { &*core::mem::transmute::<*const u8, *const Self>(bytes.as_ptr()) })
}
#[inline(always)]
pub fn set_seed(&mut self, seed: u64) {
self.seed = seed;
}
#[inline(always)]
pub fn set_maker(&mut self, maker: Pubkey) {
self.maker = maker;
}
#[inline(always)]
pub fn set_mint_a(&mut self, mint_a: Pubkey) {
self.mint_a = mint_a;
}
#[inline(always)]
pub fn set_mint_b(&mut self, mint_b: Pubkey) {
self.mint_b = mint_b;
}
#[inline(always)]
pub fn set_receive(&mut self, receive: u64) {
self.receive = receive;
}
#[inline(always)]
pub fn set_bump(&mut self, bump: [u8; 1]) {
self.bump = bump;
}
#[inline(always)]
pub fn set_inner(
&mut self,
seed: u64,
maker: Pubkey,
mint_a: Pubkey,
mint_b: Pubkey,
receive: u64,
bump: [u8; 1],
) {
self.seed = seed;
self.maker = maker;
self.mint_a = mint_a;
self.mint_b = mint_b;
self.receive = receive;
self.bump = bump;
}
}
这段代码定义了 Escrow 状态结构体,并利用 #[repr(C)] 确保内存布局的确定性,这是实现 Zero-Copy(零拷贝) 高性能模式的核心。通过在 load 和 load_mut 函数中使用 core::mem::transmute,程序能够将原始账户字节流直接“映射”为结构体引用,彻底省去了传统序列化(如 Borsh)带来的计算开销;配合 size_of 静态计算的精确长度校验,该实现可以在极致节省 Compute Units (CU) 的前提下,安全且高效地管理托管合约的交易条款。
#[repr(C)]? 因为 Rust 默认的结构体布局是不确定的,加了它才能保证字段顺序和字节对齐与你定义的完全一致,从而安全地进行指针强转。unsafe: 在底层框架如 Pinocchio 中,使用 unsafe 进行零拷贝是提升性能的标准做法,它让程序直接在内存“视窗”上操作数据,而不是在内存中搬运数据。src/instructions/helpers.rs 文件使用 unsafe 余额修改
use pinocchio::instruction::{Seed, Signer};
use pinocchio::program_error::ProgramError;
use pinocchio::{ProgramResult, account_info::AccountInfo};
use pinocchio_associated_token_account::instructions::CreateIdempotent;
use pinocchio_system::instructions::CreateAccount;
use pinocchio_token::state::TokenAccount;
// --- 1. 签名者检查助手 ---
pub struct SignerAccount;
impl SignerAccount {
#[inline(always)]
pub fn check(account: &AccountInfo) -> Result<(), ProgramError> {
if !account.is_signer() {
return Err(ProgramError::MissingRequiredSignature);
}
Ok(())
}
}
// --- 2. 程序账户 (PDA/State) 助手 ---
pub struct ProgramAccount;
impl ProgramAccount {
#[inline(always)]
/// 初始化程序状态账户 (如 Escrow)
pub fn init<T>(
payer: &AccountInfo, // 1. 支付租金的人
new_account: &AccountInfo, // 2. 要创建的 PDA
signer_seeds: &[Seed], // 3. PDA 种子
space: usize, // 4. 空间大小
lamports: u64,
) -> ProgramResult {
// 计算租金 (这里假设你已经算好了 lamports,或者调用系统程序计算)
// 最简单的做法是调用 pinocchio_system 的 CreateAccount
CreateAccount {
from: payer,
to: new_account,
lamports,
space: space as u64,
owner: &crate::ID,
}
.invoke_signed(&[Signer::from(signer_seeds)])
}
/// 检查该账户是否由本程序拥有
#[inline(always)]
pub fn check(account: &AccountInfo) -> Result<(), ProgramError> {
if account.owner() != &crate::ID {
return Err(ProgramError::InvalidAccountOwner);
}
Ok(())
}
/// 关闭账户并回收 Lamports (常用于 Refund/Take)
pub fn close(account: &AccountInfo, destination: &AccountInfo) -> ProgramResult {
// 将 lamports 转移给接收者
let lamports = account.lamports();
// 2. 手动转移 Lamports
// 注意:在 Pinocchio 0.9.2 源码中,修改 lamports 需要通过 unsafe 的 unchecked 方法
// 或者使用 try_borrow_mut_lamports (会增加 CU 开销)
// 鉴于 Pinocchio 追求底层效率,这里使用源码提供的 unchecked 方式:
unsafe {
// 将原账户余额清零
*account.borrow_mut_lamports_unchecked() = 0;
// 将余额累加到接收者账户
*destination.borrow_mut_lamports_unchecked() += lamports;
}
// 清理数据并将所有者重置为系统程序
account.close()
}
}
// --- 3. Mint (代币定义) 助手 ---
pub struct MintInterface;
impl MintInterface {
#[inline(always)]
pub fn check(account: &AccountInfo) -> Result<(), ProgramError> {
// SPL Token Mint 固定长度为 82
// 且必须由 Token Program 拥有
if account.data_len() != 82 {
// SPL Token Mint 固定长度
return Err(ProgramError::InvalidAccountData);
}
Ok(())
}
}
// --- 4. 关联代币账户 (ATA) 助手 ---
pub struct AssociatedTokenAccount;
impl AssociatedTokenAccount {
pub fn init(
funding_account: &AccountInfo, // 1. 出钱的人 (Payer/Signer)
account: &AccountInfo, // 2. 要创建的 ATA 地址
wallet: &AccountInfo, // 3. 所有者 (Owner,即这个 ATA 归谁管)
mint: &AccountInfo, // 4. Mint
system_program: &AccountInfo, // 5. 系统程序
token_program: &AccountInfo, // 6. 代币程序
) -> ProgramResult {
CreateIdempotent {
funding_account,
account,
wallet,
mint,
system_program,
token_program,
}
.invoke()
}
pub fn init_if_needed(
account: &AccountInfo,
mint: &AccountInfo,
funding_account: &AccountInfo, // 教程传入的第3个参数
wallet: &AccountInfo,
system_program: &AccountInfo,
token_program: &AccountInfo,
) -> ProgramResult {
CreateIdempotent {
funding_account,
account,
wallet,
mint,
system_program,
token_program,
}
.invoke()
}
pub fn check(
ata: &AccountInfo,
owner: &AccountInfo,
mint: &AccountInfo,
_token_program: &AccountInfo,
) -> Result<(), ProgramError> {
let token_account = TokenAccount::from_account_info(ata)?;
if token_account.owner() != owner.key() || token_account.mint() != mint.key() {
return Err(ProgramError::InvalidAccountData);
}
Ok(())
}
}
这段代码构建了一套高性能的底层助手工具库,通过封装签名者校验、PDA 的生命周期管理(包括初始化与利用 unsafe 指针手动回收 Lamports 的“硬核”关户操作)、以及关联代币账户(ATA)的幂等创建与一致性检查,将复杂的底层账户操作抽象为简洁的接口,从而在确保指令逻辑安全性的同时,最大限度地发挥了 Pinocchio v0.9.2 极致节省计算单元(CU)的特性。
close 方法这里用了 unsafe?在 Pinocchio v0.9.2 中,AccountInfo 并没有像新版本那样提供便捷的安全 Setter。为了追求极致性能,开发者直接操作内存地址(borrow_mut_lamports_unchecked)来手动转移余额。这正是 Pinocchio 开发者常说的“给开发者最高的自由度,也要求开发者最严谨的逻辑”。
make 指令完成以下三项工作:
mint_a 拥有的 escrow 的关联代币账户 (ATA))。instructions/make.rs 文件/*
1. 初始化托管记录并存储所有交易条款。
2. 创建金库(一个由 mint_a 拥有的 escrow 的关联代币账户 (ATA))。
3. 使用 CPI 调用 SPL-Token 程序,将创建者的 Token A 转移到该金库中。
*/
use pinocchio::{
ProgramResult, account_info::AccountInfo, instruction::Seed, program_error::ProgramError,
pubkey::find_program_address,
};
use pinocchio_token::instructions::Transfer;
use crate::{
AssociatedTokenAccount, ESCROW_SEED, Escrow, MintInterface, ProgramAccount, SignerAccount,
};
pub struct MakeAccounts<'a> {
pub maker: &'a AccountInfo,
pub escrow: &'a AccountInfo,
pub mint_a: &'a AccountInfo,
pub mint_b: &'a AccountInfo,
pub maker_ata_a: &'a AccountInfo,
pub vault: &'a AccountInfo,
pub system_program: &'a AccountInfo,
pub token_program: &'a AccountInfo,
}
impl<'a> TryFrom<&'a [AccountInfo]> for MakeAccounts<'a> {
type Error = ProgramError;
fn try_from(accounts: &'a [AccountInfo]) -> Result<Self, Self::Error> {
let [
maker,
escrow,
mint_a,
mint_b,
maker_ata_a,
vault,
system_program,
token_program,
_,
] = accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
};
// Basic Accounts Checks
SignerAccount::check(maker)?;
MintInterface::check(mint_a)?;
MintInterface::check(mint_b)?;
AssociatedTokenAccount::check(maker_ata_a, maker, mint_a, token_program)?;
// Return the accounts
Ok(Self {
maker,
escrow,
mint_a,
mint_b,
maker_ata_a,
vault,
system_program,
token_program,
})
}
}
pub struct MakeInstructionData {
pub seed: u64,
pub receive: u64,
pub amount: u64,
}
impl<'a> TryFrom<&'a [u8]> for MakeInstructionData {
type Error = ProgramError;
fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
if data.len() != size_of::<u64>() * 3 {
return Err(ProgramError::InvalidInstructionData);
}
let seed = u64::from_le_bytes(data[0..8].try_into().unwrap());
let receive = u64::from_le_bytes(data[8..16].try_into().unwrap());
let amount = u64::from_le_bytes(data[16..24].try_into().unwrap());
// Instruction Checks
if amount == 0 {
return Err(ProgramError::InvalidInstructionData);
}
Ok(Self {
seed,
receive,
amount,
})
}
}
pub struct Make<'a> {
pub accounts: MakeAccounts<'a>,
pub instruction_data: MakeInstructionData,
pub bump: u8,
}
impl<'a> TryFrom<(&'a [u8], &'a [AccountInfo])> for Make<'a> {
type Error = ProgramError;
fn try_from((data, accounts): (&'a [u8], &'a [AccountInfo])) -> Result<Self, Self::Error> {
let accounts = MakeAccounts::try_from(accounts)?;
let instruction_data = MakeInstructionData::try_from(data)?;
// Initialize the Accounts needed
let (_, bump) = find_program_address(
&[
ESCROW_SEED,
accounts.maker.key().as_ref(),
&instruction_data.seed.to_le_bytes(),
],
&crate::ID,
);
Ok(Self {
accounts,
instruction_data,
bump,
})
}
}
impl<'a> Make<'a> {
pub const DISCRIMINATOR: &'a u8 = &0;
pub fn process(&mut self) -> ProgramResult {
// --- 1. 准备种子 (用于 init PDA) ---
let seed_binding = self.instruction_data.seed.to_le_bytes();
let bump_binding = [self.bump];
let escrow_seeds = [
Seed::from(ESCROW_SEED),
Seed::from(self.accounts.maker.key().as_ref()),
Seed::from(&seed_binding),
Seed::from(&bump_binding),
];
// --- 2. 创建 Escrow PDA ---
ProgramAccount::init::<Escrow>(
self.accounts.maker, // Payer (出钱签名的人)
self.accounts.escrow, // 要创建的 PDA
&escrow_seeds,
Escrow::LEN,
2_000_000,
)?;
// --- 3. 创建 Vault (ATA) ---
// Initialize the vault
AssociatedTokenAccount::init(
self.accounts.maker, // 1. funding_account -> 传 Maker (他是 Signer,付钱)
self.accounts.vault, // 2. account -> 传 Vault (这就是要创建的 ATA)
self.accounts.escrow, // 3. wallet -> 传 Escrow (它是 PDA,作为金库的主人)
self.accounts.mint_a, // 4. mint -> 传 MintA
self.accounts.system_program,
self.accounts.token_program,
)?;
// --- 4. 填充数据 ---
// Populate the escrow account
let mut data = self.accounts.escrow.try_borrow_mut_data()?;
let escrow = Escrow::load_mut(data.as_mut())?;
escrow.set_inner(
self.instruction_data.seed,
*self.accounts.maker.key(),
*self.accounts.mint_a.key(),
*self.accounts.mint_b.key(),
self.instruction_data.receive,
[self.bump],
);
// Transfer tokens to vault
Transfer {
from: self.accounts.maker_ata_a,
to: self.accounts.vault,
authority: self.accounts.maker,
amount: self.instruction_data.amount,
}
.invoke()?;
Ok(())
}
}
这段代码实现了托管合约(Escrow)的 Make 指令 核心逻辑:它首先通过 TryFrom 模式对输入账户进行严格的签名与关联校验,并解析指令数据;随后在执行过程中,程序先后初始化一个 Escrow PDA 用于存储交易条款数据,并创建一个由该 PDA 拥有的 Vault 关联代币账户(ATA),最后通过 CPI(跨程序调用)将创建者指定的 Token A 转移至金库锁定,从而完成了整个交易要约的链上初始化。
该 process 函数的操作可以归纳为以下四个步骤:
ESCROW_SEED、创建者地址和随机种子计算出唯一的 PDA 及其 bump。ProgramAccount::init 和 AssociatedTokenAccount::init 为合约状态和代币存放点分配物理空间。load_mut 方法,将交易条款(如期望获得的 Token B 数量)直接写入 PDA 内存。Transfer 指令,正式将资产的所有权从创建者移交给合约控制的金库。take 指令完成交换操作:
instructions/take.rs 文件use std::slice;
use pinocchio::{
ProgramResult,
account_info::AccountInfo,
instruction::{Seed, Signer},
program_error::ProgramError,
pubkey::create_program_address,
};
use pinocchio_token::{
instructions::{CloseAccount, Transfer},
state::TokenAccount,
};
use crate::{
AssociatedTokenAccount, ESCROW_SEED, Escrow, MintInterface, ProgramAccount, SignerAccount,
};
/*
1. 关闭托管记录,将其租金 lamports 返还给创建者。
2. 将 Token A 从保管库转移到接受者,然后关闭保管库。
3. 将约定数量的 Token B 从接受者转移到创建者。
*/
pub struct TakeAccounts<'a> {
pub taker: &'a AccountInfo,
pub maker: &'a AccountInfo,
pub escrow: &'a AccountInfo,
pub mint_a: &'a AccountInfo,
pub mint_b: &'a AccountInfo,
pub vault: &'a AccountInfo,
pub taker_ata_a: &'a AccountInfo,
pub taker_ata_b: &'a AccountInfo,
pub maker_ata_b: &'a AccountInfo,
pub system_program: &'a AccountInfo,
pub token_program: &'a AccountInfo,
}
impl<'a> TryFrom<&'a [AccountInfo]> for TakeAccounts<'a> {
type Error = ProgramError;
fn try_from(accounts: &'a [AccountInfo]) -> Result<Self, Self::Error> {
let [
taker,
maker,
escrow,
mint_a,
mint_b,
vault,
taker_ata_a,
taker_ata_b,
maker_ata_b,
system_program,
token_program,
_,
] = accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
};
// Basic Accounts Checks
SignerAccount::check(taker)?;
ProgramAccount::check(escrow)?;
MintInterface::check(mint_a)?;
MintInterface::check(mint_b)?;
AssociatedTokenAccount::check(taker_ata_b, taker, mint_b, token_program)?;
AssociatedTokenAccount::check(vault, escrow, mint_a, token_program)?;
// Return the accounts
Ok(Self {
taker,
maker,
escrow,
mint_a,
mint_b,
taker_ata_a,
taker_ata_b,
maker_ata_b,
vault,
system_program,
token_program,
})
}
}
pub struct Take<'a> {
pub accounts: TakeAccounts<'a>,
}
impl<'a> TryFrom<&'a [AccountInfo]> for Take<'a> {
type Error = ProgramError;
fn try_from(accounts: &'a [AccountInfo]) -> Result<Self, Self::Error> {
let accounts = TakeAccounts::try_from(accounts)?;
// Initialize necessary accounts
AssociatedTokenAccount::init_if_needed(
accounts.taker_ata_a,
accounts.mint_a,
accounts.taker,
accounts.taker,
accounts.system_program,
accounts.token_program,
)?;
AssociatedTokenAccount::init_if_needed(
accounts.maker_ata_b,
accounts.mint_b,
accounts.taker,
accounts.maker,
accounts.system_program,
accounts.token_program,
)?;
Ok(Self { accounts })
}
}
impl<'a> Take<'a> {
pub const DISCRIMINATOR: &'a u8 = &1;
pub fn process(&mut self) -> ProgramResult {
let data = self.accounts.escrow.try_borrow_data()?;
let escrow = Escrow::load(&data)?;
// Check if the escrow is valid
let escrow_key = create_program_address(
&[
ESCROW_SEED,
self.accounts.maker.key(),
&escrow.seed.to_le_bytes(),
&escrow.bump,
],
&crate::ID,
)?;
if &escrow_key != self.accounts.escrow.key() {
return Err(ProgramError::InvalidAccountOwner);
}
let seed_binding = escrow.seed.to_le_bytes();
let bump_binding = escrow.bump;
let escrow_seeds = [
Seed::from(ESCROW_SEED),
Seed::from(self.accounts.maker.key().as_ref()),
Seed::from(&seed_binding),
Seed::from(&bump_binding),
];
let signer = Signer::from(&escrow_seeds);
let amount = TokenAccount::from_account_info(self.accounts.vault)?.amount();
// Transfer from the Vault to the Taker
Transfer {
from: self.accounts.vault,
to: self.accounts.taker_ata_a,
authority: self.accounts.escrow,
amount,
}
.invoke_signed(slice::from_ref(&signer))?;
// Close the Vault
CloseAccount {
account: self.accounts.vault,
destination: self.accounts.maker,
authority: self.accounts.escrow,
}
.invoke_signed(slice::from_ref(&signer))?;
// Transfer from the Taker to the Maker
Transfer {
from: self.accounts.taker_ata_b,
to: self.accounts.maker_ata_b,
authority: self.accounts.taker,
amount: escrow.receive,
}
.invoke()?;
// Close the Escrow
drop(data);
ProgramAccount::close(self.accounts.escrow, self.accounts.taker)?;
Ok(())
}
}
这段代码实现了托管合约的 Take 指令,即最终的交换执行逻辑:它首先通过 init_if_needed 确保接收者和创建者所需的代币账户已就绪,随后在校验托管账户(Escrow PDA)合法性后,利用 PDA 签名分两步完成资产互换——先将金库中的 Token A 转移给接收者并关闭金库,再将接收者持有的 Token B 转移给创建者;最后,通过手动回收租金的方式关闭托管账户,从而在链上安全地解除了这笔原子交易。
refund 指令允许创建者取消一个未完成的报价:
instructions/refund.rs 文件
/*
refund 指令允许创建者取消一个未完成的报价:
关闭托管 PDA,并将其租金 lamports 返还给创建者。
将代币 A 的全部余额从保险库转回创建者,然后关闭保险库账户。
*/
use std::slice;
use pinocchio::{
ProgramResult,
account_info::AccountInfo,
instruction::{Seed, Signer},
program_error::ProgramError,
};
use pinocchio_token::{
instructions::{CloseAccount, Transfer},
state::TokenAccount,
};
use crate::{AssociatedTokenAccount, ESCROW_SEED, Escrow, ProgramAccount};
pub struct Refund<'a> {
pub maker: &'a AccountInfo,
pub escrow: &'a AccountInfo,
pub mint_a: &'a AccountInfo,
pub vault: &'a AccountInfo,
pub maker_ata_a: &'a AccountInfo,
pub associated_token_program: &'a AccountInfo,
pub token_program: &'a AccountInfo,
pub system_program: &'a AccountInfo,
}
impl<'a> Refund<'a> {
pub const DISCRIMINATOR: &'a u8 = &2;
pub fn try_from(accounts: &'a [AccountInfo]) -> Result<Self, ProgramError> {
// 使用简单的切片模式匹配来获取账户,性能最优
let [
maker, // 1. Signer
escrow, // 2. Escrow PDA
mint_a, // 3. Mint A (Anchor 里的第三个账户)
vault, // 4. Vault (Token Account)
maker_ata_a, // 5. Maker ATA
associated_token_program,
token_program, // 6. Token Program
system_program, // 7. System Program
] = accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
};
Ok(Self {
maker,
escrow,
mint_a,
vault,
maker_ata_a,
associated_token_program,
token_program,
system_program,
})
}
pub fn process(&self) -> ProgramResult {
AssociatedTokenAccount::init_if_needed(
self.maker_ata_a,
self.mint_a,
self.maker,
self.maker,
self.system_program,
self.token_program,
)?;
// 1. 获取 Escrow 数据视图 (零拷贝)
let data = self.escrow.try_borrow_data()?;
let escrow_state = Escrow::load(&data)?;
// 2. 构造 PDA 签名
let seed_bytes = escrow_state.seed.to_le_bytes();
let seeds = [
Seed::from(ESCROW_SEED),
Seed::from(self.maker.key().as_ref()),
Seed::from(&seed_bytes),
Seed::from(&escrow_state.bump),
];
let signer = Signer::fro... 如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!