1

I am currently experimenting Anchor, as I need to pass in an arbitrary number of accounts I am leveraging remaining_accounts.

Client Side

await program.methods.someMethod().accounts({
  // somme accounts
})
.remainingAccounts([
  {
    isSigner: false,
    isWritable: true,
    pubkey: pub key
  }
])
.rpc();

Notice that the accounts in remaining_accounts are actually PDAs owned by my program, so I have the authority needed to write to them.

On Chain

Now in order to make my life easier I deserialize the account by using my PDA Account struct:

let mut test: Account<MyPDAStruct> = Account::try_from(&ctx.remaining_accounts[0])
    .unwrap();

So far so good, this works and I am able to deserialize the acccount.

Now I would need to write to this account, which basically means that I need to serialize the data.

I found this reference:

impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> AccountsExit<'info>
    for Account<'info, T>
{
    fn exit(&self, program_id: &Pubkey) -> Result<()> {
        // Only persist if the owner is the current program.
        if &T::owner() == program_id {
            let info = self.to_account_info();
            let mut data = info.try_borrow_mut_data()?;
            let dst: &mut [u8] = &mut data;
            let mut writer = BpfWriter::new(dst); // notice this
            self.account.try_serialize(&mut writer)?;
        }
        Ok(())
    }
}

It uses the BpfWriter which then uses sol_memcpy.

However I am not able to import the BpfWriter from anchor_lang. Any suggestion?

2 Answers 2

2

If you need these accounts to be mutable, the easier way is to pass them as regular accounts instead remaining accounts. For example if you will pass between 0 and 3 accounts, you might do something like:

    #[account(mut)]
    pub A: AccountInfo<'info>,
    #[account(mut)]
    pub B: AccountInfo<'info>,
    #[account(mut)]
    pub C: AccountInfo<'info>,

Where passing the default pubkey indicates the field is unused. This also allows you add constraints to check that the PDA is valid.

If you really need these in a Vec:


if ctx.accounts.A.key() != Pubkey::default(){
   vec.push(ctx.accounts.A.whatever);
}

Using remaining_accounts is a convenient shortcut, but remember that the maximum number of accounts is always limited by the max transaction size anyways. In practice you cannot exceed about 35 accounts in one tx, usually a little less with signers and other args.

5
  • What does Pubkey::default() evaluate to? vector of zero-ed bytes? What about the client side?
    – Sid
    Aug 17, 2022 at 18:57
  • 1
    The default key is all zero bytes, and on the client side it is equivalent to PublicKey.default in the Solana web3 library.
    – Whiteseal
    Aug 17, 2022 at 19:03
  • checked and both on chain and off chain Publickey::default() evaluates to vec ofzeros. Thanks!
    – Sid
    Aug 17, 2022 at 19:03
  • what if the account is a PDA? In that case I would need to pass in some seeds, which differ in each Account. I can probably use #instruction(). Was wondering if there's a cleaner solution
    – Sid
    Aug 17, 2022 at 19:07
  • 1
    It would be best to pass and check the seeds, but you could pass an UncheckedAccount if that's an issue for whatever reason
    – Whiteseal
    Aug 17, 2022 at 19:11
0

Still unable to import BpfWriter from anchor_lang so in the meantime I just copied the BpfWriter code into a module in my project.

Notice that you don't really need to copy the whole code, you can just implement Write by your own.

use solana_program::program_memory::sol_memcpy;
use std::cmp;
use std::io::{self, Write};

#[derive(Debug, Default)]
pub struct BpfWriter<T> {
    inner: T,
    pos: u64,
}

impl<T> BpfWriter<T> {
    pub fn new(inner: T) -> Self {
        Self { inner, pos: 0 }
    }
}

impl Write for BpfWriter<&mut [u8]> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        if self.pos >= self.inner.len() as u64 {
            return Ok(0);
        }

        let amt = cmp::min(
            self.inner.len().saturating_sub(self.pos as usize),
            buf.len(),
        );
        sol_memcpy(&mut self.inner[(self.pos as usize)..], buf, amt);
        self.pos += amt as u64;
        Ok(amt)
    }

    fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
        if self.write(buf)? == buf.len() {
            Ok(())
        } else {
            Err(io::Error::new(
                io::ErrorKind::WriteZero,
                "failed to write whole buffer",
            ))
        }
    }

    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}
1
  • Hi, did you manage to write in a remaining account? I am using try_from to get the remaining account. It is an account with a Vec where I want to push a value. The problem is that the value doesn't actually stores in the Vec although I dont get any error. I think I have to serialize it somehow Nov 28, 2022 at 22:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.