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`.
151 fn try_from(info: &'a AccountInfo<'a>) -> Result<Self> {
152 // Special handling for unit type () - only check executable, not program ID
153 let is_unit_type = T::id() == Pubkey::default();
154
155 if !is_unit_type && info.key != &T::id() {
156 return Err(Error::from(ErrorCode::InvalidProgramId).with_pubkeys((*info.key, T::id())));
157 }
158 if !info.executable {
159 return Err(ErrorCode::InvalidProgramExecutable.into());
160 }
161 Ok(Program::new(info))
162 }
163}
164
165impl<'info, B, T: Id> Accounts<'info, B> for Program<'info, T> {
166 #[inline(never)]
167 fn try_accounts(
168 _program_id: &Pubkey,
169 accounts: &mut &'info [AccountInfo<'info>],
170 _ix_data: &[u8],
171 _bumps: &mut B,
172 _reallocs: &mut BTreeSet<Pubkey>,
173 ) -> Result<Self> {
174 if accounts.is_empty() {
175 return Err(ErrorCode::AccountNotEnoughKeys.into());
176 }
177 let account = &accounts[0];
178 *accounts = &accounts[1..];
179 Program::try_from(account)
180 }
181}
182
183impl<T> ToAccountMetas for Program<'_, T> {
184 fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
185 let is_signer = is_signer.unwrap_or(self.info.is_signer);
186 let meta = match self.info.is_writable {
187 false => AccountMeta::new_readonly(*self.info.key, is_signer),
188 true => AccountMeta::new(*self.info.key, is_signer),
189 };
190 vec![meta]
191 }
192}
193
194impl<'info, T> ToAccountInfos<'info> for Program<'info, T> {
195 fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
196 vec![self.info.clone()]
197 }
198}
199
200impl<'info, T> AsRef<AccountInfo<'info>> for Program<'info, T> {
201 fn as_ref(&self) -> &AccountInfo<'info> {
202 self.info
203 }
204}
205
206impl<'info, T> Deref for Program<'info, T> {
207 type Target = AccountInfo<'info>;
208
209 fn deref(&self) -> &Self::Target {
210 self.info
211 }
212}
213
214impl<'info, T: AccountDeserialize> AccountsExit<'info> for Program<'info, T> {}
215
216impl<T: AccountDeserialize> Key for Program<'_, T> {
217 fn key(&self) -> Pubkey {
218 *self.info.key
219 }
220}
221
222// Implement Id trait for unit type to support Program<'info> without type parameter
223impl crate::Id for () {
224 fn id() -> Pubkey {
225 // For generic programs, this should never be called since they don't validate specific program IDs.
226 // However, we need to implement it to satisfy the trait bounds.
227 // Using a special marker value that indicates "any program"
228 Pubkey::default()
229 }
230}