Skip to main content

anchor_lang/accounts/
migration.rs

1//! Account container for migrating from one account type to another.
2
3use {
4    crate::{
5        bpf_writer::BpfWriter,
6        error::{Error, ErrorCode},
7        solana_program::{
8            account_info::AccountInfo, instruction::AccountMeta, pubkey::Pubkey, system_program,
9        },
10        AccountDeserialize, AccountSerialize, Accounts, AccountsExit, Key, Owner, Result,
11        ToAccountInfos, ToAccountMetas,
12    },
13    std::{
14        collections::BTreeSet,
15        ops::{Deref, DerefMut},
16    },
17};
18
19/// Internal representation of the migration state.
20#[derive(Debug)]
21pub enum MigrationInner<From, To> {
22    /// Account is in old format, will be migrated and serialized on exit
23    From(From),
24    /// Account is already in new format, will be serialized on exit
25    To(To),
26}
27
28/// Wrapper around [`AccountInfo`]
29/// that handles account schema migrations from one type to another.
30///
31/// # Table of Contents
32/// - [Basic Functionality](#basic-functionality)
33/// - [Usage Patterns](#usage-patterns)
34/// - [Example](#example)
35///
36/// # Basic Functionality
37///
38/// `Migration` facilitates migrating account data from an old schema (`From`) to a new
39/// schema (`To`). During deserialization, the account must be in the `From` format -
40/// accounts already in the `To` format will be rejected with an error.
41///
42/// The migrated data is stored in memory and will be serialized to the account when the
43/// instruction exits. On exit, the account must be in the migrated state or an error will
44/// be returned.
45///
46/// This type is typically used with the `realloc` constraint to resize the account
47/// during migration.
48///
49/// Checks:
50///
51/// - `Account.info.owner == From::owner()`
52/// - `!(Account.info.owner == SystemProgram && Account.info.lamports() == 0)`
53/// - Account must deserialize as `From` (not `To`)
54///
55/// # Usage Patterns
56///
57/// There are multiple ways to work with Migration accounts:
58///
59/// ## 1. Explicit Migration with `migrate()`
60///
61/// ```ignore
62/// ctx.accounts.my_account.migrate(AccountV2 {
63///     data: ctx.accounts.my_account.data,
64///     new_field: 42,
65/// })?;
66/// ```
67///
68/// ## 2. Direct Field Access via Deref (before migration)
69///
70/// ```ignore
71/// // Access old account fields directly
72/// let old_value = ctx.accounts.my_account.data;
73/// let old_timestamp = ctx.accounts.my_account.timestamp;
74///
75/// // Then migrate
76/// ctx.accounts.my_account.migrate(AccountV2 { ... })?;
77/// ```
78///
79/// ## 3. Idempotent Migration with `into_inner()`
80///
81/// ```ignore
82/// // Migrates if needed, returns reference to new data
83/// // Access old fields directly via deref!
84/// let migrated = ctx.accounts.my_account.into_inner(AccountV2 {
85///     data: ctx.accounts.my_account.data,
86///     new_field: ctx.accounts.my_account.data * 2,
87/// })?;
88///
89/// // Use migrated data (safe to call multiple times!)
90/// msg!("New field: {}", migrated.new_field);
91/// ```
92///
93/// ## 4. Idempotent Migration with Mutation via `into_inner_mut()`
94///
95/// ```ignore
96/// // Migrates if needed, returns mutable reference
97/// let migrated = ctx.accounts.my_account.into_inner_mut(AccountV2 {
98///     data: ctx.accounts.my_account.data,
99///     new_field: 0,
100/// })?;
101///
102/// // Mutate the new data
103/// migrated.new_field = 42;
104/// ```
105///
106/// # Example
107/// ```ignore
108/// use anchor_lang::prelude::*;
109///
110/// declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
111///
112/// #[program]
113/// pub mod my_program {
114///     use super::*;
115///
116///     pub fn migrate(ctx: Context<MigrateAccount>) -> Result<()> {
117///         // Use idempotent migration with into_inner
118///         let migrated = ctx.accounts.my_account.into_inner(AccountV2 {
119///             data: ctx.accounts.my_account.data,
120///             new_field: ctx.accounts.my_account.data * 2,
121///         })?;
122///
123///         msg!("Migrated! New field: {}", migrated.new_field);
124///         Ok(())
125///     }
126/// }
127///
128/// #[account]
129/// pub struct AccountV1 {
130///     pub data: u64,
131/// }
132///
133/// #[account]
134/// pub struct AccountV2 {
135///     pub data: u64,
136///     pub new_field: u64,
137/// }
138///
139/// #[derive(Accounts)]
140/// pub struct MigrateAccount<'info> {
141///     #[account(mut)]
142///     pub payer: Signer<'info>,
143///     #[account(
144///         mut,
145///         realloc = 8 + AccountV2::INIT_SPACE,
146///         realloc::payer = payer,
147///         realloc::zero = false
148///     )]
149///     pub my_account: Migration<'info, AccountV1, AccountV2>,
150///     pub system_program: Program<'info, System>,
151/// }
152/// ```
153#[derive(Debug)]
154pub struct Migration<'info, From, To>
155where
156    From: AccountDeserialize,
157    To: AccountSerialize,
158{
159    /// Account info reference
160    info: &'info AccountInfo<'info>,
161    /// Internal migration state
162    inner: MigrationInner<From, To>,
163}
164
165impl<'info, From, To> Migration<'info, From, To>
166where
167    From: AccountDeserialize + Owner,
168    To: AccountSerialize + Owner,
169{
170    /// Creates a new Migration in the From (unmigrated) state.
171    fn new(info: &'info AccountInfo<'info>, account: From) -> Self {
172        Self {
173            info,
174            inner: MigrationInner::From(account),
175        }
176    }
177
178    /// Returns `true` if the account has been migrated.
179    #[inline(always)]
180    pub fn is_migrated(&self) -> bool {
181        matches!(self.inner, MigrationInner::To(_))
182    }
183
184    /// Returns a reference to the old account data if not yet migrated.
185    ///
186    /// # Errors
187    /// Returns an error if the account has already been migrated.
188    pub fn try_as_from(&self) -> Result<&From> {
189        match &self.inner {
190            MigrationInner::From(from) => Ok(from),
191            MigrationInner::To(_) => Err(ErrorCode::AccountAlreadyMigrated.into()),
192        }
193    }
194
195    /// Returns a mutable reference to the old account data if not yet migrated.
196    ///
197    /// # Errors
198    /// Returns an error if the account has already been migrated.
199    pub fn try_as_from_mut(&mut self) -> Result<&mut From> {
200        match &mut self.inner {
201            MigrationInner::From(from) => Ok(from),
202            MigrationInner::To(_) => Err(ErrorCode::AccountAlreadyMigrated.into()),
203        }
204    }
205
206    /// Migrates the account by providing the new data.
207    ///
208    /// This method stores the new data in memory. The data will be
209    /// serialized to the account when the instruction exits.
210    ///
211    /// # Errors
212    /// Returns an error if the account has already been migrated.
213    pub fn migrate(&mut self, new_data: To) -> Result<()> {
214        if self.is_migrated() {
215            return Err(ErrorCode::AccountAlreadyMigrated.into());
216        }
217
218        self.inner = MigrationInner::To(new_data);
219        Ok(())
220    }
221
222    /// Gets a reference to the migrated value, or migrates it with the provided data.
223    ///
224    /// This method provides flexible access to the migrated state:
225    /// - If already migrated, returns a reference to the existing value
226    /// - If not migrated, migrates with the provided data, then returns a reference
227    ///
228    /// # Arguments
229    /// * `new_data` - The new `To` value to migrate to (only used if not yet migrated)
230    ///
231    /// # Example
232    /// ```ignore
233    /// pub fn process(ctx: Context<MyInstruction>) -> Result<()> {
234    ///     // Migrate and get reference in one call
235    ///     // Access old fields directly via deref!
236    ///     let migrated = ctx.accounts.my_account.into_inner(AccountV2 {
237    ///         data: ctx.accounts.my_account.data,
238    ///         new_field: 42,
239    ///     })?;
240    ///
241    ///     // Use migrated...
242    ///     msg!("Migrated data: {}", migrated.data);
243    ///
244    ///     Ok(())
245    /// }
246    /// ```
247    pub fn into_inner(&mut self, new_data: To) -> &To {
248        if !self.is_migrated() {
249            self.inner = MigrationInner::To(new_data);
250        }
251
252        match &self.inner {
253            MigrationInner::To(to) => to,
254            _ => unreachable!(),
255        }
256    }
257
258    /// Gets a mutable reference to the migrated value, or migrates it with the provided data.
259    ///
260    /// This method provides flexible mutable access to the migrated state:
261    /// - If already migrated, returns a mutable reference to the existing value
262    /// - If not migrated, migrates with the provided data, then returns a mutable reference
263    ///
264    /// # Arguments
265    /// * `new_data` - The new `To` value to migrate to (only used if not yet migrated)
266    ///
267    /// # Example
268    /// ```ignore
269    /// pub fn process(ctx: Context<MyInstruction>) -> Result<()> {
270    ///     // Migrate and get mutable reference in one call
271    ///     // Access old fields directly via deref!
272    ///     let migrated = ctx.accounts.my_account.into_inner_mut(AccountV2 {
273    ///         data: ctx.accounts.my_account.data,
274    ///         new_field: 0,
275    ///     })?;
276    ///
277    ///     // Mutate the migrated value
278    ///     migrated.new_field = 42;
279    ///
280    ///     Ok(())
281    /// }
282    /// ```
283    pub fn into_inner_mut(&mut self, new_data: To) -> &mut To {
284        if !self.is_migrated() {
285            self.inner = MigrationInner::To(new_data);
286        }
287
288        match &mut self.inner {
289            MigrationInner::To(to) => to,
290            _ => unreachable!(),
291        }
292    }
293
294    /// Deserializes the given `info` into a `Migration`.
295    ///
296    /// Only accepts accounts in the `From` format. Accounts already in the `To`
297    /// format will be rejected.
298    #[inline(never)]
299    pub fn try_from(info: &'info AccountInfo<'info>) -> Result<Self> {
300        if info.owner == &system_program::ID && info.lamports() == 0 {
301            return Err(ErrorCode::AccountNotInitialized.into());
302        }
303
304        if info.owner != &From::owner() {
305            return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram)
306                .with_pubkeys((*info.owner, From::owner())));
307        }
308
309        let mut data: &[u8] = &info.try_borrow_data()?;
310        Ok(Self::new(info, From::try_deserialize(&mut data)?))
311    }
312
313    /// Deserializes the given `info` into a `Migration` without checking
314    /// the account discriminator.
315    ///
316    /// **Warning:** Use with caution. This skips discriminator validation.
317    #[inline(never)]
318    pub fn try_from_unchecked(info: &'info AccountInfo<'info>) -> Result<Self> {
319        if info.owner == &system_program::ID && info.lamports() == 0 {
320            return Err(ErrorCode::AccountNotInitialized.into());
321        }
322
323        if info.owner != &From::owner() {
324            return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram)
325                .with_pubkeys((*info.owner, From::owner())));
326        }
327
328        let mut data: &[u8] = &info.try_borrow_data()?;
329        Ok(Self::new(info, From::try_deserialize_unchecked(&mut data)?))
330    }
331}
332
333impl<'info, B, From, To> Accounts<'info, B> for Migration<'info, From, To>
334where
335    From: AccountDeserialize + Owner,
336    To: AccountSerialize + Owner,
337{
338    #[inline(never)]
339    fn try_accounts(
340        _program_id: &Pubkey,
341        accounts: &mut &'info [AccountInfo<'info>],
342        _ix_data: &[u8],
343        _bumps: &mut B,
344        _reallocs: &mut BTreeSet<Pubkey>,
345    ) -> Result<Self> {
346        if accounts.is_empty() {
347            return Err(ErrorCode::AccountNotEnoughKeys.into());
348        }
349        let account = &accounts[0];
350        *accounts = &accounts[1..];
351        Self::try_from(account)
352    }
353}
354
355impl<'info, From, To> AccountsExit<'info> for Migration<'info, From, To>
356where
357    From: AccountDeserialize + Owner,
358    To: AccountSerialize + Owner,
359{
360    fn exit(&self, program_id: &Pubkey) -> Result<()> {
361        // Check if account is closed
362        if crate::common::is_closed(self.info) {
363            return Ok(());
364        }
365
366        // Check that the account has been migrated and serialize
367        match &self.inner {
368            MigrationInner::From(_) => {
369                // Account was not migrated - this is an error
370                return Err(ErrorCode::AccountNotMigrated.into());
371            }
372            MigrationInner::To(to) => {
373                // Only persist if the owner is the current program
374                let expected_owner = To::owner();
375                if &expected_owner != program_id {
376                    return Err(Error::from(ErrorCode::InvalidProgramId)
377                        .with_pubkeys((*program_id, expected_owner)));
378                }
379
380                // Serialize the migrated data
381                let mut data = self.info.try_borrow_mut_data()?;
382                let dst: &mut [u8] = &mut data;
383                let mut writer = BpfWriter::new(dst);
384                to.try_serialize(&mut writer)?;
385            }
386        }
387
388        Ok(())
389    }
390}
391
392impl<From, To> ToAccountMetas for Migration<'_, From, To>
393where
394    From: AccountDeserialize,
395    To: AccountSerialize,
396{
397    fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
398        let is_signer = is_signer.unwrap_or(self.info.is_signer);
399        let meta = match self.info.is_writable {
400            false => AccountMeta::new_readonly(*self.info.key, is_signer),
401            true => AccountMeta::new(*self.info.key, is_signer),
402        };
403        vec![meta]
404    }
405}
406
407impl<'info, From, To> ToAccountInfos<'info> for Migration<'info, From, To>
408where
409    From: AccountDeserialize,
410    To: AccountSerialize,
411{
412    fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
413        vec![self.info.clone()]
414    }
415}
416
417impl<'info, From, To> AsRef<AccountInfo<'info>> for Migration<'info, From, To>
418where
419    From: AccountDeserialize,
420    To: AccountSerialize,
421{
422    fn as_ref(&self) -> &AccountInfo<'info> {
423        self.info
424    }
425}
426
427impl<From, To> Key for Migration<'_, From, To>
428where
429    From: AccountDeserialize,
430    To: AccountSerialize,
431{
432    fn key(&self) -> Pubkey {
433        *self.info.key
434    }
435}
436
437// Deref to From when account is in Old state
438impl<'info, From, To> Deref for Migration<'info, From, To>
439where
440    From: AccountDeserialize,
441    To: AccountSerialize,
442{
443    type Target = From;
444
445    fn deref(&self) -> &Self::Target {
446        match &self.inner {
447            MigrationInner::From(from) => from,
448            MigrationInner::To(_) => {
449                crate::solana_program::msg!("Cannot deref to From: account is already migrated.");
450                panic!();
451            }
452        }
453    }
454}
455
456// DerefMut to From when account is in Old state
457impl<'info, From, To> DerefMut for Migration<'info, From, To>
458where
459    From: AccountDeserialize,
460    To: AccountSerialize,
461{
462    fn deref_mut(&mut self) -> &mut Self::Target {
463        match &mut self.inner {
464            MigrationInner::From(from) => from,
465            MigrationInner::To(_) => {
466                crate::solana_program::msg!(
467                    "Cannot deref_mut to From: account is already migrated."
468                );
469                panic!();
470            }
471        }
472    }
473}
474
475#[cfg(test)]
476mod tests {
477    use {
478        super::*,
479        crate::{AnchorDeserialize, AnchorSerialize, Discriminator},
480    };
481
482    const TEST_DISCRIMINATOR_V1: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8];
483    const TEST_DISCRIMINATOR_V2: [u8; 8] = [8, 7, 6, 5, 4, 3, 2, 1];
484    const TEST_OWNER: Pubkey = Pubkey::new_from_array([1u8; 32]);
485    const TEST_OTHER_OWNER: Pubkey = Pubkey::new_from_array([2u8; 32]);
486
487    #[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize, PartialEq)]
488    struct AccountV1 {
489        pub data: u64,
490    }
491
492    impl Discriminator for AccountV1 {
493        const DISCRIMINATOR: &'static [u8] = &TEST_DISCRIMINATOR_V1;
494    }
495
496    impl Owner for AccountV1 {
497        fn owner() -> Pubkey {
498            TEST_OWNER
499        }
500    }
501
502    impl AccountSerialize for AccountV1 {
503        fn try_serialize<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
504            writer.write_all(&TEST_DISCRIMINATOR_V1)?;
505            AnchorSerialize::serialize(self, writer)?;
506            Ok(())
507        }
508    }
509
510    impl AccountDeserialize for AccountV1 {
511        fn try_deserialize(buf: &mut &[u8]) -> Result<Self> {
512            if buf.len() < 8 {
513                return Err(ErrorCode::AccountDiscriminatorNotFound.into());
514            }
515            let disc = &buf[..8];
516            if disc != TEST_DISCRIMINATOR_V1 {
517                return Err(ErrorCode::AccountDiscriminatorMismatch.into());
518            }
519            Self::try_deserialize_unchecked(buf)
520        }
521
522        fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self> {
523            let mut data = &buf[8..];
524            AnchorDeserialize::deserialize(&mut data)
525                .map_err(|_| ErrorCode::AccountDidNotDeserialize.into())
526        }
527    }
528
529    #[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize, PartialEq)]
530    struct AccountV2 {
531        pub data: u64,
532        pub new_field: u64,
533    }
534
535    impl Discriminator for AccountV2 {
536        const DISCRIMINATOR: &'static [u8] = &TEST_DISCRIMINATOR_V2;
537    }
538
539    impl Owner for AccountV2 {
540        fn owner() -> Pubkey {
541            TEST_OWNER
542        }
543    }
544
545    impl AccountSerialize for AccountV2 {
546        fn try_serialize<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
547            writer.write_all(&TEST_DISCRIMINATOR_V2)?;
548            AnchorSerialize::serialize(self, writer)?;
549            Ok(())
550        }
551    }
552
553    #[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize, PartialEq)]
554    struct AccountV2OtherOwner {
555        pub data: u64,
556        pub new_field: u64,
557    }
558
559    impl Owner for AccountV2OtherOwner {
560        fn owner() -> Pubkey {
561            TEST_OTHER_OWNER
562        }
563    }
564
565    impl AccountSerialize for AccountV2OtherOwner {
566        fn try_serialize<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
567            writer.write_all(&TEST_DISCRIMINATOR_V2)?;
568            AnchorSerialize::serialize(self, writer)?;
569            Ok(())
570        }
571    }
572
573    impl AccountDeserialize for AccountV2 {
574        fn try_deserialize(buf: &mut &[u8]) -> Result<Self> {
575            if buf.len() < 8 {
576                return Err(ErrorCode::AccountDiscriminatorNotFound.into());
577            }
578            let disc = &buf[..8];
579            if disc != TEST_DISCRIMINATOR_V2 {
580                return Err(ErrorCode::AccountDiscriminatorMismatch.into());
581            }
582            Self::try_deserialize_unchecked(buf)
583        }
584
585        fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self> {
586            let mut data = &buf[8..];
587            AnchorDeserialize::deserialize(&mut data)
588                .map_err(|_| ErrorCode::AccountDidNotDeserialize.into())
589        }
590    }
591
592    fn create_account_info<'a>(
593        key: &'a Pubkey,
594        owner: &'a Pubkey,
595        lamports: &'a mut u64,
596        data: &'a mut [u8],
597    ) -> AccountInfo<'a> {
598        AccountInfo::new(key, false, true, lamports, data, owner, false)
599    }
600
601    // Verifies that a freshly deserialized Migration account reports
602    // is_migrated() as false, since it starts in the From state.
603    #[test]
604    fn test_is_migrated_returns_false_initially() {
605        let key = Pubkey::default();
606        let mut lamports = 100;
607        let v1 = AccountV1 { data: 42 };
608        let mut data = vec![0u8; 100];
609        data[..8].copy_from_slice(&TEST_DISCRIMINATOR_V1);
610        v1.serialize(&mut &mut data[8..]).unwrap();
611
612        let info = create_account_info(&key, &TEST_OWNER, &mut lamports, &mut data);
613        let migration: Migration<AccountV1, AccountV2> = Migration::try_from(&info).unwrap();
614
615        assert!(!migration.is_migrated());
616    }
617
618    // Verifies that after calling migrate(), the account correctly
619    // reports is_migrated() as true.
620    #[test]
621    fn test_is_migrated_returns_true_after_migrate() {
622        let key = Pubkey::default();
623        let mut lamports = 100;
624        let v1 = AccountV1 { data: 42 };
625        let mut data = vec![0u8; 100];
626        data[..8].copy_from_slice(&TEST_DISCRIMINATOR_V1);
627        v1.serialize(&mut &mut data[8..]).unwrap();
628
629        let info = create_account_info(&key, &TEST_OWNER, &mut lamports, &mut data);
630        let mut migration: Migration<AccountV1, AccountV2> = Migration::try_from(&info).unwrap();
631
632        migration
633            .migrate(AccountV2 {
634                data: 42,
635                new_field: 100,
636            })
637            .unwrap();
638
639        assert!(migration.is_migrated());
640    }
641
642    // Verifies that try_as_from() successfully returns a reference to the
643    // old account data before migration has occurred.
644    #[test]
645    fn test_try_as_from_returns_data_before_migration() {
646        let key = Pubkey::default();
647        let mut lamports = 100;
648        let v1 = AccountV1 { data: 42 };
649        let mut data = vec![0u8; 100];
650        data[..8].copy_from_slice(&TEST_DISCRIMINATOR_V1);
651        v1.serialize(&mut &mut data[8..]).unwrap();
652
653        let info = create_account_info(&key, &TEST_OWNER, &mut lamports, &mut data);
654        let migration: Migration<AccountV1, AccountV2> = Migration::try_from(&info).unwrap();
655
656        let from = migration.try_as_from().unwrap();
657        assert_eq!(from.data, 42);
658    }
659
660    // Verifies that try_as_from() returns an error after migration,
661    // providing a safe alternative to Deref that won't panic.
662    #[test]
663    fn test_try_as_from_returns_error_after_migration() {
664        let key = Pubkey::default();
665        let mut lamports = 100;
666        let v1 = AccountV1 { data: 42 };
667        let mut data = vec![0u8; 100];
668        data[..8].copy_from_slice(&TEST_DISCRIMINATOR_V1);
669        v1.serialize(&mut &mut data[8..]).unwrap();
670
671        let info = create_account_info(&key, &TEST_OWNER, &mut lamports, &mut data);
672        let mut migration: Migration<AccountV1, AccountV2> = Migration::try_from(&info).unwrap();
673
674        migration
675            .migrate(AccountV2 {
676                data: 42,
677                new_field: 100,
678            })
679            .unwrap();
680
681        assert!(migration.try_as_from().is_err());
682    }
683
684    // Verifies that try_as_from_mut() allows mutable access to the old
685    // account data before migration, and changes are persisted.
686    #[test]
687    fn test_try_as_from_mut_works_before_migration() {
688        let key = Pubkey::default();
689        let mut lamports = 100;
690        let v1 = AccountV1 { data: 42 };
691        let mut data = vec![0u8; 100];
692        data[..8].copy_from_slice(&TEST_DISCRIMINATOR_V1);
693        v1.serialize(&mut &mut data[8..]).unwrap();
694
695        let info = create_account_info(&key, &TEST_OWNER, &mut lamports, &mut data);
696        let mut migration: Migration<AccountV1, AccountV2> = Migration::try_from(&info).unwrap();
697
698        let from = migration.try_as_from_mut().unwrap();
699        from.data = 100;
700        assert_eq!(migration.try_as_from().unwrap().data, 100);
701    }
702
703    // Verifies that calling migrate() twice returns an error,
704    // preventing accidental double-migration.
705    #[test]
706    fn test_migrate_fails_if_already_migrated() {
707        let key = Pubkey::default();
708        let mut lamports = 100;
709        let v1 = AccountV1 { data: 42 };
710        let mut data = vec![0u8; 100];
711        data[..8].copy_from_slice(&TEST_DISCRIMINATOR_V1);
712        v1.serialize(&mut &mut data[8..]).unwrap();
713
714        let info = create_account_info(&key, &TEST_OWNER, &mut lamports, &mut data);
715        let mut migration: Migration<AccountV1, AccountV2> = Migration::try_from(&info).unwrap();
716
717        migration
718            .migrate(AccountV2 {
719                data: 42,
720                new_field: 100,
721            })
722            .unwrap();
723        let result = migration.migrate(AccountV2 {
724            data: 42,
725            new_field: 200,
726        });
727
728        assert!(result.is_err());
729    }
730
731    // Verifies that into_inner() performs migration and returns a
732    // reference to the new account data in a single call.
733    #[test]
734    fn test_into_inner_migrates_and_returns_reference() {
735        let key = Pubkey::default();
736        let mut lamports = 100;
737        let v1 = AccountV1 { data: 42 };
738        let mut data = vec![0u8; 100];
739        data[..8].copy_from_slice(&TEST_DISCRIMINATOR_V1);
740        v1.serialize(&mut &mut data[8..]).unwrap();
741
742        let info = create_account_info(&key, &TEST_OWNER, &mut lamports, &mut data);
743        let mut migration: Migration<AccountV1, AccountV2> = Migration::try_from(&info).unwrap();
744
745        let to = migration.into_inner(AccountV2 {
746            data: 42,
747            new_field: 100,
748        });
749
750        assert_eq!(to.data, 42);
751        assert_eq!(to.new_field, 100);
752        assert!(migration.is_migrated());
753    }
754
755    // Verifies that into_inner() is idempotent - calling it multiple times
756    // returns the existing migrated data and ignores subsequent new_data arguments.
757    #[test]
758    fn test_into_inner_is_idempotent() {
759        let key = Pubkey::default();
760        let mut lamports = 100;
761        let v1 = AccountV1 { data: 42 };
762        let mut data = vec![0u8; 100];
763        data[..8].copy_from_slice(&TEST_DISCRIMINATOR_V1);
764        v1.serialize(&mut &mut data[8..]).unwrap();
765
766        let info = create_account_info(&key, &TEST_OWNER, &mut lamports, &mut data);
767        let mut migration: Migration<AccountV1, AccountV2> = Migration::try_from(&info).unwrap();
768
769        let to1 = migration.into_inner(AccountV2 {
770            data: 42,
771            new_field: 100,
772        });
773        assert_eq!(to1.new_field, 100);
774
775        // Second call should return existing value, not use the new data
776        let to2 = migration.into_inner(AccountV2 {
777            data: 42,
778            new_field: 999,
779        });
780        assert_eq!(to2.new_field, 100); // Still 100, not 999
781    }
782
783    // Verifies that into_inner_mut() returns a mutable reference,
784    // allowing modification of the migrated account data.
785    #[test]
786    fn test_into_inner_mut_allows_mutation() {
787        let key = Pubkey::default();
788        let mut lamports = 100;
789        let v1 = AccountV1 { data: 42 };
790        let mut data = vec![0u8; 100];
791        data[..8].copy_from_slice(&TEST_DISCRIMINATOR_V1);
792        v1.serialize(&mut &mut data[8..]).unwrap();
793
794        let info = create_account_info(&key, &TEST_OWNER, &mut lamports, &mut data);
795        let mut migration: Migration<AccountV1, AccountV2> = Migration::try_from(&info).unwrap();
796
797        let to = migration.into_inner_mut(AccountV2 {
798            data: 42,
799            new_field: 100,
800        });
801        to.new_field = 200;
802
803        let to_ref = migration.into_inner(AccountV2 {
804            data: 0,
805            new_field: 0,
806        });
807        assert_eq!(to_ref.new_field, 200);
808    }
809
810    // Verifies that Deref allows direct field access (e.g., account.data)
811    // before migration has occurred.
812    #[test]
813    fn test_deref_works_before_migration() {
814        let key = Pubkey::default();
815        let mut lamports = 100;
816        let v1 = AccountV1 { data: 42 };
817        let mut data = vec![0u8; 100];
818        data[..8].copy_from_slice(&TEST_DISCRIMINATOR_V1);
819        v1.serialize(&mut &mut data[8..]).unwrap();
820
821        let info = create_account_info(&key, &TEST_OWNER, &mut lamports, &mut data);
822        let migration: Migration<AccountV1, AccountV2> = Migration::try_from(&info).unwrap();
823
824        assert_eq!(migration.data, 42);
825    }
826
827    // Verifies that Deref panics after migration. This documents the current
828    // behavior - use try_as_from() for safe access that returns Result instead.
829    #[test]
830    #[should_panic]
831    fn test_deref_panics_after_migration() {
832        let key = Pubkey::default();
833        let mut lamports = 100;
834        let v1 = AccountV1 { data: 42 };
835        let mut data = vec![0u8; 100];
836        data[..8].copy_from_slice(&TEST_DISCRIMINATOR_V1);
837        v1.serialize(&mut &mut data[8..]).unwrap();
838
839        let info = create_account_info(&key, &TEST_OWNER, &mut lamports, &mut data);
840        let mut migration: Migration<AccountV1, AccountV2> = Migration::try_from(&info).unwrap();
841
842        migration
843            .migrate(AccountV2 {
844                data: 42,
845                new_field: 100,
846            })
847            .unwrap();
848
849        // This should panic
850        let _ = migration.data;
851    }
852
853    // Verifies that deserialization fails when the account owner doesn't
854    // match the expected program, preventing unauthorized access.
855    #[test]
856    fn test_try_from_fails_with_wrong_owner() {
857        let key = Pubkey::default();
858        let wrong_owner = Pubkey::new_from_array([99u8; 32]);
859        let mut lamports = 100;
860        let v1 = AccountV1 { data: 42 };
861        let mut data = vec![0u8; 100];
862        data[..8].copy_from_slice(&TEST_DISCRIMINATOR_V1);
863        v1.serialize(&mut &mut data[8..]).unwrap();
864
865        let info = create_account_info(&key, &wrong_owner, &mut lamports, &mut data);
866        let result: Result<Migration<AccountV1, AccountV2>> = Migration::try_from(&info);
867
868        assert!(result.is_err());
869    }
870
871    // Verifies that deserialization fails for uninitialized accounts
872    // (owned by system program with zero lamports).
873    #[test]
874    fn test_try_from_fails_with_uninitialized_account() {
875        let key = Pubkey::default();
876        let mut lamports = 0;
877        let mut data = vec![0u8; 100];
878
879        let info = create_account_info(&key, &system_program::ID, &mut lamports, &mut data);
880        let result: Result<Migration<AccountV1, AccountV2>> = Migration::try_from(&info);
881
882        assert!(result.is_err());
883    }
884
885    #[test]
886    fn test_exit_fails_when_migrated_owner_does_not_match_program_id() {
887        let key = Pubkey::default();
888        let mut lamports = 100;
889        let v1 = AccountV1 { data: 42 };
890        let mut data = vec![0u8; 100];
891        data[..8].copy_from_slice(&TEST_DISCRIMINATOR_V1);
892        v1.serialize(&mut &mut data[8..]).unwrap();
893
894        let info = create_account_info(&key, &TEST_OWNER, &mut lamports, &mut data);
895        let mut migration: Migration<AccountV1, AccountV2OtherOwner> =
896            Migration::try_from(&info).unwrap();
897
898        migration
899            .migrate(AccountV2OtherOwner {
900                data: 42,
901                new_field: 100,
902            })
903            .unwrap();
904
905        let err = migration
906            .exit(&TEST_OWNER)
907            .expect_err("exit must fail when migrated data cannot be persisted");
908        match err {
909            Error::AnchorError(e) => {
910                assert_eq!(e.error_code_number, ErrorCode::InvalidProgramId as u32);
911            }
912            other => panic!("unexpected error variant: {other:?}"),
913        }
914
915        let mut persisted_data: &[u8] = &info.try_borrow_data().unwrap();
916        let persisted = AccountV1::try_deserialize(&mut persisted_data).unwrap();
917        assert_eq!(persisted.data, 42);
918    }
919}