Rust与Anchor开发模式指南 rust-patterns

这是一份关于Rust编程语言和Anchor框架在Solana区块链上开发智能合约的实用模式指南。内容涵盖账户验证、PDA(程序派生地址)推导、跨程序调用(CPI)安全以及通用Rust最佳实践。适用于智能合约开发者、区块链工程师和安全审计人员,旨在提升代码安全性、可读性和性能。关键词:Rust, Anchor, Solana, 智能合约, 区块链开发, PDA, CPI, 账户安全, 最佳实践。

智能合约 0 次安装 0 次浏览 更新于 2/23/2026

名称: rust-patterns 描述: Rust 和 Anchor/Solana 开发模式。在编写 Rust 代码、Anchor 程序或审查 Solana 智能合约时使用。涵盖账户验证、PDA 推导、CPI 安全和通用 Rust 最佳实践。

Rust & Anchor 模式

Anchor 账户验证

账户约束

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(
        init,
        payer = authority,
        space = 8 + MyAccount::INIT_SPACE,
        seeds = [b"my-seed", authority.key().as_ref()],
        bump,
    )]
    pub my_account: Account<'info, MyAccount>,

    #[account(mut)]
    pub authority: Signer<'info>,

    pub system_program: Program<'info, System>,
}

核心约束

约束 用途
mut 账户可变
signer 必须签署交易
has_one = field 账户字段匹配另一个账户
seeds = [...] PDA 推导种子
bump PDA 碰撞种子(规范)
constraint = expr 自定义布尔检查
close = target 关闭账户,将租金发送到目标账户
token::mint = mint 验证代币账户的铸币地址
token::authority = auth 验证代币权限

PDA 推导

// 查找 PDA(链下)
let (pda, bump) = Pubkey::find_program_address(
    &[b"seed", user.as_ref()],
    &program_id,
);

// 始终使用规范碰撞(来自 find_program_address)
// 切勿接受用户提供的碰撞
#[account(
    seeds = [b"seed", authority.key().as_ref()],
    bump,  // Anchor 存储并使用规范碰撞
)]

CPI(跨程序调用)安全

// 使用 PDA 签名者进行 CPI
let seeds = &[b"vault", authority.key().as_ref(), &[bump]];
let signer_seeds = &[&seeds[..]];

let cpi_ctx = CpiContext::new_with_signer(
    token_program.to_account_info(),
    Transfer {
        from: vault.to_account_info(),
        to: destination.to_account_info(),
        authority: vault_authority.to_account_info(),
    },
    signer_seeds,
);
token::transfer(cpi_ctx, amount)?;

签名者验证

  • 对于必须签名的账户,始终使用 Signer<'info> 类型
  • 切勿使用 AccountInfo 并手动检查 is_signer,除非必要
  • 验证账户所有权:Account<'info, T> 自动检查程序所有权

代币账户验证

#[account(
    mut,
    token::mint = mint,
    token::authority = user,
)]
pub user_token_account: Account<'info, TokenAccount>,

通用 Rust 模式

错误处理

// 使用 thiserror 处理库错误
#[derive(Debug, thiserror::Error)]
pub enum MyError {
    #[error("资金不足: 拥有 {available}, 需要 {required}")]
    InsufficientFunds { available: u64, required: u64 },

    #[error("账户未找到: {0}")]
    NotFound(String),
}

// 使用 anyhow 处理应用程序错误
fn process() -> anyhow::Result<()> {
    let data = fetch_data().context("获取数据失败")?;
    Ok(())
}

// **切勿**在生产环境中使用:
value.unwrap()     // 会导致恐慌
value.expect("x")  // 附带消息的恐慌

优先使用引用

// 不好:不必要地获取所有权
fn process(data: String) -> bool { data.contains("x") }

// 好:借用
fn process(data: &str) -> bool { data.contains("x") }

迭代器模式

// 不好:手动循环并推送
let mut results = Vec::new();
for item in items {
    if item.active {
        results.push(item.value);
    }
}

// 好:迭代器链
let results: Vec<_> = items.iter()
    .filter(|item| item.active)
    .map(|item| item.value)
    .collect();

派生特征

// 为数据类型派生通用特征
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct UserId(pub u64);

// 使用 #[must_use] 标记不应被忽略的结果
#[must_use]
pub fn validate(input: &str) -> bool { ... }

常见陷阱

参见 common-pitfalls.md 了解:

  • 缺少签名者/所有者检查
  • PDA 碰撞规范化
  • 代币数学运算中的算术溢出
  • 重新初始化攻击