spl_tlv_account_resolution/
account.rs

1//! Struct for managing extra required account configurations
2//!
3//! For example, defining accounts required for your interface program, which
4//! can be `AccountMeta`s - which have fixed addresses - or PDAs - which have
5//! addresses derived from a collection of seeds
6
7use {
8    crate::{error::AccountResolutionError, pubkey_data::PubkeyData, seeds::Seed},
9    bytemuck::{Pod, Zeroable},
10    solana_account_info::AccountInfo,
11    solana_instruction::AccountMeta,
12    solana_program_error::ProgramError,
13    solana_pubkey::{Pubkey, PUBKEY_BYTES},
14    spl_pod::primitives::PodBool,
15};
16
17/// Resolve a program-derived address (PDA) from the instruction data
18/// and the accounts that have already been resolved
19fn resolve_pda<'a, F>(
20    seeds: &[Seed],
21    instruction_data: &[u8],
22    program_id: &Pubkey,
23    get_account_key_data_fn: F,
24) -> Result<Pubkey, ProgramError>
25where
26    F: Fn(usize) -> Option<(&'a Pubkey, Option<&'a [u8]>)>,
27{
28    let mut pda_seeds: Vec<&[u8]> = vec![];
29    for config in seeds {
30        match config {
31            Seed::Uninitialized => (),
32            Seed::Literal { bytes } => pda_seeds.push(bytes),
33            Seed::InstructionData { index, length } => {
34                let arg_start = *index as usize;
35                let arg_end = arg_start + *length as usize;
36                if arg_end > instruction_data.len() {
37                    return Err(AccountResolutionError::InstructionDataTooSmall.into());
38                }
39                pda_seeds.push(&instruction_data[arg_start..arg_end]);
40            }
41            Seed::AccountKey { index } => {
42                let account_index = *index as usize;
43                let address = get_account_key_data_fn(account_index)
44                    .ok_or::<ProgramError>(AccountResolutionError::AccountNotFound.into())?
45                    .0;
46                pda_seeds.push(address.as_ref());
47            }
48            Seed::AccountData {
49                account_index,
50                data_index,
51                length,
52            } => {
53                let account_index = *account_index as usize;
54                let account_data = get_account_key_data_fn(account_index)
55                    .ok_or::<ProgramError>(AccountResolutionError::AccountNotFound.into())?
56                    .1
57                    .ok_or::<ProgramError>(AccountResolutionError::AccountDataNotFound.into())?;
58                let arg_start = *data_index as usize;
59                let arg_end = arg_start + *length as usize;
60                if account_data.len() < arg_end {
61                    return Err(AccountResolutionError::AccountDataTooSmall.into());
62                }
63                pda_seeds.push(&account_data[arg_start..arg_end]);
64            }
65        }
66    }
67    Ok(Pubkey::find_program_address(&pda_seeds, program_id).0)
68}
69
70/// Resolve a pubkey from a pubkey data configuration.
71fn resolve_key_data<'a, F>(
72    key_data: &PubkeyData,
73    instruction_data: &[u8],
74    get_account_key_data_fn: F,
75) -> Result<Pubkey, ProgramError>
76where
77    F: Fn(usize) -> Option<(&'a Pubkey, Option<&'a [u8]>)>,
78{
79    match key_data {
80        PubkeyData::Uninitialized => Err(ProgramError::InvalidAccountData),
81        PubkeyData::InstructionData { index } => {
82            let key_start = *index as usize;
83            let key_end = key_start + PUBKEY_BYTES;
84            if key_end > instruction_data.len() {
85                return Err(AccountResolutionError::InstructionDataTooSmall.into());
86            }
87            Ok(Pubkey::new_from_array(
88                instruction_data[key_start..key_end].try_into().unwrap(),
89            ))
90        }
91        PubkeyData::AccountData {
92            account_index,
93            data_index,
94        } => {
95            let account_index = *account_index as usize;
96            let account_data = get_account_key_data_fn(account_index)
97                .ok_or::<ProgramError>(AccountResolutionError::AccountNotFound.into())?
98                .1
99                .ok_or::<ProgramError>(AccountResolutionError::AccountDataNotFound.into())?;
100            let arg_start = *data_index as usize;
101            let arg_end = arg_start + PUBKEY_BYTES;
102            if account_data.len() < arg_end {
103                return Err(AccountResolutionError::AccountDataTooSmall.into());
104            }
105            Ok(Pubkey::new_from_array(
106                account_data[arg_start..arg_end].try_into().unwrap(),
107            ))
108        }
109    }
110}
111
112/// `Pod` type for defining a required account in a validation account.
113///
114/// This can be any of the following:
115///
116/// * A standard `AccountMeta`
117/// * A PDA (with seed configurations)
118/// * A pubkey stored in some data (account or instruction data)
119///
120/// Can be used in TLV-encoded data.
121#[repr(C)]
122#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
123pub struct ExtraAccountMeta {
124    /// Discriminator to tell whether this represents a standard
125    /// `AccountMeta`, PDA, or pubkey data.
126    pub discriminator: u8,
127    /// This `address_config` field can either be the pubkey of the account,
128    /// the seeds used to derive the pubkey from provided inputs (PDA), or the
129    /// data used to derive the pubkey (account or instruction data).
130    pub address_config: [u8; 32],
131    /// Whether the account should sign
132    pub is_signer: PodBool,
133    /// Whether the account should be writable
134    pub is_writable: PodBool,
135}
136/// Helper used to know when the top bit is set, to interpret the
137/// discriminator as an index rather than as a type
138const U8_TOP_BIT: u8 = 1 << 7;
139impl ExtraAccountMeta {
140    /// Create a `ExtraAccountMeta` from a public key,
141    /// thus representing a standard `AccountMeta`
142    pub fn new_with_pubkey(
143        pubkey: &Pubkey,
144        is_signer: bool,
145        is_writable: bool,
146    ) -> Result<Self, ProgramError> {
147        Ok(Self {
148            discriminator: 0,
149            address_config: pubkey.to_bytes(),
150            is_signer: is_signer.into(),
151            is_writable: is_writable.into(),
152        })
153    }
154
155    /// Create a `ExtraAccountMeta` from a list of seed configurations,
156    /// thus representing a PDA
157    pub fn new_with_seeds(
158        seeds: &[Seed],
159        is_signer: bool,
160        is_writable: bool,
161    ) -> Result<Self, ProgramError> {
162        Ok(Self {
163            discriminator: 1,
164            address_config: Seed::pack_into_address_config(seeds)?,
165            is_signer: is_signer.into(),
166            is_writable: is_writable.into(),
167        })
168    }
169
170    /// Create a `ExtraAccountMeta` from a pubkey data configuration.
171    pub fn new_with_pubkey_data(
172        key_data: &PubkeyData,
173        is_signer: bool,
174        is_writable: bool,
175    ) -> Result<Self, ProgramError> {
176        Ok(Self {
177            discriminator: 2,
178            address_config: PubkeyData::pack_into_address_config(key_data)?,
179            is_signer: is_signer.into(),
180            is_writable: is_writable.into(),
181        })
182    }
183
184    /// Create a `ExtraAccountMeta` from a list of seed configurations,
185    /// representing a PDA for an external program
186    ///
187    /// This PDA belongs to a program elsewhere in the account list, rather
188    /// than the executing program. For a PDA on the executing program, use
189    /// `ExtraAccountMeta::new_with_seeds`.
190    pub fn new_external_pda_with_seeds(
191        program_index: u8,
192        seeds: &[Seed],
193        is_signer: bool,
194        is_writable: bool,
195    ) -> Result<Self, ProgramError> {
196        Ok(Self {
197            discriminator: program_index
198                .checked_add(U8_TOP_BIT)
199                .ok_or(AccountResolutionError::InvalidSeedConfig)?,
200            address_config: Seed::pack_into_address_config(seeds)?,
201            is_signer: is_signer.into(),
202            is_writable: is_writable.into(),
203        })
204    }
205
206    /// Resolve an `ExtraAccountMeta` into an `AccountMeta`, potentially
207    /// resolving a program-derived address (PDA) if necessary
208    pub fn resolve<'a, F>(
209        &self,
210        instruction_data: &[u8],
211        program_id: &Pubkey,
212        get_account_key_data_fn: F,
213    ) -> Result<AccountMeta, ProgramError>
214    where
215        F: Fn(usize) -> Option<(&'a Pubkey, Option<&'a [u8]>)>,
216    {
217        match self.discriminator {
218            0 => AccountMeta::try_from(self),
219            x if x == 1 || x >= U8_TOP_BIT => {
220                let program_id = if x == 1 {
221                    program_id
222                } else {
223                    get_account_key_data_fn(x.saturating_sub(U8_TOP_BIT) as usize)
224                        .ok_or::<ProgramError>(AccountResolutionError::AccountNotFound.into())?
225                        .0
226                };
227                let seeds = Seed::unpack_address_config(&self.address_config)?;
228                Ok(AccountMeta {
229                    pubkey: resolve_pda(
230                        &seeds,
231                        instruction_data,
232                        program_id,
233                        get_account_key_data_fn,
234                    )?,
235                    is_signer: self.is_signer.into(),
236                    is_writable: self.is_writable.into(),
237                })
238            }
239            2 => {
240                let key_data = PubkeyData::unpack(&self.address_config)?;
241                Ok(AccountMeta {
242                    pubkey: resolve_key_data(&key_data, instruction_data, get_account_key_data_fn)?,
243                    is_signer: self.is_signer.into(),
244                    is_writable: self.is_writable.into(),
245                })
246            }
247            _ => Err(ProgramError::InvalidAccountData),
248        }
249    }
250}
251
252impl From<&AccountMeta> for ExtraAccountMeta {
253    fn from(meta: &AccountMeta) -> Self {
254        Self {
255            discriminator: 0,
256            address_config: meta.pubkey.to_bytes(),
257            is_signer: meta.is_signer.into(),
258            is_writable: meta.is_writable.into(),
259        }
260    }
261}
262impl From<AccountMeta> for ExtraAccountMeta {
263    fn from(meta: AccountMeta) -> Self {
264        ExtraAccountMeta::from(&meta)
265    }
266}
267impl From<&AccountInfo<'_>> for ExtraAccountMeta {
268    fn from(account_info: &AccountInfo) -> Self {
269        Self {
270            discriminator: 0,
271            address_config: account_info.key.to_bytes(),
272            is_signer: account_info.is_signer.into(),
273            is_writable: account_info.is_writable.into(),
274        }
275    }
276}
277impl From<AccountInfo<'_>> for ExtraAccountMeta {
278    fn from(account_info: AccountInfo) -> Self {
279        ExtraAccountMeta::from(&account_info)
280    }
281}
282
283impl TryFrom<&ExtraAccountMeta> for AccountMeta {
284    type Error = ProgramError;
285
286    fn try_from(pod: &ExtraAccountMeta) -> Result<Self, Self::Error> {
287        if pod.discriminator == 0 {
288            Ok(AccountMeta {
289                pubkey: Pubkey::from(pod.address_config),
290                is_signer: pod.is_signer.into(),
291                is_writable: pod.is_writable.into(),
292            })
293        } else {
294            Err(AccountResolutionError::AccountTypeNotAccountMeta.into())
295        }
296    }
297}