anchor_lang/accounts/
account_loader.rs

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