How to use zero_copy with anchor in Solana PDAs

1.2k Views Asked by At

I'm building on Solana and need some PDAs to store my program state.

The default with anchor is constantly serialize/deserialize the accounts, even when just passing the address into instructions, which crushes BPM's 4k stack limit REALLY soon for my app.

So I found the zero_copy feature, which is exactly what I need, see https://docs.rs/anchor-lang/latest/anchor_lang/attr.account.html.

The examples shown in the anchor docs, as well as some code samples I found through web search, all refer to wallet-owned accounts, not to PDAs. There is literally NOTHING to find online about zero_copy with PDAs, so I'm wondering if it's possible at all?!

Anyway - I feel I really, really need it, otherwise my PDA accounts will be limited to something around 400 bytes or so.

So I gave it a try:

#[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(())
    }
}

#[account(zero_copy)]
pub struct Aff {
    aff_account: Pubkey,
    bump: u8,
}

#[derive(Accounts)]
#[instruction(i: u8)]
pub struct CreateAff<'info> {
    #[account(init, space = 41, payer = user, seeds = [AFFSEED], bump)]
    aff_account: AccountInfor<'info, Aff>,
    #[account(mut)]
    user: Signer<'info>,
    /// CHECK: This is not dangerous because we don't read or write from this account
    system_program: AccountInfo<'info>,
}

So far, so good. It compiles. Running a simple test:

  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({
        accounts: {
          affAccount: affPDA,
          user: usr,
          systemProgram: SystemProgram.programId,
        },
        signers: [],
      });
      const affe = await program.account.counts.fetch(affPDA);
      console.log("affe: ", affe);
      assert.ok(true);
    } else {
      assert.ok(true);
    }
  });

renders me an error:

       Creates the aff account if doesn't exist:
     Error: Invalid arguments: affAccount not provided.
      at /Users/bb/app/nodestore/node_modules/@project-serum/anchor/dist/cjs/program/common.js:39:23
      at Array.forEach (<anonymous>)
      at validateAccounts (node_modules/@project-serum/anchor/dist/cjs/program/common.js:33:16)
      at ix (node_modules/@project-serum/anchor/dist/cjs/program/namespace/instruction.js:38:46)
      at txFn (node_modules/@project-serum/anchor/dist/cjs/program/namespace/transaction.js:16:20)
      at Object.rpc [as createAff] (node_modules/@project-serum/anchor/dist/cjs/program/namespace/rpc.js:9:24)
      at Context.<anonymous> (tests/nodeshop.js:63:25)
      at processTicksAndRejections (node:internal/process/task_queues:96:5)

It's complaining affAccount is not provided, even though I'm clearly passing it in.

So the problem seems to be some part of the runtime cannot handle affAccount being AccountLoader (for zero_copy) rather than the standard AccountInfo.

Any help how I can fix or at least further debug this are highly appreciated.

1

There are 1 best solutions below

0
AllBlooming On BEST ANSWER

I got it working. Sometimes it helps just posting a question, it helps thinking things through... ;-)

So great news: zero_copy with PDAs is possible! :-)

Here's what it was:

I originally gave the create_aff function (and the corresponding accounts struct) an i argument, even though I'm not using and additional i in the PDA account seeds. This was just a copy/paste error from a previous PDA I had been working on :-/

Since I was consistent with the i, the compiler didn't complain.

I removed the i parameter from the create_aff's parameter list as well as the #[instruction(i: u8)] from the CreateAff accounts struct declaration and, violà, it's working now.

Long live Solana and anchor. Oh, and a recommendation to the builders of anchor: Just make zero_copy the default, and lengthy borsh the exception!