solanapushdown-automatonsolana-web3jsanchor-solanasolana-transaction-instruction

How to use zero_copy with anchor in Solana PDAs


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.


Solution

  • 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!