Skip to main content

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