Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ The minor version will be incremented upon a breaking change and the patch versi

### Features

- lang: Add `Migration<'info, From, To>` account type for schema migrations between account types ([#4060](https://github.com/solana-foundation/anchor/pull/4060)).
- cli: Added a `check_program_id_mismatch` in build time to check if the program ID in the source code matches the program ID in the keypair file ([#4018](https://github.com/solana-foundation/anchor/pull/4018)). This check will be skipped during `anchor test`.

### Fixes
Expand Down
71 changes: 71 additions & 0 deletions docs/content/docs/references/account-types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,74 @@ pub struct InstructionAccounts<'info> {
pub account: UncheckedAccount<'info>,
}
```

### `Migration<'info, From, To>`

Description: Account container that handles schema migrations from one account type (`From`) to another (`To`). During deserialization, the account must be in the `From` format. On instruction exit, the account must be migrated to the `To` format, which is then serialized. Typically used with the `realloc` constraint to resize accounts during migration.

Checks:
- `Account.info.owner == From::owner()`
- Account is initialized (not owned by system program with 0 lamports)
- Account deserializes as `From` type

```rust title="snippet"
use anchor_lang::prelude::*;

#[account]
pub struct AccountV1 {
pub data: u64,
}

#[account]
pub struct AccountV2 {
pub data: u64,
pub new_field: u64,
}

#[derive(Accounts)]
pub struct MigrateAccount<'info> {
#[account(mut)]
pub payer: Signer<'info>,
// [!code word:Migration]
// [!code highlight:5]
#[account(
mut,
realloc = 8 + AccountV2::INIT_SPACE,
realloc::payer = payer,
realloc::zero = false
)]
pub my_account: Migration<'info, AccountV1, AccountV2>,
pub system_program: Program<'info, System>,
}
```

Usage patterns:

```rust title="migrate with explicit call"
// Access old fields via Deref before migration
let old_data = ctx.accounts.my_account.data;

// Migrate to new schema
ctx.accounts.my_account.migrate(AccountV2 {
data: old_data,
new_field: 42,
})?;
```

```rust title="idempotent migration with into_inner"
// Migrates if needed, returns reference to new data
let migrated = ctx.accounts.my_account.into_inner(AccountV2 {
data: ctx.accounts.my_account.data,
new_field: ctx.accounts.my_account.data * 2,
});
msg!("New field: {}", migrated.new_field);
```

```rust title="idempotent migration with mutation"
// Migrates if needed, returns mutable reference
let migrated = ctx.accounts.my_account.into_inner_mut(AccountV2 {
data: ctx.accounts.my_account.data,
new_field: 0,
});
migrated.new_field = 42;
```
Loading