Skip to main content

hopper_core/accounts/
migrating.rs

1//! Dual-layout migration account wrapper.
2//!
3//! Wraps an account that is transitioning from one layout version to another.
4//! Provides access to the old layout for reading and the new layout for writing
5//! after migration.
6
7use hopper_runtime::error::ProgramError;
8use hopper_runtime::{AccountView, Address};
9
10use crate::account::{FixedLayout, Pod, VerifiedAccount, VerifiedAccountMut};
11use crate::check;
12use crate::check::modifier::HopperLayout;
13use crate::migrate::MigrationKind;
14
15/// An account undergoing migration from layout `From` to layout `To`.
16///
17/// Construction validates that the account currently holds a `From` layout.
18/// After calling `migrate_append()` or performing manual migration, the
19/// caller can access the `To` layout via `into_latest()`.
20pub struct MigratingAccount<'a, From, To>
21where
22    From: Pod + FixedLayout + HopperLayout,
23    To: Pod + FixedLayout + HopperLayout,
24{
25    view: &'a AccountView,
26    program_id: &'a Address,
27    _from: core::marker::PhantomData<From>,
28    _to: core::marker::PhantomData<To>,
29}
30
31impl<'a, From, To> MigratingAccount<'a, From, To>
32where
33    From: Pod + FixedLayout + HopperLayout,
34    To: Pod + FixedLayout + HopperLayout,
35{
36    /// Construct from an AccountView, validating it holds the `From` layout.
37    #[inline]
38    pub fn from_account(
39        account: &'a AccountView,
40        program_id: &'a Address,
41    ) -> Result<Self, ProgramError> {
42        check::check_owner(account, program_id)?;
43        check::check_writable(account)?;
44        let data = account.try_borrow()?;
45        crate::account::check_header(&data, From::DISC, From::VERSION, &From::LAYOUT_ID)?;
46        check::check_size(&data, From::LEN_WITH_HEADER)?;
47        Ok(Self {
48            view: account,
49            program_id,
50            _from: core::marker::PhantomData,
51            _to: core::marker::PhantomData,
52        })
53    }
54
55    /// Read the old layout (immutable).
56    #[inline]
57    pub fn old(&self) -> Result<VerifiedAccount<'a, From>, ProgramError> {
58        let data = self.view.try_borrow()?;
59        VerifiedAccount::from_ref(data)
60    }
61
62    /// Read the old layout (mutable) for in-place transformation.
63    #[inline]
64    pub fn old_mut(&self) -> Result<VerifiedAccountMut<'a, From>, ProgramError> {
65        let data = self.view.try_borrow_mut()?;
66        VerifiedAccountMut::from_ref_mut(data)
67    }
68
69    /// Access the new layout after migration has been applied.
70    ///
71    /// The caller must have already performed the migration (realloc + header
72    /// update) before calling this. The header is re-validated against `To`.
73    #[inline]
74    pub fn into_latest(&self) -> Result<VerifiedAccountMut<'a, To>, ProgramError> {
75        let data = self.view.try_borrow_mut()?;
76        crate::account::check_header(&data, To::DISC, To::VERSION, &To::LAYOUT_ID)?;
77        check::check_size(&data, To::LEN_WITH_HEADER)?;
78        VerifiedAccountMut::from_ref_mut(data)
79    }
80
81    /// Perform an append migration in-place using the existing migration helper.
82    ///
83    /// After this returns successfully, `into_latest()` will succeed.
84    #[inline]
85    pub fn migrate_append(&self, payer: &AccountView) -> Result<(), ProgramError> {
86        crate::migrate::migrate_append(
87            self.view,
88            payer,
89            self.program_id,
90            &From::LAYOUT_ID,
91            To::VERSION,
92            &To::LAYOUT_ID,
93            To::DISC,
94            To::LEN_WITH_HEADER,
95        )
96    }
97
98    /// Determine what kind of migration is needed.
99    ///
100    /// Append migration is valid when the new layout is strictly larger
101    /// and shares the same base prefix as the old layout.
102    #[inline]
103    pub fn migration_kind(&self) -> MigrationKind {
104        if To::LEN_WITH_HEADER > From::LEN_WITH_HEADER && To::DISC == From::DISC {
105            MigrationKind::Append
106        } else {
107            MigrationKind::Full
108        }
109    }
110
111    /// The account's address.
112    #[inline(always)]
113    pub fn address(&self) -> &Address {
114        self.view.address()
115    }
116
117    /// The underlying AccountView.
118    #[inline(always)]
119    pub fn to_account_view(&self) -> &'a AccountView {
120        self.view
121    }
122}