Solana Anchor框架中PDA使用zero_copy的实现与报错解决
问题原因与修复方案
首先明确:zero_copy 特性完全支持PDA账户,不存在仅能用于钱包所有账户的限制,你遇到的报错和zero_copy对PDA的兼容性无关,是代码存在几处明显错误导致的。
存在的具体问题
- Rust端账户类型写错:你将zero_copy对应的账户类型写成了拼写错误的
AccountInfor,实际上所有标记zero_copy的账户,无论是否为PDA,都必须使用AccountLoader类型包裹,否则Anchor无法正确识别账户类型。 - 指令参数位置错误:你的
create_aff指令定义了一个u8类型的指令参数_i,但TS端调用RPC时没有传入该参数,直接将账户配置对象作为第一个参数传入,Anchor会将该对象识别为_i的参数值,后续解析账户时自然找不到affAccount字段,直接抛出参数缺失错误。 - 账户空间计算错误:
zero_copy要求结构体按8字节内存对齐,你定义的Aff结构除了8字节Anchor账户鉴别器、32字节公钥、1字节bump外,还需要7字节的对齐填充,总空间应为48字节,你写的41字节会导致初始化时空间不足。 - 测试代码笔误:初始化账户后你调用的是
program.account.counts.fetch,但你定义的账户名称是aff,会导致后续查询失败。
修复后的代码
Rust端代码
// PDA种子常量 pub const AFFSEED: &[u8] = b"aff_seed"; #[program] mod myapp { use super::*; pub fn create_aff(ctx: Context<CreateAff>, _i: u8) -> Result<()> { let acc = &mut ctx.accounts.aff_account.load_init()?; acc.aff_account = ctx.accounts.user.key(); acc.bump = *ctx.bumps.get("aff_account").unwrap(); Ok(()) } } // 显式标注内存布局与对齐,避免zero_copy内存解析错误 #[account(zero_copy)] #[repr(C, align(8))] pub struct Aff { aff_account: Pubkey, bump: u8, } #[derive(Accounts)] #[instruction(i: u8)] pub struct CreateAff<'info> { // 修正类型为AccountLoader,修正空间为48字节 #[account(init, space = 48, payer = user, seeds = [AFFSEED], bump)] aff_account: AccountLoader<'info, Aff>, #[account(mut)] user: Signer<'info>, /// CHECK: 仅作为系统程序调用,无需额外校验 system_program: AccountInfo<'info>, }
测试端JS代码
it("Creates the aff account if doesn't exist", async () => { const [affPDA, bump] = await PublicKey.findProgramAddress( [anchor.utils.bytes.utf8.encode(AFFSEED)], program.programId ); console.log("CreateAff: affPDA is [", affPDA.toString(), "], bump is", bump); const contents = await program.account.aff.fetchNullable(affPDA); if (contents == null) { // 注意:先按顺序传入指令参数,最后再传入账户配置对象 await program.rpc.createAff( 0, // 对应Rust端的_i:u8参数,传任意u8值即可 { accounts: { affAccount: affPDA, user: usr, systemProgram: SystemProgram.programId, }, signers: [], } ); // 修正查询的账户类型为aff const affe = await program.account.aff.fetch(affPDA); console.log("affe: ", affe); assert.ok(true); } else { assert.ok(true); } });
补充说明
使用AccountLoader+zero_copy操作PDA时,和操作普通用户持有账户没有任何区别:初始化时用load_init()获取可写引用,读取时用load(),修改时用load_mut()即可。该模式下Anchor不会对账户做全量序列化/反序列化,直接通过内存映射访问账户数据,完全不会触发BPF 4KB栈限制,单账户最多可存储10KB数据(Solana默认单账户大小上限,需要更大容量可手动调用账户扩容指令)。
内容的提问来源于stack exchange,提问作者AllBlooming
相关产品推荐
相关产品推荐

