Skip to content

Commit d7ace7a

Browse files
L0STEjacobcreech
andauthored
feat: Add Migration<'info, From, To> account type (#4060)
* Added a migration type * Fixed my midcurved implementation * Fixed some errors + added realloc for migration type * Last nit then up only * Better naming * Updated to always be stricted * Simplified and improved * Last nit then really up only * Additions and Fixes based on comments * clippy * last addition * last nits * Update error.rs * Update migration.rs * fix: fmt error --------- Co-authored-by: Jacob Creech <82475023+jacobcreech@users.noreply.github.com>
1 parent 3441f2d commit d7ace7a

File tree

9 files changed

+1017
-7
lines changed

9 files changed

+1017
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ The minor version will be incremented upon a breaking change and the patch versi
1212

1313
### Features
1414

15+
- lang: Add `Migration<'info, From, To>` account type for schema migrations between account types ([#4060](https://github.com/solana-foundation/anchor/pull/4060)).
1516
- 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`.
1617
- lang: lang: Add instruction parser to `declare_program!` ([#4118](https://github.com/solana-foundation/anchor/pull/4118)).
1718

docs/content/docs/references/account-types.mdx

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,3 +230,74 @@ pub struct InstructionAccounts<'info> {
230230
pub account: UncheckedAccount<'info>,
231231
}
232232
```
233+
234+
### `Migration<'info, From, To>`
235+
236+
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.
237+
238+
Checks:
239+
- `Account.info.owner == From::owner()`
240+
- Account is initialized (not owned by system program with 0 lamports)
241+
- Account deserializes as `From` type
242+
243+
```rust title="snippet"
244+
use anchor_lang::prelude::*;
245+
246+
#[account]
247+
pub struct AccountV1 {
248+
pub data: u64,
249+
}
250+
251+
#[account]
252+
pub struct AccountV2 {
253+
pub data: u64,
254+
pub new_field: u64,
255+
}
256+
257+
#[derive(Accounts)]
258+
pub struct MigrateAccount<'info> {
259+
#[account(mut)]
260+
pub payer: Signer<'info>,
261+
// [!code word:Migration]
262+
// [!code highlight:5]
263+
#[account(
264+
mut,
265+
realloc = 8 + AccountV2::INIT_SPACE,
266+
realloc::payer = payer,
267+
realloc::zero = false
268+
)]
269+
pub my_account: Migration<'info, AccountV1, AccountV2>,
270+
pub system_program: Program<'info, System>,
271+
}
272+
```
273+
274+
Usage patterns:
275+
276+
```rust title="migrate with explicit call"
277+
// Access old fields via Deref before migration
278+
let old_data = ctx.accounts.my_account.data;
279+
280+
// Migrate to new schema
281+
ctx.accounts.my_account.migrate(AccountV2 {
282+
data: old_data,
283+
new_field: 42,
284+
})?;
285+
```
286+
287+
```rust title="idempotent migration with into_inner"
288+
// Migrates if needed, returns reference to new data
289+
let migrated = ctx.accounts.my_account.into_inner(AccountV2 {
290+
data: ctx.accounts.my_account.data,
291+
new_field: ctx.accounts.my_account.data * 2,
292+
});
293+
msg!("New field: {}", migrated.new_field);
294+
```
295+
296+
```rust title="idempotent migration with mutation"
297+
// Migrates if needed, returns mutable reference
298+
let migrated = ctx.accounts.my_account.into_inner_mut(AccountV2 {
299+
data: ctx.accounts.my_account.data,
300+
new_field: 0,
301+
});
302+
migrated.new_field = 42;
303+
```

0 commit comments

Comments
 (0)