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    fn new(acc_info: &'info AccountInfo<'info>) -> AccountLoader<'info, T> {
114        Self {
115            acc_info,
116            phantom: PhantomData,
117        }
118    }
119
120    /// Constructs a new `Loader` from a previously initialized account.
121    #[inline(never)]
122    pub fn try_from(acc_info: &'info AccountInfo<'info>) -> Result<AccountLoader<'info, T>> {
123        if acc_info.owner != &T::owner() {
124            return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram)
125                .with_pubkeys((*acc_info.owner, T::owner())));
126        }
127
128        let data = &acc_info.try_borrow_data()?;
129        let disc = T::DISCRIMINATOR;
130        if data.len() < disc.len() {
131            return Err(ErrorCode::AccountDiscriminatorNotFound.into());
132        }
133
134        let given_disc = &data[..disc.len()];
135        if given_disc != disc {
136            return Err(ErrorCode::AccountDiscriminatorMismatch.into());
137        }
138
139        Ok(AccountLoader::new(acc_info))
140    }
141
142    /// Constructs a new `Loader` from an uninitialized account.
143    #[inline(never)]
144    pub fn try_from_unchecked(
145        _program_id: &Pubkey,
146        acc_info: &'info AccountInfo<'info>,
147    ) -> Result<AccountLoader<'info, T>> {
148        if acc_info.owner != &T::owner() {
149            return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram)
150                .with_pubkeys((*acc_info.owner, T::owner())));
151        }
152        Ok(AccountLoader::new(acc_info))
153    }
154
155    /// Returns a Ref to the account data structure for reading.
156    pub fn load(&self) -> Result<Ref<'_, T>> {
157        let data = self.acc_info.try_borrow_data()?;
158        let disc = T::DISCRIMINATOR;
159        if data.len() < disc.len() {
160            return Err(ErrorCode::AccountDiscriminatorNotFound.into());
161        }
162
163        let given_disc = &data[..disc.len()];
164        if given_disc != disc {
165            return Err(ErrorCode::AccountDiscriminatorMismatch.into());
166        }
167
168        Ok(Ref::map(data, |data| {
169            bytemuck::from_bytes(&data[disc.len()..mem::size_of::<T>() + disc.len()])
170        }))
171    }
172
173    /// Returns a `RefMut` to the account data structure for reading or writing.
174    pub fn load_mut(&self) -> Result<RefMut<'_, T>> {
175        // AccountInfo api allows you to borrow mut even if the account isn't
176        // writable, so add this check for a better dev experience.
177        if !self.acc_info.is_writable {
178            return Err(ErrorCode::AccountNotMutable.into());
179        }
180
181        let data = self.acc_info.try_borrow_mut_data()?;
182        let disc = T::DISCRIMINATOR;
183        if data.len() < disc.len() {
184            return Err(ErrorCode::AccountDiscriminatorNotFound.into());
185        }
186
187        let given_disc = &data[..disc.len()];
188        if given_disc != disc {
189            return Err(ErrorCode::AccountDiscriminatorMismatch.into());
190        }
191
192        Ok(RefMut::map(data, |data| {
193            bytemuck::from_bytes_mut(
194                &mut data.deref_mut()[disc.len()..mem::size_of::<T>() + disc.len()],
195            )
196        }))
197    }
198
199    /// Returns a `RefMut` to the account data structure for reading or writing.
200    /// Should only be called once, when the account is being initialized.
201    pub fn load_init(&self) -> Result<RefMut<'_, T>> {
202        // AccountInfo api allows you to borrow mut even if the account isn't
203        // writable, so add this check for a better dev experience.
204        if !self.acc_info.is_writable {
205            return Err(ErrorCode::AccountNotMutable.into());
206        }
207
208        let data = self.acc_info.try_borrow_mut_data()?;
209
210        // The discriminator should be zero, since we're initializing.
211        let disc = T::DISCRIMINATOR;
212        let given_disc = &data[..disc.len()];
213        let has_disc = given_disc.iter().any(|b| *b != 0);
214        if has_disc {
215            return Err(ErrorCode::AccountDiscriminatorAlreadySet.into());
216        }
217
218        Ok(RefMut::map(data, |data| {
219            bytemuck::from_bytes_mut(
220                &mut data.deref_mut()[disc.len()..mem::size_of::<T>() + disc.len()],
221            )
222        }))
223    }
224}
225
226impl<'info, B, T: ZeroCopy + Owner> Accounts<'info, B> for AccountLoader<'info, T> {
227    #[inline(never)]
228    fn try_accounts(
229        _program_id: &Pubkey,
230        accounts: &mut &'info [AccountInfo<'info>],
231        _ix_data: &[u8],
232        _bumps: &mut B,
233        _reallocs: &mut BTreeSet<Pubkey>,
234    ) -> Result<Self> {
235        if accounts.is_empty() {
236            return Err(ErrorCode::AccountNotEnoughKeys.into());
237        }
238        let account = &accounts[0];
239        *accounts = &accounts[1..];
240        let l = AccountLoader::try_from(account)?;
241        Ok(l)
242    }
243}
244
245impl<'info, T: ZeroCopy + Owner> AccountsExit<'info> for AccountLoader<'info, T> {
246    // The account *cannot* be loaded when this is called.
247    fn exit(&self, program_id: &Pubkey) -> Result<()> {
248        // Only persist if the owner is the current program and the account is not closed.
249        if &T::owner() == program_id && !crate::common::is_closed(self.acc_info) {
250            let mut data = self.acc_info.try_borrow_mut_data()?;
251            let dst: &mut [u8] = &mut data;
252            let mut writer = BpfWriter::new(dst);
253            writer.write_all(T::DISCRIMINATOR).unwrap();
254        }
255        Ok(())
256    }
257}
258
259impl<'info, T: ZeroCopy + Owner> AccountsClose<'info> for AccountLoader<'info, T> {
260    fn close(&self, sol_destination: AccountInfo<'info>) -> Result<()> {
261        crate::common::close(self.as_ref(), sol_destination.as_ref())
262    }
263}
264
265impl<T: ZeroCopy + Owner> ToAccountMetas for AccountLoader<'_, T> {
266    fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
267        let is_signer = is_signer.unwrap_or(self.acc_info.is_signer);
268        let meta = match self.acc_info.is_writable {
269            false => AccountMeta::new_readonly(*self.acc_info.key, is_signer),
270            true => AccountMeta::new(*self.acc_info.key, is_signer),
271        };
272        vec![meta]
273    }
274}
275
276impl<'info, T: ZeroCopy + Owner> AsRef<AccountInfo<'info>> for AccountLoader<'info, T> {
277    fn as_ref(&self) -> &AccountInfo<'info> {
278        self.acc_info
279    }
280}
281
282impl<'info, T: ZeroCopy + Owner> ToAccountInfos<'info> for AccountLoader<'info, T> {
283    fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
284        vec![self.acc_info.clone()]
285    }
286}
287
288impl<T: ZeroCopy + Owner> Key for AccountLoader<'_, T> {
289    fn key(&self) -> Pubkey {
290        *self.acc_info.key
291    }
292}