anchor_lang/accounts/
option.rs

1//! Option<T> type for optional accounts.
2//!
3//! # Example
4//! ```ignore
5//! #[derive(Accounts)]
6//! pub struct Example {
7//!     pub my_acc: Option<Account<'info, MyData>>
8//! }
9//! ```
10
11use std::collections::BTreeSet;
12
13use solana_program::account_info::AccountInfo;
14use solana_program::instruction::AccountMeta;
15use solana_program::pubkey::Pubkey;
16
17use crate::{
18    error::ErrorCode, Accounts, AccountsClose, AccountsExit, Result, ToAccountInfos, ToAccountMetas,
19};
20
21impl<'info, B, T: Accounts<'info, B>> Accounts<'info, B> for Option<T> {
22    fn try_accounts(
23        program_id: &Pubkey,
24        accounts: &mut &'info [AccountInfo<'info>],
25        ix_data: &[u8],
26        bumps: &mut B,
27        reallocs: &mut BTreeSet<Pubkey>,
28    ) -> Result<Self> {
29        if accounts.is_empty() {
30            return if cfg!(feature = "allow-missing-optionals") {
31                // We don't care if accounts is empty (when this feature is active),
32                // so if that's the case we return None. This allows adding optional
33                // accounts at the end of the Accounts struct without causing a breaking
34                // change. This is safe and will error out if a required account is then
35                // added after the optional account and the accounts aren't passed in.
36                Ok(None)
37            } else {
38                // If the feature is inactive (it is off by default), then we error out
39                // like every other Account.
40                Err(ErrorCode::AccountNotEnoughKeys.into())
41            };
42        }
43
44        // If there are enough accounts, it will check the program_id and return
45        // None if it matches, popping the first account off the accounts vec.
46        if accounts[0].key == program_id {
47            *accounts = &accounts[1..];
48            Ok(None)
49        } else {
50            // If the program_id doesn't equal the account key, we default to
51            // the try_accounts implementation for the inner type and then wrap that with
52            // Some. This should handle all possible valid cases.
53            T::try_accounts(program_id, accounts, ix_data, bumps, reallocs).map(Some)
54        }
55    }
56}
57
58impl<'info, T: ToAccountInfos<'info>> ToAccountInfos<'info> for Option<T> {
59    fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
60        self.as_ref()
61            .map_or_else(Vec::new, |account| account.to_account_infos())
62    }
63}
64
65impl<T: ToAccountMetas> ToAccountMetas for Option<T> {
66    fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
67        self.as_ref()
68            .expect("Cannot run `to_account_metas` on None")
69            .to_account_metas(is_signer)
70    }
71}
72
73impl<'info, T: AccountsClose<'info>> AccountsClose<'info> for Option<T> {
74    fn close(&self, sol_destination: AccountInfo<'info>) -> Result<()> {
75        self.as_ref()
76            .map_or(Ok(()), |t| T::close(t, sol_destination))
77    }
78}
79
80impl<'info, T: AccountsExit<'info>> AccountsExit<'info> for Option<T> {
81    fn exit(&self, program_id: &Pubkey) -> Result<()> {
82        self.as_ref().map_or(Ok(()), |t| t.exit(program_id))
83    }
84}