Skip to main content

anchor_lang/accounts/
account_loader.rs

1//! Type facilitating on demand zero copy deserialization.
2
3use {
4    crate::{
5        bpf_writer::BpfWriter,
6        error::{Error, ErrorCode},
7        solana_program::{account_info::AccountInfo, instruction::AccountMeta, pubkey::Pubkey},
8        Accounts, AccountsClose, AccountsExit, Key, Owner, Result, ToAccountInfos, ToAccountMetas,
9        ZeroCopy,
10    },
11    std::{
12        cell::{Ref, RefMut},
13        collections::BTreeSet,
14        fmt,
15        io::Write,
16        marker::PhantomData,
17        mem,
18        ops::DerefMut,
19    },
20};
21
22/// Type facilitating on demand zero copy deserialization.
23///
24/// Note that using accounts in this way is distinctly different from using,
25/// for example, the [`Account`](crate::accounts::account::Account). Namely,
26/// one must call
27/// - `load_init` after initializing an account (this will ignore the missing
28///   account discriminator that gets added only after the user's instruction code)
29/// - `load` when the account is not mutable
30/// - `load_mut` when the account is mutable
31///
32/// For more details on zero-copy-deserialization, see the
33/// [`account`](crate::account) attribute.
34/// <p style=";padding:0.75em;border: 1px solid #ee6868">
35/// <strong>⚠️ </strong> When using this type it's important to be mindful
36/// of any calls to the <code>load</code> functions so as not to
37/// induce a <code>RefCell</code> panic, especially when sharing accounts across CPI
38/// boundaries. When in doubt, one should make sure all refs resulting from
39/// a call to a <code>load</code> function are dropped before CPI.
40/// This can be done explicitly by calling <code>drop(my_var)</code> or implicitly
41/// by wrapping the code using the <code>Ref</code> in braces <code>{..}</code> or
42/// moving it into its own function.
43/// </p>
44///
45/// # Example
46/// ```ignore
47/// use anchor_lang::prelude::*;
48///
49/// declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
50///
51/// #[program]
52/// pub mod bar {
53///     use super::*;
54///
55///     pub fn create_bar(ctx: Context<CreateBar>, data: u64) -> Result<()> {
56///         let bar = &mut ctx.accounts.bar.load_init()?;
57///         bar.authority = ctx.accounts.authority.key();
58///         bar.data = data;
59///         Ok(())
60///     }
61///
62///     pub fn update_bar(ctx: Context<UpdateBar>, data: u64) -> Result<()> {
63///         (*ctx.accounts.bar.load_mut()?).data = data;
64///         Ok(())
65///     }
66/// }
67///
68/// #[account(zero_copy)]
69/// #[derive(Default)]
70/// pub struct Bar {
71///     authority: Pubkey,
72///     data: u64
73/// }
74///
75/// #[derive(Accounts)]
76/// pub struct CreateBar<'info> {
77///     #[account(
78///         init,
79///         payer = authority
80///     )]
81///     bar: AccountLoader<'info, Bar>,
82///     #[account(mut)]
83///     authority: Signer<'info>,
84///     system_program: AccountInfo<'info>,
85/// }
86///
87/// #[derive(Accounts)]
88/// pub struct UpdateBar<'info> {
89///     #[account(
90///         mut,
91///         has_one = authority,
92///     )]
93///     pub bar: AccountLoader<'info, Bar>,
94///     pub authority: Signer<'info>,
95/// }
96/// ```
97#[derive(Clone)]
98pub struct AccountLoader<'info, T: ZeroCopy + Owner> {
99    acc_info: &'info AccountInfo<'info>,
100    phantom: PhantomData<&'info T>,
101}
102
103impl<T: ZeroCopy + Owner + fmt::Debug> fmt::Debug for AccountLoader<'_, T> {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        f.debug_struct("AccountLoader")
106            .field("acc_info", &self.acc_info)
107            .field("phantom", &self.phantom)
108            .finish()
109    }
110}
111
112impl<'info, T: ZeroCopy + Owner> AccountLoader<'info, T> {
113    /// Constructs a new [`AccountLoader`] without performing any account validation checks.
114    ///
115    /// - [`Self::try_from`] to perform all checks, or
116    /// - [`Self::try_from_unchecked`] to check the owner but not the discriminator
117    pub fn new_unchecked(acc_info: &'info AccountInfo<'info>) -> AccountLoader<'info, T> {
118        Self {
119            acc_info,
120            phantom: PhantomData,
121        }
122    }
123
124    /// Constructs a new [`AccountLoader`] from a previously initialized account.
125    #[inline(never)]
126    pub fn try_from(acc_info: &'info AccountInfo<'info>) -> Result<AccountLoader<'info, T>> {
127        if acc_info.owner != &T::owner() {
128            return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram)
129                .with_pubkeys((*acc_info.owner, T::owner())));
130        }
131
132        let data = &acc_info.try_borrow_data()?;
133        let disc = T::DISCRIMINATOR;
134        if data.len() < disc.len() {
135            return Err(ErrorCode::AccountDiscriminatorNotFound.into());
136        }
137
138        let given_disc = &data[..disc.len()];
139        if given_disc != disc {
140            return Err(ErrorCode::AccountDiscriminatorMismatch.into());
141        }
142
143        Ok(AccountLoader::new_unchecked(acc_info))
144    }
145
146    /// Constructs a new [`AccountLoader`] from an uninitialized account.
147    #[inline(never)]
148    pub fn try_from_unchecked(
149        _program_id: &Pubkey,
150        acc_info: &'info AccountInfo<'info>,
151    ) -> Result<AccountLoader<'info, T>> {
152        if acc_info.owner != &T::owner() {
153            return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram)
154                .with_pubkeys((*acc_info.owner, T::owner())));
155        }
156        Ok(AccountLoader::new_unchecked(acc_info))
157    }
158
159    fn check_size(&self, data: &[u8]) -> Result<()> {
160        let required = T::DISCRIMINATOR
161            .len()
162            .checked_add(mem::size_of::<T>())
163            .ok_or(ErrorCode::AccountDidNotDeserialize)?;
164        if data.len() < required {
165            return Err(ErrorCode::AccountDidNotDeserialize.into());
166        }
167        Ok(())
168    }
169
170    /// Returns a Ref to the account data structure for reading.
171    pub fn load(&self) -> Result<Ref<'_, T>> {
172        let data = self.acc_info.try_borrow_data()?;
173        let disc = T::DISCRIMINATOR;
174        if data.len() < disc.len() {
175            return Err(ErrorCode::AccountDiscriminatorNotFound.into());
176        }
177
178        let given_disc = &data[..disc.len()];
179        if given_disc != disc {
180            return Err(ErrorCode::AccountDiscriminatorMismatch.into());
181        }
182
183        self.check_size(&data)?;
184
185        Ok(Ref::map(data, |data| {
186            bytemuck::from_bytes(&data[disc.len()..mem::size_of::<T>() + disc.len()])
187        }))
188    }
189
190    /// Returns a `RefMut` to the account data structure for reading or writing.
191    pub fn load_mut(&self) -> Result<RefMut<'_, T>> {
192        // AccountInfo api allows you to borrow mut even if the account isn't
193        // writable, so add this check for a better dev experience.
194        if !self.acc_info.is_writable {
195            return Err(ErrorCode::AccountNotMutable.into());
196        }
197
198        let data = self.acc_info.try_borrow_mut_data()?;
199        let disc = T::DISCRIMINATOR;
200        if data.len() < disc.len() {
201            return Err(ErrorCode::AccountDiscriminatorNotFound.into());
202        }
203
204        let given_disc = &data[..disc.len()];
205        if given_disc != disc {
206            return Err(ErrorCode::AccountDiscriminatorMismatch.into());
207        }
208
209        self.check_size(&data)?;
210
211        Ok(RefMut::map(data, |data| {
212            bytemuck::from_bytes_mut(
213                &mut data.deref_mut()[disc.len()..mem::size_of::<T>() + disc.len()],
214            )
215        }))
216    }
217
218    /// Returns a `RefMut` to the account data structure for reading or writing.
219    /// Should only be called once, when the account is being initialized.
220    pub fn load_init(&self) -> Result<RefMut<'_, T>> {
221        // AccountInfo api allows you to borrow mut even if the account isn't
222        // writable, so add this check for a better dev experience.
223        if !self.acc_info.is_writable {
224            return Err(ErrorCode::AccountNotMutable.into());
225        }
226
227        let data = self.acc_info.try_borrow_mut_data()?;
228
229        // The discriminator should be zero, since we're initializing.
230        let disc = T::DISCRIMINATOR;
231        self.check_size(&data)?;
232
233        let given_disc = &data[..disc.len()];
234        let has_disc = given_disc.iter().any(|b| *b != 0);
235        if has_disc {
236            return Err(ErrorCode::AccountDiscriminatorAlreadySet.into());
237        }
238
239        Ok(RefMut::map(data, |data| {
240            bytemuck::from_bytes_mut(
241                &mut data.deref_mut()[disc.len()..mem::size_of::<T>() + disc.len()],
242            )
243        }))
244    }
245}
246
247impl<'info, B, T: ZeroCopy + Owner> Accounts<'info, B> for AccountLoader<'info, T> {
248    #[inline(never)]
249    fn try_accounts(
250        _program_id: &Pubkey,
251        accounts: &mut &'info [AccountInfo<'info>],
252        _ix_data: &[u8],
253        _bumps: &mut B,
254        _reallocs: &mut BTreeSet<Pubkey>,
255    ) -> Result<Self> {
256        if accounts.is_empty() {
257            return Err(ErrorCode::AccountNotEnoughKeys.into());
258        }
259        let account = &accounts[0];
260        *accounts = &accounts[1..];
261        let l = AccountLoader::try_from(account)?;
262        Ok(l)
263    }
264}
265
266impl<'info, T: ZeroCopy + Owner> AccountsExit<'info> for AccountLoader<'info, T> {
267    // The account *cannot* be loaded when this is called.
268    fn exit(&self, program_id: &Pubkey) -> Result<()> {
269        // Only persist if the owner is the current program and the account is not closed.
270        if &T::owner() == program_id && !crate::common::is_closed(self.acc_info) {
271            // Guard against truncation: refuse to rewrite the discriminator over an undersized buffer.
272            let required = T::DISCRIMINATOR.len() + mem::size_of::<T>();
273            if self.acc_info.try_data_len()? < required {
274                return Err(ErrorCode::AccountDidNotDeserialize.into());
275            }
276            let mut data = self.acc_info.try_borrow_mut_data()?;
277            let dst: &mut [u8] = &mut data;
278            let mut writer = BpfWriter::new(dst);
279            writer.write_all(T::DISCRIMINATOR).unwrap();
280        }
281        Ok(())
282    }
283}
284
285impl<'info, T: ZeroCopy + Owner> AccountsClose<'info> for AccountLoader<'info, T> {
286    fn close(&self, sol_destination: AccountInfo<'info>) -> Result<()> {
287        crate::common::close(self.as_ref(), sol_destination.as_ref())
288    }
289}
290
291impl<T: ZeroCopy + Owner> ToAccountMetas for AccountLoader<'_, T> {
292    fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
293        let is_signer = is_signer.unwrap_or(self.acc_info.is_signer);
294        let meta = match self.acc_info.is_writable {
295            false => AccountMeta::new_readonly(*self.acc_info.key, is_signer),
296            true => AccountMeta::new(*self.acc_info.key, is_signer),
297        };
298        vec![meta]
299    }
300}
301
302impl<'info, T: ZeroCopy + Owner> AsRef<AccountInfo<'info>> for AccountLoader<'info, T> {
303    fn as_ref(&self) -> &AccountInfo<'info> {
304        self.acc_info
305    }
306}
307
308impl<'info, T: ZeroCopy + Owner> ToAccountInfos<'info> for AccountLoader<'info, T> {
309    fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
310        vec![self.acc_info.clone()]
311    }
312}
313
314impl<T: ZeroCopy + Owner> Key for AccountLoader<'_, T> {
315    fn key(&self) -> Pubkey {
316        *self.acc_info.key
317    }
318}