理解 Anchor 账户鉴别器
Anchor 程序会在所有账户数据前添加一个 8 字节的鉴别器。该鉴别器是账户类型名称的哈希值,用于验证账户数据是否与预期类型匹配。
┌─────────────────────────────────────────────────┐│ Account Data Layout │├────────────────┬────────────────────────────────┤│ Discriminator │ Account Fields ││ (8 bytes) │ (variable size) │└────────────────┴────────────────────────────────┘
anchor-litesvm 账户工具会自动处理此鉴别器。
获取 Anchor 账户
基本账户获取
use anchor_litesvm::AnchorLiteSVM;use anchor_lang::prelude::*;use solana_signer::Signer;use solana_program::pubkey::Pubkey;#[account]pub struct UserAccount {pub name: String,pub balance: u64,pub authority: Pubkey,}#[test]fn test_fetch_account() {let mut ctx = AnchorLiteSVM::build_with_program(PROGRAM_ID, include_bytes!("../target/deploy/your_program.so"));let user = ctx.create_funded_account(10_000_000_000).unwrap();// ... initialize the account ...let (user_pda, _) = Pubkey::find_program_address(&[b"user", user.pubkey().as_ref()],&PROGRAM_ID,);// Fetch and deserialize the accountlet account: UserAccount = ctx.get_account(&user_pda).unwrap();assert_eq!(account.name, "Alice");assert_eq!(account.authority, user.pubkey());}
get_account<T>() 会自动验证 8 字节鉴别器,以确保账户数据与预期类型匹配。
非检查反序列化
对于鉴别器验证可能失败的 PDA 或自定义账户布局,请使用 get_account_unchecked:
use anchor_litesvm::AnchorLiteSVM;#[test]fn test_fetch_unchecked() {let ctx = AnchorLiteSVM::build_with_program(PROGRAM_ID, include_bytes!("../target/deploy/your_program.so"));// Fetch without discriminator validationlet account: UserAccount = ctx.get_account_unchecked(&some_pda).unwrap();}
请谨慎使用
get_account_unchecked。若不进行鉴别器验证,可能会反序列化无效数据。仅在确定账户类型时才使用此方法。
直接使用账户模块
您也可以直接使用账户反序列化函数:
use anchor_litesvm::{get_anchor_account, get_anchor_account_unchecked};use litesvm::LiteSVM;#[test]fn test_direct_account_fetch() {let mut svm = LiteSVM::new();// ... setup ...// Fetch with discriminator checklet account: UserAccount = get_anchor_account(&svm, &account_pubkey).unwrap();// Fetch without discriminator checklet account: UserAccount = get_anchor_account_unchecked(&svm, &account_pubkey).unwrap();}
账户错误
AccountError 枚举提供了详细的错误信息:
use anchor_litesvm::AccountError;#[test]fn test_handle_errors() {let ctx = AnchorLiteSVM::build_with_program(PROGRAM_ID, include_bytes!("../target/deploy/your_program.so"));let result: Result<UserAccount, AccountError> = ctx.get_account(&nonexistent_pubkey);match result {Ok(account) => println!("Found account: {}", account.name),Err(AccountError::AccountNotFound(pubkey)) => {println!("Account not found: {}", pubkey);}Err(AccountError::DiscriminatorMismatch) => {println!("Wrong account type!");}Err(AccountError::DeserializationError(msg)) => {println!("Failed to deserialize: {}", msg);}}}
| 错误 | 描述 |
|---|---|
AccountNotFound(Pubkey) | 给定地址处不存在账户 |
DiscriminatorMismatch | 账户鉴别器与预期类型不匹配 |
DeserializationError(String) | 账户数据反序列化失败 |
处理多种账户类型
获取不同账户类型
use anchor_litesvm::AnchorLiteSVM;use anchor_lang::prelude::*;#[account]pub struct Config {pub admin: Pubkey,pub fee_bps: u16,}#[account]pub struct UserProfile {pub owner: Pubkey,pub username: String,pub created_at: i64,}#[account]pub struct Order {pub user: Pubkey,pub amount: u64,pub status: OrderStatus,}#[derive(AnchorSerialize, AnchorDeserialize, Clone)]pub enum OrderStatus {Pending,Completed,Cancelled,}#[test]fn test_multiple_account_types() {let ctx = AnchorLiteSVM::build_with_program(PROGRAM_ID, include_bytes!("../target/deploy/your_program.so"));// Each account type is deserialized correctlylet config: Config = ctx.get_account(&config_pda).unwrap();let profile: UserProfile = ctx.get_account(&profile_pda).unwrap();let order: Order = ctx.get_account(&order_pda).unwrap();assert_eq!(config.fee_bps, 100);assert_eq!(profile.username, "alice");assert!(matches!(order.status, OrderStatus::Pending));}
获取前检查账户是否存在
use anchor_litesvm::AnchorLiteSVM;#[test]fn test_check_before_fetch() {let ctx = AnchorLiteSVM::build_with_program(PROGRAM_ID, include_bytes!("../target/deploy/your_program.so"));let user_pda = Pubkey::new_unique();// Check existence firstif ctx.account_exists(&user_pda) {let account: UserAccount = ctx.get_account(&user_pda).unwrap();println!("Found: {}", account.name);} else {println!("Account not initialized yet");}}
完整示例
以下是演示账户管理的综合示例:
use anchor_litesvm::AnchorLiteSVM;use anchor_litesvm::TestHelpers;use anchor_lang::prelude::*;use solana_signer::Signer;use solana_program::pubkey::Pubkey;anchor_lang::declare_program!(counter);#[test]fn test_counter_workflow() {let mut ctx = AnchorLiteSVM::build_with_program(id(),include_bytes!("../target/deploy/counter.so"),);let authority = ctx.svm.create_funded_account(10_000_000_000).unwrap();// Derive the counter PDAlet (counter_pda, bump) = Pubkey::find_program_address(&[b"counter", authority.pubkey().as_ref()],&id(),);// Verify counter doesn't exist yetassert!(!ctx.account_exists(&counter_pda));// Initialize using generated client typeslet init_ix = ctx.program().accounts(counter::client::accounts::Initialize {authority: authority.pubkey(),counter: counter_pda,system_program: anchor_lang::system_program::ID,}).args(counter::client::args::Initialize {}).instruction().unwrap();ctx.execute_instruction(init_ix, &[&authority]).unwrap();// Verify counter exists and has correct initial stateassert!(ctx.account_exists(&counter_pda));let counter: counter::Counter = ctx.get_account(&counter_pda).unwrap();assert_eq!(counter.count, 0);assert_eq!(counter.authority, authority.pubkey());assert_eq!(counter.bump, bump);// Increment using generated client typeslet increment_ix = ctx.program().accounts(counter::client::accounts::Increment {authority: authority.pubkey(),counter: counter_pda,}).args(counter::client::args::Increment {}).instruction().unwrap();ctx.execute_instruction(increment_ix, &[&authority]).unwrap();// Verify incrementedlet counter: counter::Counter = ctx.get_account(&counter_pda).unwrap();assert_eq!(counter.count, 1);// Increment againctx.execute_instruction(increment_ix.clone(), &[&authority]).unwrap();let counter: counter::Counter = ctx.get_account(&counter_pda).unwrap();assert_eq!(counter.count, 2);println!("Counter test passed! Final count: {}", counter.count);}
Is this page helpful?