名称: 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 碰撞规范化
- 代币数学运算中的算术溢出
- 重新初始化攻击