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