2

I need to extend the fields I have within a PDA. Some accounts are already live, so I want those to still be compatible with the new layout. As a simple illustrative example, consider this account structure:

#[account]
pub struct AccountA {
    pub variableA: u32, 
}

Now I modify it, so so the program pads PDAs as such:

#[account]
pub struct AccountA {
    pub variableA: u32,
    pub variableB: u32,
}

I'd like to know if it's possible to retain compatibility with accounts created using the original struct after this change. Specifically, will the following line still function properly?

let acc_a: &mut Account<'_, AccountA> = &mut ctx.accounts.acc_a;

And make it so it will work with both smaller accounts with only one field from before the upgrade, as well as the larger ones post upgrade with two fields.

In essence I want a function similar to this:

impl AccountA {
    pub fn value(&self, value: u32) -> bool {
        return value < self.variableA || value > self.variableB;
    }
}

If caller provides PDA of AccountA from before the change, it should still work on the first of the or statements.

There is a plan to implement a function to resize and extend the existing account, but I want the transition to be seamless. Ideally, existing users should only need to invoke the resizing function if they desire the new functionality.

3 Answers 3

1

If you account is already live, you can't make it backwards compatible.

You can do this if the account is zero_copy and you planned ahead by having some buffer space or padding, for example:

#[account(zero_copy)]
pub struct SomeStruct {
    pub var_a: u64,
    pub var_b: u32,
    pub var_c: u32,
    pub var_d: u16,
    // pad to next 8-byte
    pub padding: [u8; 6],
    pub _reserved1: [u8; 64],
}

Then you can add

#[account(zero_copy)]
pub struct SomeStruct {
    pub var_a: u64,
    pub var_b: u32,
    pub var_c: u32,
    pub var_d: u16,
    // pad to next 8-byte
    pub padding: [u8; 6],
    pub var_e: u32, // added
    pub _reserved1: [u8; 60],
}

Older accounts will deserialize, and accessing var_e from older accounts will always give you 0 (unless you messed with the reserved space previously), which is probably what you intended.

4
  • Can't accounts be realloc-ed, including in anchor? I've used it before to expand on a vector stored in an account. Oct 18, 2023 at 16:30
  • Yes that's true, I thought the question was using an A account after updating the account to include B, without modifying the old A account. If you want account A to be compatible with the B version, AND use the variable you added in B like in your example, you can do that only if the A account has padding where B adds the variable.
    – Whiteseal
    Oct 18, 2023 at 16:33
  • @Whiteseal Hello, thanks for the post, is it possible to add reserves to the struct without having to use zero_copy?
    – Kise
    Jan 29 at 9:57
  • You can add reserves to regular accounts the same way, but you might have to be more careful with de-serializing an old account that had buffer space where your new account has some var, as that space will be all byte 0s.
    – Whiteseal
    Jan 29 at 20:59
0

i think you can't because if you change the struct of your account's data, then anchor can't deserialize it (the old ones, because the struct is not the same anymore).

I see only 2 ways to go :

  • create an instruction to transform pda v1 into pda v2 (with new seeds & new struct)
  • use 2 pdas in your account (v1 & v2), one being empty / not initialized (anchor will probably throw an error if the account is not initialized tho...), and this implies that you have 2 differents seeds structure also.

IMO, writing an instruction to transform v1 into v2 remains the best option (and if a user want to use an instruction that requires v2 account, but you have a v1 account, then just add the upgrade instruction before the other one in you transaction, this should work great).

0
  1. You should first reallocate PDA account size by adding additional 4bytes in your example. Remember you should not change account schema yet.
#[derive(Accounts)]
pub struct ResizeAccountA<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,
    #[account(mut, realloc = 8+4+4, realloc::payer = payer, realloc::zero = false)]
    pub house: Account<'info, House>,
    pub system_program: Program<'info, System>,
}
  1. Now, you have added additional space for your AccountA to store new u32 data. Update your AccountA schema with variable_b: u32 and add update instructions to update this new values which is set to 0 by default.
  2. After migration is done, you can get rid of update instruction if you no longer need it.
2
  • Does this work? If so this is a life saver Nov 23, 2023 at 20:58
  • @kevinrodriguez-io yes! I'd migrated some accounts for my mainnet programs and all worked as charm! Lmk if you need more questions. Nov 27, 2023 at 3:52

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.