spl_tlv_account_resolution/
account.rs1use {
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
17fn 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
70fn 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#[repr(C)]
122#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
123pub struct ExtraAccountMeta {
124 pub discriminator: u8,
127 pub address_config: [u8; 32],
131 pub is_signer: PodBool,
133 pub is_writable: PodBool,
135}
136const U8_TOP_BIT: u8 = 1 << 7;
139impl ExtraAccountMeta {
140 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 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 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 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 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}