1

Let us suppose that you want to update an account state to include a new field.

pub struct MyState {
  pub a: u8,
  pub new_field: u8
}

This means that fetching and decoding the old accounts prior to the update would no longer work the way it did before. I.e. using program.accounts.MyState.all().

So, I was wondering, how do you go about fetching and decoding both old and new accounts?

2 Answers 2

2

You can resize your state account using realloc. Anchor lets you do it like this.

#[derive(Accounts)]
#[instruction(size: usize)]
pub struct ResizeState<'info> {
    #[account(
    mut,
    has_one = update_authority,
    realloc = size,
    realloc::payer = payer,
    realloc::zero = false,
    )]
    pub state: Account<'info, State>,

    #[account(mut)]
    pub payer: Signer<'info>,

    pub update_authority: Signer<'info>,

    pub system_program: Program<'info, System>,
}
```rust

You retain your transaction history since it's the same address.
3
  • Thanks a ton! This means if I have an old account, I could pass it to such a resize instruction first, before procedding. If I have a new account I just continue as is?
    – Burger Bob
    Commented Feb 19, 2023 at 22:54
  • 1
    To retain the same account you'd have to do a realloc to fit the new space value. To use a new account you'd just init the account with the new space
    – Ademola
    Commented Feb 20, 2023 at 14:13
  • But to do the realloc you would have to do it through a program instruction, right?
    – Burger Bob
    Commented Feb 20, 2023 at 14:39
4

Next time, allocate extra space so there is room to upgrade, as try_from_slice_unchecked & client-side desers will (generally) work if there are extra bytes. New fields are Option<T> and will be deser'd to None.

Because you have existing account data, or if you've hit your padding limit, you should make a new account and add a migration path in your program.

Something like

[#account]
struct MyThing {
   pub a: u8,
}

[#account]
struct MyThingV2 {
   pub a: u8,
   pub b: u8,
}

const MY_THING_V2_SIZE = 8 + 1 + 1 + 100; // 100 for padding

if you're using a PDA and don't want to change, you could do something like this

let my_thing: MyThingV2 = match existing_account.data_len() {
    1 => {
        // deser & get V1 state. Resize existing_account to MY_THING_V2_SIZE bytes, return V2
    },
    110 => {
        // deser and return V2
    },
    _ => panic!("Unexpected length"),
}

otherwise, you can use anchor account macros to init the accountV2, pass in the accountV1, copy the data over & close v1. Include this as a separate ix that is optionally added to your tx if you detect that the accountV2 doesn't exist.

The first method reuses the existing account, which preserves transaction history and maintains the same PDA derivation, while the latter creates a completely new account.

Docs for realloc here. Good luck!

2
  • Ohh gotcha, thanks! I was wondering: (1) What does "deser" mean? (2) Would it also be possible to have the transaction history of the old account copied over to account v2?
    – Burger Bob
    Commented Feb 19, 2023 at 8:47
  • 1
    deser - deserialize, and if you need to preserve the transaction history than I'd recommend using the first method which will preserve the account address and thus tx history.
    – Lando
    Commented Feb 19, 2023 at 22:51

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.