When an upgradeable Solana program needs to change its account data layout — adding fields, removing fields, or changing field types — existing on-chain accounts must be migrated to the new format.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/blueshift-gg/quasar/llms.txt
Use this file to discover all available pages before exploring further.
Migration<From, To> is Quasar’s type-safe mechanism for performing this transition atomically in a single instruction. During account parsing it validates the account as From; in the handler the migrate() method rewrites it as To in-place.
Why Account Migration Is Needed
Quasar validates accounts using a discriminator (a developer-specified byte prefix) and an exact data length check. When you add a field to an account struct, the discriminator changes and/or the required data length grows. Existing on-chain accounts still have the old discriminator and old length — they would fail the newAccount<To> validation.
Migration<From, To> bridges this gap:
- At parse time, it validates the account as
From(old layout is accepted) - In the handler,
migrate()reallocates if needed and writes theTolayout - The account is now valid as
Account<To>and can be used as such
Compile-Time Contracts
Quasar enforces several invariants at compile time when you useMigration<From, To>:
const expressions. If any contract is violated, you get a compile error, not a runtime panic.
How Migration<From, To> Works
Migration<From, To> is #[repr(transparent)] over AccountView:
Parse Time (From validation)
AccountLoad::check validates the account as From:
Reading From Data
Migration<From, To> implements Deref<Target = From::Target>, giving read access to the current From account data:
The migrate() Method
migrate() performs the actual transition:
Guard checks
Asserts all compile-time migration contracts are satisfied. Verifies the account still has the
From discriminator (not already migrated). Returns ProgramError::AccountAlreadyInitialized if the To discriminator is already present.Realloc if needed
Calls
realloc_account(&mut self.__view, To::SPACE, payer, None). If To::SPACE > From::SPACE, lamports are transferred from payer to make the account rent-exempt at the new size. If To::SPACE < From::SPACE, excess lamports are returned to payer.Write To layout
Copies
To::DISCRIMINATOR bytes to the start of account data, then copies the new_data: To::Target bytes immediately after the discriminator.Declaring Migration in an Accounts Struct
Declare the migration field as a plain ownedMigration<From, To> value in your accounts struct. The handler mutably borrows the field through &mut self when calling .migrate():
Complete Migration Example
The following is taken from the Quasar test suite’stest-migrate program:
- State definitions
- Migration instruction
Discriminator Handling
The discriminator transition is explicit and safe:- Before migration: account data starts with
From::DISCRIMINATOR - After migration: account data starts with
To::DISCRIMINATOR
To discriminator is present) and that it has a valid From discriminator (rejects if neither is present). This makes migration idempotent — calling it twice on the same account fails on the second call rather than corrupting data.
The compile-time _DISC_NEQ assertion ensures that From::DISCRIMINATOR and To::DISCRIMINATOR are not prefixes of each other, preventing ambiguous discriminator matching.
Realloc and Rent
migrate() calls realloc_account which handles rent adjustment automatically:
- Growing (
To::SPACE > From::SPACE): transfersrent_exempt(To::SPACE) - current_lamportsfrompayerto the account - Shrinking (
To::SPACE < From::SPACE): returnscurrent_lamports - rent_exempt(To::SPACE)topayer - Same size (
To::SPACE == From::SPACE): no lamport change
migrate(). The migrate() method passes None to realloc_account, which triggers a syscall to read the current rent parameters. There is no API to provide a pre-loaded Sysvar<Rent> to migrate().
Migration<From, To> requires that From and To have the same owner program. This is enforced at compile time. Cross-program account migration (changing the owner) is not supported through this wrapper — use a manual CPI to the system program’s assign instruction instead.