指令与跨程序调用
指令结构
每个 Anchor 指令由三部分组成:
- Context:包含账户和程序信息
- 参数:指令的输入数据
- 返回值:
Result<()>或Result<T>
rustpub fn transfer( ctx: Context<Transfer>, // 账户上下文 amount: u64, // 指令参数 memo: String, // 可以有多个参数 ) -> Result<()> { // 指令逻辑 Ok(()) }
Context 结构
Context<T> 提供了丰富的信息访问:
rustpub fn example(ctx: Context<Example>) -> Result<()> { // 访问账户 let account = &ctx.accounts.my_account; // 访问程序 ID let program_id = ctx.program_id; // 访问剩余账户 for remaining in ctx.remaining_accounts { // 处理... } // 访问 PDA bumps let bump = ctx.bumps.vault; Ok(()) }
指令鉴别器
Anchor 使用 8 字节鉴别器标识指令,默认由 sha256("global:<instruction_name>")[0..8] 计算:
rust// 指令名使用 snake_case pub fn initialize_vault(ctx: Context<InitializeVault>) -> Result<()> { // sha256("global:initialize_vault")[0..8] Ok(()) }
自定义鉴别器:
rust#[instruction(discriminator = [1, 2, 3, 4, 5, 6, 7, 8])] pub fn custom_instruction(ctx: Context<Custom>) -> Result<()> { Ok(()) }
自定义类型参数
使用自定义结构体作为参数:
rust#[derive(AnchorSerialize, AnchorDeserialize, Clone)] pub struct TransferParams { pub amount: u64, pub memo: String, } pub fn transfer_with_params( ctx: Context<Transfer>, params: TransferParams, ) -> Result<()> { msg!("转账 {} lamports,备注: {}", params.amount, params.memo); Ok(()) }
错误处理
定义清晰的自定义错误:
rust#[error_code] pub enum VaultError { #[msg("金库余额不足")] InsufficientBalance, #[msg("金额必须大于零")] InvalidAmount, #[msg("未授权的操作")] Unauthorized, #[msg("金额超过最大限制: {0}")] AmountExceedsLimit(u64), }
在指令中使用:
rustpub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> { let vault = &ctx.accounts.vault; require!(amount > 0, VaultError::InvalidAmount); require!(vault.balance >= amount, VaultError::InsufficientBalance); // 或者使用 if 语句 if amount > MAX_WITHDRAWAL { return Err(VaultError::AmountExceedsLimit(MAX_WITHDRAWAL).into()); } // 执行提款... Ok(()) }
跨程序调用(CPI)
CPI 允许一个程序调用另一个程序的指令,是 Solana 可组合性的基础。
基本 CPI
rustuse anchor_lang::system_program::{transfer, Transfer}; pub fn transfer_sol(ctx: Context<TransferSol>, amount: u64) -> Result<()> { let cpi_accounts = Transfer { from: ctx.accounts.sender.to_account_info(), to: ctx.accounts.recipient.to_account_info(), }; let cpi_program = ctx.accounts.system_program.to_account_info(); let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts); transfer(cpi_ctx, amount)?; Ok(()) }
带 PDA 签名的 CPI
当 PDA 需要作为签名者时:
rustpub fn transfer_from_vault(ctx: Context<TransferFromVault>, amount: u64) -> Result<()> { let vault = &ctx.accounts.vault; // 构造签名种子 let seeds = &[ b"vault".as_ref(), vault.authority.as_ref(), &[vault.bump], ]; let signer_seeds = &[&seeds[..]]; let cpi_accounts = Transfer { from: ctx.accounts.vault.to_account_info(), to: ctx.accounts.recipient.to_account_info(), }; let cpi_program = ctx.accounts.system_program.to_account_info(); let cpi_ctx = CpiContext::new_with_signer( cpi_program, cpi_accounts, signer_seeds ); transfer(cpi_ctx, amount)?; Ok(()) }
Token 转账 CPI
rustuse anchor_spl::token::{transfer, Transfer}; pub fn transfer_tokens(ctx: Context<TransferTokens>, amount: u64) -> Result<()> { let cpi_accounts = Transfer { from: ctx.accounts.source.to_account_info(), to: ctx.accounts.destination.to_account_info(), authority: ctx.accounts.authority.to_account_info(), }; let cpi_program = ctx.accounts.token_program.to_account_info(); let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts); transfer(cpi_ctx, amount)?; Ok(()) }
调用其他 Anchor 程序
使用 declare_program! 宏实现类型安全的 CPI:
project/ ├── idls/ │ └── target_program.json # 目标程序的 IDL └── programs/ └── caller/ └── src/ └── lib.rs
rustuse anchor_lang::prelude::*; declare_id!("YourProgramID"); // 从 IDL 生成模块 declare_program!(target_program); use target_program::{ accounts::Counter, cpi::{self, accounts::Increment}, program::TargetProgram, }; #[program] pub mod caller { use super::*; pub fn call_increment(ctx: Context<CallIncrement>) -> Result<()> { let cpi_ctx = CpiContext::new( ctx.accounts.target_program.to_account_info(), Increment { counter: ctx.accounts.counter.to_account_info(), authority: ctx.accounts.authority.to_account_info(), }, ); target_program::cpi::increment(cpi_ctx)?; Ok(()) } } #[derive(Accounts)] pub struct CallIncrement<'info> { #[account(mut)] pub counter: Account<'info, Counter>, pub authority: Signer<'info>, pub target_program: Program<'info, TargetProgram>, }
指令组织最佳实践
对于复杂程序,将指令逻辑分离到独立文件:
instructions/mod.rs:
rustpub mod initialize; pub mod deposit; pub mod withdraw; pub use initialize::*; pub use deposit::*; pub use withdraw::*;
instructions/deposit.rs:
rustuse anchor_lang::prelude::*; use crate::state::Vault; use crate::errors::VaultError; #[derive(Accounts)] pub struct Deposit<'info> { #[account(mut)] pub vault: Account<'info, Vault>, #[account(mut)] pub depositor: Signer<'info>, pub system_program: Program<'info, System>, } impl<'info> Deposit<'info> { pub fn transfer_to_vault(&self, amount: u64) -> Result<()> { let cpi_accounts = anchor_lang::system_program::Transfer { from: self.depositor.to_account_info(), to: self.vault.to_account_info(), }; let cpi_ctx = CpiContext::new( self.system_program.to_account_info(), cpi_accounts ); anchor_lang::system_program::transfer(cpi_ctx, amount) } } pub fn handler(ctx: Context<Deposit>, amount: u64) -> Result<()> { require!(amount > 0, VaultError::InvalidAmount); ctx.accounts.transfer_to_vault(amount)?; let vault = &mut ctx.accounts.vault; vault.total_deposits = vault.total_deposits .checked_add(amount) .ok_or(VaultError::Overflow)?; msg!("存入 {} lamports", amount); Ok(()) }