Skip to main content

anchor_lang/accounts/
program.rs

1//! Type validating that the account is the given Program
2
3use {
4    crate::{
5        error::{Error, ErrorCode},
6        solana_program::{
7            account_info::AccountInfo,
8            bpf_loader_upgradeable::{self, UpgradeableLoaderState},
9            instruction::AccountMeta,
10            pubkey::Pubkey,
11        },
12        AccountDeserialize, Accounts, AccountsExit, Id, Key, Result, ToAccountInfos,
13        ToAccountMetas,
14    },
15    std::{collections::BTreeSet, fmt, marker::PhantomData, ops::Deref},
16};
17
18/// Type validating that the account is the given Program
19///
20/// The type has a `programdata_address` function that will return `Option::Some`
21/// if the program is owned by the [`BPFUpgradeableLoader`](https://docs.rs/solana-program/latest/solana_program/bpf_loader_upgradeable/index.html)
22/// which will contain the `programdata_address` property of the `Program` variant of the [`UpgradeableLoaderState`](https://docs.rs/solana-loader-v3-interface/latest/solana_loader_v3_interface/state/enum.UpgradeableLoaderState.html) enum.
23///
24/// # Table of Contents
25/// - [Basic Functionality](#basic-functionality)
26/// - [Generic Program Validation](#generic-program-validation)
27/// - [Out of the Box Types](#out-of-the-box-types)
28///
29/// # Basic Functionality
30///
31/// For `Program<'info, T>` where T implements Id:
32/// Checks:
33///
34/// - `account_info.key == expected_program`
35/// - `account_info.executable == true`
36///
37/// # Generic Program Validation
38///
39/// For `Program<'info>` (without type parameter):
40/// - Only checks: `account_info.executable == true`
41/// - Use this when you only need to verify that an address is executable,
42///   without validating against a specific program ID.
43///
44/// # Example
45/// ```ignore
46/// #[program]
47/// mod my_program {
48///     fn set_admin_settings(...){...}
49/// }
50///
51/// #[account]
52/// #[derive(Default)]
53/// pub struct AdminSettings {
54///     ...
55/// }
56///
57/// #[derive(Accounts)]
58/// pub struct SetAdminSettings<'info> {
59///     #[account(mut, seeds = [b"admin"], bump)]
60///     pub admin_settings: Account<'info, AdminSettings>,
61///     #[account(constraint = program.programdata_address()? == Some(program_data.key()))]
62///     pub program: Program<'info, MyProgram>,
63///     #[account(constraint = program_data.upgrade_authority_address == Some(authority.key()))]
64///     pub program_data: Account<'info, ProgramData>,
65///     pub authority: Signer<'info>,
66/// }
67/// ```
68/// The given program has a function with which the upgrade authority can set admin settings.
69///
70/// The required constraints are as follows:
71///
72/// - `program` is the account of the program itself.
73///   Its constraint checks that `program_data` is the account that contains the program's upgrade authority.
74///   Implicitly, this checks that `program` is a BPFUpgradeable program (`program.programdata_address()?`
75///   will be `None` if it's not).
76/// - `program_data`'s constraint checks that its upgrade authority is the `authority` account.
77/// - Finally, `authority` needs to sign the transaction.
78///
79/// ## Generic Program Example
80/// ```ignore
81/// #[derive(Accounts)]
82/// pub struct ValidateExecutableProgram<'info> {
83///     // Only validates that the provided account is executable
84///     pub any_program: Program<'info>,
85///     pub authority: Signer<'info>,
86/// }
87/// ```
88///
89/// # Out of the Box Types
90///
91/// Between the [`anchor_lang`](https://docs.rs/anchor-lang/latest/anchor_lang) and [`anchor_spl`](https://docs.rs/anchor_spl/latest/anchor_spl) crates,
92/// the following `Program` types are provided out of the box:
93///
94/// - [`System`](https://docs.rs/anchor-lang/latest/anchor_lang/struct.System.html)
95/// - [`AssociatedToken`](https://docs.rs/anchor-spl/latest/anchor_spl/associated_token/struct.AssociatedToken.html)
96/// - [`Token`](https://docs.rs/anchor-spl/latest/anchor_spl/token/struct.Token.html)
97///
98#[derive(Clone)]
99pub struct Program<'info, T = ()> {
100    info: &'info AccountInfo<'info>,
101    _phantom: PhantomData<T>,
102}
103
104impl<T: fmt::Debug> fmt::Debug for Program<'_, T> {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        f.debug_struct("Program").field("info", &self.info).finish()
107    }
108}
109
110impl<'a, T> Program<'a, T> {
111    pub(crate) fn new(info: &'a AccountInfo<'a>) -> Program<'a, T> {
112        Self {
113            info,
114            _phantom: PhantomData,
115        }
116    }
117
118    pub fn programdata_address(&self) -> Result<Option<Pubkey>> {
119        if *self.info.owner == bpf_loader_upgradeable::ID {
120            let mut data: &[u8] = &self.info.try_borrow_data()?;
121            let upgradable_loader_state =
122                UpgradeableLoaderState::try_deserialize_unchecked(&mut data)?;
123
124            match upgradable_loader_state {
125                UpgradeableLoaderState::Uninitialized
126                | UpgradeableLoaderState::Buffer {
127                    authority_address: _,
128                }
129                | UpgradeableLoaderState::ProgramData {
130                    slot: _,
131                    upgrade_authority_address: _,
132                } => {
133                    // Unreachable because check in try_from
134                    // ensures that program is executable
135                    // and therefore a program account.
136                    unreachable!()
137                }
138                UpgradeableLoaderState::Program {
139                    programdata_address,
140                } => Ok(Some(programdata_address)),
141            }
142        } else {
143            Ok(None)
144        }
145    }
146}
147
148impl<'a, T: Id> TryFrom<&'a AccountInfo<'a>> for Program<'a, T> {
149    type Error = Error;
150    /// Deserializes the given `info` into a `Program`, checking the pubkey matches
151    /// [`T::id`];
152    fn try_from(info: &'a AccountInfo<'a>) -> Result<Self> {
153        if info.key != &T::id() {
154            return Err(Error::from(ErrorCode::InvalidProgramId).with_pubkeys((*info.key, T::id())));
155        }
156        if !info.executable {
157            return Err(ErrorCode::InvalidProgramExecutable.into());
158        }
159        Ok(Program::new(info))
160    }
161}
162
163/// `Progam<'info>` with no type parameter (or `Program<'info, ()>`) is a special shorthand for
164/// 'any program account'.
165impl<'a> TryFrom<&'a AccountInfo<'a>> for Program<'a, ()> {
166    type Error = Error;
167    /// Deserializes the given `info` into a `Program` without checking the pubkey.
168    fn try_from(info: &'a AccountInfo<'a>) -> Result<Self> {
169        if !info.executable {
170            return Err(ErrorCode::InvalidProgramExecutable.into());
171        }
172        Ok(Program::new(info))
173    }
174}
175
176impl<'info, B, T: Id> Accounts<'info, B> for Program<'info, T> {
177    #[inline(never)]
178    fn try_accounts(
179        _program_id: &Pubkey,
180        accounts: &mut &'info [AccountInfo<'info>],
181        _ix_data: &[u8],
182        _bumps: &mut B,
183        _reallocs: &mut BTreeSet<Pubkey>,
184    ) -> Result<Self> {
185        if accounts.is_empty() {
186            return Err(ErrorCode::AccountNotEnoughKeys.into());
187        }
188        let account = &accounts[0];
189        *accounts = &accounts[1..];
190        Program::try_from(account)
191    }
192}
193
194impl<'info, B> Accounts<'info, B> for Program<'info, ()> {
195    #[inline(never)]
196    fn try_accounts(
197        _program_id: &Pubkey,
198        accounts: &mut &'info [AccountInfo<'info>],
199        _ix_data: &[u8],
200        _bumps: &mut B,
201        _reallocs: &mut BTreeSet<Pubkey>,
202    ) -> Result<Self> {
203        if accounts.is_empty() {
204            return Err(ErrorCode::AccountNotEnoughKeys.into());
205        }
206        let account = &accounts[0];
207        *accounts = &accounts[1..];
208        Program::try_from(account)
209    }
210}
211
212impl<T> ToAccountMetas for Program<'_, T> {
213    fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
214        let is_signer = is_signer.unwrap_or(self.info.is_signer);
215        let meta = match self.info.is_writable {
216            false => AccountMeta::new_readonly(*self.info.key, is_signer),
217            true => AccountMeta::new(*self.info.key, is_signer),
218        };
219        vec![meta]
220    }
221}
222
223impl<'info, T> ToAccountInfos<'info> for Program<'info, T> {
224    fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
225        vec![self.info.clone()]
226    }
227}
228
229impl<'info, T> AsRef<AccountInfo<'info>> for Program<'info, T> {
230    fn as_ref(&self) -> &AccountInfo<'info> {
231        self.info
232    }
233}
234
235impl<'info, T> Deref for Program<'info, T> {
236    type Target = AccountInfo<'info>;
237
238    fn deref(&self) -> &Self::Target {
239        self.info
240    }
241}
242
243impl<'info, T: AccountDeserialize> AccountsExit<'info> for Program<'info, T> {}
244
245impl<T: AccountDeserialize> Key for Program<'_, T> {
246    fn key(&self) -> Pubkey {
247        *self.info.key
248    }
249}
250
251#[cfg(test)]
252mod tests {
253    use super::*;
254    use crate::system_program::System;
255
256    fn account_info<'a>(
257        key: &'a Pubkey,
258        owner: &'a Pubkey,
259        lamports: &'a mut u64,
260        data: &'a mut [u8],
261        executable: bool,
262    ) -> AccountInfo<'a> {
263        AccountInfo::new(key, false, false, lamports, data, owner, executable)
264    }
265
266    #[test]
267    fn program_system_rejects_non_system_account() {
268        let wrong_key = Pubkey::new_from_array([7u8; 32]);
269        let owner = Pubkey::default();
270        let mut lamports = 0u64;
271        let mut data = [];
272        let info = account_info(&wrong_key, &owner, &mut lamports, &mut data, true);
273
274        let err = Program::<System>::try_from(&info)
275            .err()
276            .expect("non-system key must be rejected");
277        match err {
278            Error::AnchorError(e) => assert_eq!(
279                e.error_code_number,
280                ErrorCode::InvalidProgramId as u32,
281            ),
282            other => panic!("unexpected error variant: {other:?}"),
283        }
284    }
285
286    #[test]
287    fn program_system_accepts_system_account() {
288        let key = System::id();
289        let owner = Pubkey::default();
290        let mut lamports = 0u64;
291        let mut data = [];
292        let info = account_info(&key, &owner, &mut lamports, &mut data, true);
293
294        Program::<System>::try_from(&info).expect("system program account must be accepted");
295    }
296
297    #[test]
298    fn program_system_rejects_non_executable_system_account() {
299        let key = System::id();
300        let owner = Pubkey::default();
301        let mut lamports = 0u64;
302        let mut data = [];
303        let info = account_info(&key, &owner, &mut lamports, &mut data, false);
304
305        let err = Program::<System>::try_from(&info)
306            .err()
307            .expect("non-executable account must be rejected");
308        match err {
309            Error::AnchorError(e) => assert_eq!(
310                e.error_code_number,
311                ErrorCode::InvalidProgramExecutable as u32,
312            ),
313            other => panic!("unexpected error variant: {other:?}"),
314        }
315    }
316
317    // The `Program<'info, ()>` shorthand should still accept any executable account.
318    #[test]
319    fn program_unit_accepts_arbitrary_executable() {
320        let key = Pubkey::new_from_array([7u8; 32]);
321        let owner = Pubkey::default();
322        let mut lamports = 0u64;
323        let mut data = [];
324        let info = account_info(&key, &owner, &mut lamports, &mut data, true);
325
326        Program::<()>::try_from(&info).expect("any executable account must be accepted");
327    }
328}