spl_tlv_account_resolution/
state.rs

1//! State transition types
2
3use {
4    crate::{account::ExtraAccountMeta, error::AccountResolutionError},
5    solana_account_info::AccountInfo,
6    solana_instruction::{AccountMeta, Instruction},
7    solana_program_error::ProgramError,
8    solana_pubkey::Pubkey,
9    spl_discriminator::SplDiscriminate,
10    spl_pod::{
11        list::{self, ListView},
12        primitives::PodU32,
13    },
14    spl_type_length_value::state::{TlvState, TlvStateBorrowed, TlvStateMut},
15    std::future::Future,
16};
17
18/// Type representing the output of an account fetching function, for easy
19/// chaining between APIs
20pub type AccountDataResult = Result<Option<Vec<u8>>, AccountFetchError>;
21/// Generic error type that can come out of any client while fetching account
22/// data
23pub type AccountFetchError = Box<dyn std::error::Error + Send + Sync>;
24
25/// Helper to convert an `AccountInfo` to an `AccountMeta`
26fn account_info_to_meta(account_info: &AccountInfo) -> AccountMeta {
27    AccountMeta {
28        pubkey: *account_info.key,
29        is_signer: account_info.is_signer,
30        is_writable: account_info.is_writable,
31    }
32}
33
34/// De-escalate an account meta if necessary
35fn de_escalate_account_meta(account_meta: &mut AccountMeta, account_metas: &[AccountMeta]) {
36    // This is a little tricky to read, but the idea is to see if
37    // this account is marked as writable or signer anywhere in
38    // the instruction at the start. If so, DON'T escalate it to
39    // be a writer or signer in the CPI
40    let maybe_highest_privileges = account_metas
41        .iter()
42        .filter(|&x| x.pubkey == account_meta.pubkey)
43        .map(|x| (x.is_signer, x.is_writable))
44        .reduce(|acc, x| (acc.0 || x.0, acc.1 || x.1));
45    // If `Some`, then the account was found somewhere in the instruction
46    if let Some((is_signer, is_writable)) = maybe_highest_privileges {
47        if !is_signer && is_signer != account_meta.is_signer {
48            // Existing account is *NOT* a signer already, but the CPI
49            // wants it to be, so de-escalate to not be a signer
50            account_meta.is_signer = false;
51        }
52        if !is_writable && is_writable != account_meta.is_writable {
53            // Existing account is *NOT* writable already, but the CPI
54            // wants it to be, so de-escalate to not be writable
55            account_meta.is_writable = false;
56        }
57    }
58}
59
60/// Stateless helper for storing additional accounts required for an
61/// instruction.
62///
63/// This struct works with any `SplDiscriminate`, and stores the extra accounts
64/// needed for that specific instruction, using the given `ArrayDiscriminator`
65/// as the type-length-value `ArrayDiscriminator`, and then storing all of the
66/// given `AccountMeta`s as a zero-copy slice.
67///
68/// Sample usage:
69///
70/// ```rust
71/// use {
72///     futures_util::TryFutureExt,
73///     solana_account_info::AccountInfo,
74///     solana_instruction::{AccountMeta, Instruction},
75///     solana_pubkey::Pubkey,
76///     spl_discriminator::{ArrayDiscriminator, SplDiscriminate},
77///     spl_tlv_account_resolution::{
78///         account::ExtraAccountMeta,
79///         seeds::Seed,
80///         state::{AccountDataResult, AccountFetchError, ExtraAccountMetaList}
81///     },
82/// };
83///
84/// struct MyInstruction;
85/// impl SplDiscriminate for MyInstruction {
86///     // Give it a unique discriminator, can also be generated using a hash function
87///     const SPL_DISCRIMINATOR: ArrayDiscriminator = ArrayDiscriminator::new([1; ArrayDiscriminator::LENGTH]);
88/// }
89///
90/// // actually put it in the additional required account keys and signer / writable
91/// let extra_metas = [
92///     AccountMeta::new(Pubkey::new_unique(), false).into(),
93///     AccountMeta::new_readonly(Pubkey::new_unique(), false).into(),
94///     ExtraAccountMeta::new_with_seeds(
95///         &[
96///             Seed::Literal {
97///                 bytes: b"some_string".to_vec(),
98///             },
99///             Seed::InstructionData {
100///                 index: 1,
101///                 length: 1, // u8
102///             },
103///             Seed::AccountKey { index: 1 },
104///         ],
105///         false,
106///         true,
107///     ).unwrap(),
108///     ExtraAccountMeta::new_external_pda_with_seeds(
109///         0,
110///         &[Seed::AccountKey { index: 2 }],
111///         false,
112///         false,
113///     ).unwrap(),
114/// ];
115///
116/// // assume that this buffer is actually account data, already allocated to `account_size`
117/// let account_size = ExtraAccountMetaList::size_of(extra_metas.len()).unwrap();
118/// let mut buffer = vec![0; account_size];
119///
120/// // Initialize the structure for your instruction
121/// ExtraAccountMetaList::init::<MyInstruction>(&mut buffer, &extra_metas).unwrap();
122///
123/// // Off-chain, you can add the additional accounts directly from the account data
124/// // You need to provide the resolver a way to fetch account data off-chain
125/// struct MyClient;
126/// impl MyClient {
127///     pub async fn get_account_data(&self, address: Pubkey) -> AccountDataResult {
128///         Ok(None)
129///     }
130/// }
131///
132/// let client = MyClient;
133/// let program_id = Pubkey::new_unique();
134/// let mut instruction = Instruction::new_with_bytes(program_id, &[0, 1, 2], vec![]);
135/// # futures::executor::block_on(async {
136///     // Now use the resolver to add the additional accounts off-chain
137///     ExtraAccountMetaList::add_to_instruction::<MyInstruction, _, _>(
138///         &mut instruction,
139///         |address: Pubkey| client.get_account_data(address),
140///         &buffer,
141///     )
142///     .await;
143/// # });
144///
145/// // On-chain, you can add the additional accounts *and* account infos
146/// let mut cpi_instruction = Instruction::new_with_bytes(program_id, &[0, 1, 2], vec![]);
147/// let mut cpi_account_infos = vec![]; // assume the other required account infos are already included
148/// let remaining_account_infos: &[AccountInfo<'_>] = &[]; // these are the account infos provided to the instruction that are *not* part of any other known interface
149/// ExtraAccountMetaList::add_to_cpi_instruction::<MyInstruction>(
150///     &mut cpi_instruction,
151///     &mut cpi_account_infos,
152///     &buffer,
153///     &remaining_account_infos,
154/// );
155/// ```
156pub struct ExtraAccountMetaList;
157impl ExtraAccountMetaList {
158    /// Initialize pod slice data for the given instruction and its required
159    /// list of `ExtraAccountMeta`s
160    pub fn init<T: SplDiscriminate>(
161        data: &mut [u8],
162        extra_account_metas: &[ExtraAccountMeta],
163    ) -> Result<(), ProgramError> {
164        let mut state = TlvStateMut::unpack(data).unwrap();
165        let tlv_size = ListView::<ExtraAccountMeta>::size_of(extra_account_metas.len())?;
166        let (bytes, _) = state.alloc::<T>(tlv_size, false)?;
167        let mut validation_data = ListView::<ExtraAccountMeta>::init(bytes)?;
168        for meta in extra_account_metas {
169            validation_data.push(*meta)?;
170        }
171        Ok(())
172    }
173
174    /// Update pod slice data for the given instruction and its required
175    /// list of `ExtraAccountMeta`s
176    pub fn update<T: SplDiscriminate>(
177        data: &mut [u8],
178        extra_account_metas: &[ExtraAccountMeta],
179    ) -> Result<(), ProgramError> {
180        let mut state = TlvStateMut::unpack(data).unwrap();
181        let tlv_size = ListView::<ExtraAccountMeta>::size_of(extra_account_metas.len())?;
182        let bytes = state.realloc_first::<T>(tlv_size)?;
183        let mut validation_data = ListView::<ExtraAccountMeta>::init(bytes)?;
184        for meta in extra_account_metas {
185            validation_data.push(*meta)?;
186        }
187        Ok(())
188    }
189
190    /// Get the underlying `ListViewReadOnly<ExtraAccountMeta>` from an unpacked TLV
191    ///
192    /// Due to lifetime annoyances, this function can't just take in the bytes,
193    /// since then we would be returning a reference to a locally created
194    /// `TlvStateBorrowed`. I hope there's a better way to do this!
195    pub fn unpack_with_tlv_state<'a, T: SplDiscriminate>(
196        tlv_state: &'a TlvStateBorrowed,
197    ) -> Result<list::ListViewReadOnly<'a, ExtraAccountMeta, PodU32>, ProgramError> {
198        let bytes = tlv_state.get_first_bytes::<T>()?;
199        ListView::<ExtraAccountMeta>::unpack(bytes)
200    }
201
202    /// Get the byte size required to hold `num_items` items
203    pub fn size_of(num_items: usize) -> Result<usize, ProgramError> {
204        Ok(TlvStateBorrowed::get_base_len()
205            .saturating_add(ListView::<ExtraAccountMeta>::size_of(num_items)?))
206    }
207
208    /// Checks provided account infos against validation data, using
209    /// instruction data and program ID to resolve any dynamic PDAs
210    /// if necessary.
211    ///
212    /// Note: this function will also verify all extra required accounts
213    /// have been provided in the correct order
214    pub fn check_account_infos<T: SplDiscriminate>(
215        account_infos: &[AccountInfo],
216        instruction_data: &[u8],
217        program_id: &Pubkey,
218        data: &[u8],
219    ) -> Result<(), ProgramError> {
220        let state = TlvStateBorrowed::unpack(data).unwrap();
221        let extra_meta_list = ExtraAccountMetaList::unpack_with_tlv_state::<T>(&state)?;
222
223        let initial_accounts_len = account_infos.len() - extra_meta_list.len();
224
225        // Convert to `AccountMeta` to check resolved metas
226        let provided_metas = account_infos
227            .iter()
228            .map(account_info_to_meta)
229            .collect::<Vec<_>>();
230
231        for (i, config) in extra_meta_list.iter().enumerate() {
232            let meta = {
233                // Create a list of `Ref`s so we can reference account data in the
234                // resolution step
235                let account_key_data_refs = account_infos
236                    .iter()
237                    .map(|info| {
238                        let key = *info.key;
239                        let data = info.try_borrow_data()?;
240                        Ok((key, data))
241                    })
242                    .collect::<Result<Vec<_>, ProgramError>>()?;
243
244                config.resolve(instruction_data, program_id, |usize| {
245                    account_key_data_refs
246                        .get(usize)
247                        .map(|(pubkey, opt_data)| (pubkey, Some(opt_data.as_ref())))
248                })?
249            };
250
251            // Ensure the account is in the correct position
252            let expected_index = i
253                .checked_add(initial_accounts_len)
254                .ok_or::<ProgramError>(AccountResolutionError::CalculationFailure.into())?;
255            if provided_metas.get(expected_index) != Some(&meta) {
256                return Err(AccountResolutionError::IncorrectAccount.into());
257            }
258        }
259
260        Ok(())
261    }
262
263    /// Add the additional account metas to an existing instruction
264    pub async fn add_to_instruction<T: SplDiscriminate, F, Fut>(
265        instruction: &mut Instruction,
266        fetch_account_data_fn: F,
267        data: &[u8],
268    ) -> Result<(), ProgramError>
269    where
270        F: Fn(Pubkey) -> Fut,
271        Fut: Future<Output = AccountDataResult>,
272    {
273        let state = TlvStateBorrowed::unpack(data)?;
274        let bytes = state.get_first_bytes::<T>()?;
275        let extra_account_metas = ListView::<ExtraAccountMeta>::unpack(bytes)?;
276
277        // Fetch account data for each of the instruction accounts
278        let mut account_key_datas = vec![];
279        for meta in instruction.accounts.iter() {
280            let account_data = fetch_account_data_fn(meta.pubkey)
281                .await
282                .map_err::<ProgramError, _>(|_| {
283                    AccountResolutionError::AccountFetchFailed.into()
284                })?;
285            account_key_datas.push((meta.pubkey, account_data));
286        }
287
288        for extra_meta in extra_account_metas.iter() {
289            let mut meta =
290                extra_meta.resolve(&instruction.data, &instruction.program_id, |usize| {
291                    account_key_datas
292                        .get(usize)
293                        .map(|(pubkey, opt_data)| (pubkey, opt_data.as_ref().map(|x| x.as_slice())))
294                })?;
295            de_escalate_account_meta(&mut meta, &instruction.accounts);
296
297            // Fetch account data for the new account
298            account_key_datas.push((
299                meta.pubkey,
300                fetch_account_data_fn(meta.pubkey)
301                    .await
302                    .map_err::<ProgramError, _>(|_| {
303                        AccountResolutionError::AccountFetchFailed.into()
304                    })?,
305            ));
306            instruction.accounts.push(meta);
307        }
308        Ok(())
309    }
310
311    /// Add the additional account metas and account infos for a CPI
312    pub fn add_to_cpi_instruction<'a, T: SplDiscriminate>(
313        cpi_instruction: &mut Instruction,
314        cpi_account_infos: &mut Vec<AccountInfo<'a>>,
315        data: &[u8],
316        account_infos: &[AccountInfo<'a>],
317    ) -> Result<(), ProgramError> {
318        let state = TlvStateBorrowed::unpack(data)?;
319        let bytes = state.get_first_bytes::<T>()?;
320        let extra_account_metas = ListView::<ExtraAccountMeta>::unpack(bytes)?;
321
322        for extra_meta in extra_account_metas.iter() {
323            let mut meta = {
324                // Create a list of `Ref`s so we can reference account data in the
325                // resolution step
326                let account_key_data_refs = cpi_account_infos
327                    .iter()
328                    .map(|info| {
329                        let key = *info.key;
330                        let data = info.try_borrow_data()?;
331                        Ok((key, data))
332                    })
333                    .collect::<Result<Vec<_>, ProgramError>>()?;
334
335                extra_meta.resolve(
336                    &cpi_instruction.data,
337                    &cpi_instruction.program_id,
338                    |usize| {
339                        account_key_data_refs
340                            .get(usize)
341                            .map(|(pubkey, opt_data)| (pubkey, Some(opt_data.as_ref())))
342                    },
343                )?
344            };
345            de_escalate_account_meta(&mut meta, &cpi_instruction.accounts);
346
347            let account_info = account_infos
348                .iter()
349                .find(|&x| *x.key == meta.pubkey)
350                .ok_or(AccountResolutionError::IncorrectAccount)?
351                .clone();
352
353            cpi_instruction.accounts.push(meta);
354            cpi_account_infos.push(account_info);
355        }
356        Ok(())
357    }
358}
359
360#[cfg(test)]
361mod tests {
362    use {
363        super::*,
364        crate::{pubkey_data::PubkeyData, seeds::Seed},
365        solana_instruction::AccountMeta,
366        solana_pubkey::Pubkey,
367        spl_discriminator::{ArrayDiscriminator, SplDiscriminate},
368        std::collections::HashMap,
369    };
370
371    pub struct TestInstruction;
372    impl SplDiscriminate for TestInstruction {
373        const SPL_DISCRIMINATOR: ArrayDiscriminator =
374            ArrayDiscriminator::new([1; ArrayDiscriminator::LENGTH]);
375    }
376
377    pub struct TestOtherInstruction;
378    impl SplDiscriminate for TestOtherInstruction {
379        const SPL_DISCRIMINATOR: ArrayDiscriminator =
380            ArrayDiscriminator::new([2; ArrayDiscriminator::LENGTH]);
381    }
382
383    pub struct MockRpc<'a> {
384        cache: HashMap<Pubkey, &'a AccountInfo<'a>>,
385    }
386    impl<'a> MockRpc<'a> {
387        pub fn setup(account_infos: &'a [AccountInfo<'a>]) -> Self {
388            let mut cache = HashMap::new();
389            for info in account_infos {
390                cache.insert(*info.key, info);
391            }
392            Self { cache }
393        }
394
395        pub async fn get_account_data(&self, pubkey: Pubkey) -> AccountDataResult {
396            Ok(self
397                .cache
398                .get(&pubkey)
399                .map(|account| account.try_borrow_data().unwrap().to_vec()))
400        }
401    }
402
403    #[tokio::test]
404    async fn init_with_metas() {
405        let metas = [
406            AccountMeta::new(Pubkey::new_unique(), false).into(),
407            AccountMeta::new(Pubkey::new_unique(), true).into(),
408            AccountMeta::new_readonly(Pubkey::new_unique(), true).into(),
409            AccountMeta::new_readonly(Pubkey::new_unique(), false).into(),
410        ];
411        let account_size = ExtraAccountMetaList::size_of(metas.len()).unwrap();
412        let mut buffer = vec![0; account_size];
413
414        ExtraAccountMetaList::init::<TestInstruction>(&mut buffer, &metas).unwrap();
415
416        let mock_rpc = MockRpc::setup(&[]);
417
418        let mut instruction = Instruction::new_with_bytes(Pubkey::new_unique(), &[], vec![]);
419        ExtraAccountMetaList::add_to_instruction::<TestInstruction, _, _>(
420            &mut instruction,
421            |pubkey| mock_rpc.get_account_data(pubkey),
422            &buffer,
423        )
424        .await
425        .unwrap();
426
427        let check_metas = metas
428            .iter()
429            .map(|e| AccountMeta::try_from(e).unwrap())
430            .collect::<Vec<_>>();
431
432        assert_eq!(instruction.accounts, check_metas,);
433    }
434
435    #[tokio::test]
436    async fn init_with_infos() {
437        let program_id = Pubkey::new_unique();
438
439        let pubkey1 = Pubkey::new_unique();
440        let mut lamports1 = 0;
441        let mut data1 = [];
442        let pubkey2 = Pubkey::new_unique();
443        let mut lamports2 = 0;
444        let mut data2 = [4, 4, 4, 6, 6, 6, 8, 8];
445        let pubkey3 = Pubkey::new_unique();
446        let mut lamports3 = 0;
447        let mut data3 = [];
448        let owner = Pubkey::new_unique();
449        let account_infos = [
450            AccountInfo::new(
451                &pubkey1,
452                false,
453                true,
454                &mut lamports1,
455                &mut data1,
456                &owner,
457                false,
458            ),
459            AccountInfo::new(
460                &pubkey2,
461                true,
462                false,
463                &mut lamports2,
464                &mut data2,
465                &owner,
466                false,
467            ),
468            AccountInfo::new(
469                &pubkey3,
470                false,
471                false,
472                &mut lamports3,
473                &mut data3,
474                &owner,
475                false,
476            ),
477        ];
478
479        let required_pda = ExtraAccountMeta::new_with_seeds(
480            &[
481                Seed::AccountKey { index: 0 },
482                Seed::AccountData {
483                    account_index: 1,
484                    data_index: 2,
485                    length: 4,
486                },
487            ],
488            false,
489            true,
490        )
491        .unwrap();
492
493        // Convert to `ExtraAccountMeta`
494        let required_extra_accounts = [
495            ExtraAccountMeta::from(&account_infos[0]),
496            ExtraAccountMeta::from(&account_infos[1]),
497            ExtraAccountMeta::from(&account_infos[2]),
498            required_pda,
499        ];
500
501        let account_size = ExtraAccountMetaList::size_of(required_extra_accounts.len()).unwrap();
502        let mut buffer = vec![0; account_size];
503
504        ExtraAccountMetaList::init::<TestInstruction>(&mut buffer, &required_extra_accounts)
505            .unwrap();
506
507        let mock_rpc = MockRpc::setup(&account_infos);
508
509        let mut instruction = Instruction::new_with_bytes(program_id, &[], vec![]);
510        ExtraAccountMetaList::add_to_instruction::<TestInstruction, _, _>(
511            &mut instruction,
512            |pubkey| mock_rpc.get_account_data(pubkey),
513            &buffer,
514        )
515        .await
516        .unwrap();
517
518        let (check_required_pda, _) = Pubkey::find_program_address(
519            &[
520                account_infos[0].key.as_ref(),                      // Account key
521                &account_infos[1].try_borrow_data().unwrap()[2..6], // Account data
522            ],
523            &program_id,
524        );
525
526        // Convert to `AccountMeta` to check instruction
527        let check_metas = [
528            account_info_to_meta(&account_infos[0]),
529            account_info_to_meta(&account_infos[1]),
530            account_info_to_meta(&account_infos[2]),
531            AccountMeta::new(check_required_pda, false),
532        ];
533
534        assert_eq!(instruction.accounts, check_metas,);
535
536        assert_eq!(
537            instruction.accounts.get(3).unwrap().pubkey,
538            check_required_pda
539        );
540    }
541
542    #[tokio::test]
543    async fn init_with_extra_account_metas() {
544        let program_id = Pubkey::new_unique();
545
546        let extra_meta3_literal_str = "seed_prefix";
547
548        let ix_account1 = AccountMeta::new(Pubkey::new_unique(), false);
549        let ix_account2 = AccountMeta::new(Pubkey::new_unique(), true);
550
551        let extra_meta1 = AccountMeta::new(Pubkey::new_unique(), false);
552        let extra_meta2 = AccountMeta::new(Pubkey::new_unique(), true);
553        let extra_meta3 = ExtraAccountMeta::new_with_seeds(
554            &[
555                Seed::Literal {
556                    bytes: extra_meta3_literal_str.as_bytes().to_vec(),
557                },
558                Seed::InstructionData {
559                    index: 1,
560                    length: 1, // u8
561                },
562                Seed::AccountKey { index: 0 },
563                Seed::AccountKey { index: 2 },
564            ],
565            false,
566            true,
567        )
568        .unwrap();
569        let extra_meta4 = ExtraAccountMeta::new_with_pubkey_data(
570            &PubkeyData::InstructionData { index: 4 },
571            false,
572            true,
573        )
574        .unwrap();
575
576        let metas = [
577            ExtraAccountMeta::from(&extra_meta1),
578            ExtraAccountMeta::from(&extra_meta2),
579            extra_meta3,
580            extra_meta4,
581        ];
582
583        let mut ix_data = vec![1, 2, 3, 4];
584        let check_extra_meta4_pubkey = Pubkey::new_unique();
585        ix_data.extend_from_slice(check_extra_meta4_pubkey.as_ref());
586
587        let ix_accounts = vec![ix_account1.clone(), ix_account2.clone()];
588        let mut instruction = Instruction::new_with_bytes(program_id, &ix_data, ix_accounts);
589
590        let account_size = ExtraAccountMetaList::size_of(metas.len()).unwrap();
591        let mut buffer = vec![0; account_size];
592
593        ExtraAccountMetaList::init::<TestInstruction>(&mut buffer, &metas).unwrap();
594
595        let mock_rpc = MockRpc::setup(&[]);
596
597        ExtraAccountMetaList::add_to_instruction::<TestInstruction, _, _>(
598            &mut instruction,
599            |pubkey| mock_rpc.get_account_data(pubkey),
600            &buffer,
601        )
602        .await
603        .unwrap();
604
605        let check_extra_meta3_u8_arg = ix_data[1];
606        let check_extra_meta3_pubkey = Pubkey::find_program_address(
607            &[
608                extra_meta3_literal_str.as_bytes(),
609                &[check_extra_meta3_u8_arg],
610                ix_account1.pubkey.as_ref(),
611                extra_meta1.pubkey.as_ref(),
612            ],
613            &program_id,
614        )
615        .0;
616        let check_metas = [
617            ix_account1,
618            ix_account2,
619            extra_meta1,
620            extra_meta2,
621            AccountMeta::new(check_extra_meta3_pubkey, false),
622            AccountMeta::new(check_extra_meta4_pubkey, false),
623        ];
624
625        assert_eq!(
626            instruction.accounts.get(4).unwrap().pubkey,
627            check_extra_meta3_pubkey,
628        );
629        assert_eq!(
630            instruction.accounts.get(5).unwrap().pubkey,
631            check_extra_meta4_pubkey,
632        );
633        assert_eq!(instruction.accounts, check_metas,);
634    }
635
636    #[tokio::test]
637    async fn init_multiple() {
638        let extra_meta5_literal_str = "seed_prefix";
639        let extra_meta5_literal_u32 = 4u32;
640        let other_meta2_literal_str = "other_seed_prefix";
641
642        let extra_meta1 = AccountMeta::new(Pubkey::new_unique(), false);
643        let extra_meta2 = AccountMeta::new(Pubkey::new_unique(), true);
644        let extra_meta3 = AccountMeta::new_readonly(Pubkey::new_unique(), true);
645        let extra_meta4 = AccountMeta::new_readonly(Pubkey::new_unique(), false);
646        let extra_meta5 = ExtraAccountMeta::new_with_seeds(
647            &[
648                Seed::Literal {
649                    bytes: extra_meta5_literal_str.as_bytes().to_vec(),
650                },
651                Seed::Literal {
652                    bytes: extra_meta5_literal_u32.to_le_bytes().to_vec(),
653                },
654                Seed::InstructionData {
655                    index: 5,
656                    length: 1, // u8
657                },
658                Seed::AccountKey { index: 2 },
659            ],
660            false,
661            true,
662        )
663        .unwrap();
664        let extra_meta6 = ExtraAccountMeta::new_with_pubkey_data(
665            &PubkeyData::InstructionData { index: 8 },
666            false,
667            true,
668        )
669        .unwrap();
670
671        let other_meta1 = AccountMeta::new(Pubkey::new_unique(), false);
672        let other_meta2 = ExtraAccountMeta::new_with_seeds(
673            &[
674                Seed::Literal {
675                    bytes: other_meta2_literal_str.as_bytes().to_vec(),
676                },
677                Seed::InstructionData {
678                    index: 1,
679                    length: 4, // u32
680                },
681                Seed::AccountKey { index: 0 },
682            ],
683            false,
684            true,
685        )
686        .unwrap();
687        let other_meta3 = ExtraAccountMeta::new_with_pubkey_data(
688            &PubkeyData::InstructionData { index: 7 },
689            false,
690            true,
691        )
692        .unwrap();
693
694        let metas = [
695            ExtraAccountMeta::from(&extra_meta1),
696            ExtraAccountMeta::from(&extra_meta2),
697            ExtraAccountMeta::from(&extra_meta3),
698            ExtraAccountMeta::from(&extra_meta4),
699            extra_meta5,
700            extra_meta6,
701        ];
702        let other_metas = [
703            ExtraAccountMeta::from(&other_meta1),
704            other_meta2,
705            other_meta3,
706        ];
707
708        let account_size = ExtraAccountMetaList::size_of(metas.len()).unwrap()
709            + ExtraAccountMetaList::size_of(other_metas.len()).unwrap();
710        let mut buffer = vec![0; account_size];
711
712        ExtraAccountMetaList::init::<TestInstruction>(&mut buffer, &metas).unwrap();
713        ExtraAccountMetaList::init::<TestOtherInstruction>(&mut buffer, &other_metas).unwrap();
714
715        let mock_rpc = MockRpc::setup(&[]);
716
717        let program_id = Pubkey::new_unique();
718
719        let mut ix_data = vec![0, 0, 0, 0, 0, 7, 0, 0];
720        let check_extra_meta6_pubkey = Pubkey::new_unique();
721        ix_data.extend_from_slice(check_extra_meta6_pubkey.as_ref());
722
723        let ix_accounts = vec![];
724
725        let mut instruction = Instruction::new_with_bytes(program_id, &ix_data, ix_accounts);
726        ExtraAccountMetaList::add_to_instruction::<TestInstruction, _, _>(
727            &mut instruction,
728            |pubkey| mock_rpc.get_account_data(pubkey),
729            &buffer,
730        )
731        .await
732        .unwrap();
733
734        let check_extra_meta5_u8_arg = ix_data[5];
735        let check_extra_meta5_pubkey = Pubkey::find_program_address(
736            &[
737                extra_meta5_literal_str.as_bytes(),
738                extra_meta5_literal_u32.to_le_bytes().as_ref(),
739                &[check_extra_meta5_u8_arg],
740                extra_meta3.pubkey.as_ref(),
741            ],
742            &program_id,
743        )
744        .0;
745        let check_metas = [
746            extra_meta1,
747            extra_meta2,
748            extra_meta3,
749            extra_meta4,
750            AccountMeta::new(check_extra_meta5_pubkey, false),
751            AccountMeta::new(check_extra_meta6_pubkey, false),
752        ];
753
754        assert_eq!(
755            instruction.accounts.get(4).unwrap().pubkey,
756            check_extra_meta5_pubkey,
757        );
758        assert_eq!(
759            instruction.accounts.get(5).unwrap().pubkey,
760            check_extra_meta6_pubkey,
761        );
762        assert_eq!(instruction.accounts, check_metas,);
763
764        let program_id = Pubkey::new_unique();
765
766        let ix_account1 = AccountMeta::new(Pubkey::new_unique(), false);
767        let ix_account2 = AccountMeta::new(Pubkey::new_unique(), true);
768        let ix_accounts = vec![ix_account1.clone(), ix_account2.clone()];
769
770        let mut ix_data = vec![0, 26, 0, 0, 0, 0, 0];
771        let check_other_meta3_pubkey = Pubkey::new_unique();
772        ix_data.extend_from_slice(check_other_meta3_pubkey.as_ref());
773
774        let mut instruction = Instruction::new_with_bytes(program_id, &ix_data, ix_accounts);
775        ExtraAccountMetaList::add_to_instruction::<TestOtherInstruction, _, _>(
776            &mut instruction,
777            |pubkey| mock_rpc.get_account_data(pubkey),
778            &buffer,
779        )
780        .await
781        .unwrap();
782
783        let check_other_meta2_u32_arg = u32::from_le_bytes(ix_data[1..5].try_into().unwrap());
784        let check_other_meta2_pubkey = Pubkey::find_program_address(
785            &[
786                other_meta2_literal_str.as_bytes(),
787                check_other_meta2_u32_arg.to_le_bytes().as_ref(),
788                ix_account1.pubkey.as_ref(),
789            ],
790            &program_id,
791        )
792        .0;
793        let check_other_metas = [
794            ix_account1,
795            ix_account2,
796            other_meta1,
797            AccountMeta::new(check_other_meta2_pubkey, false),
798            AccountMeta::new(check_other_meta3_pubkey, false),
799        ];
800
801        assert_eq!(
802            instruction.accounts.get(3).unwrap().pubkey,
803            check_other_meta2_pubkey,
804        );
805        assert_eq!(
806            instruction.accounts.get(4).unwrap().pubkey,
807            check_other_meta3_pubkey,
808        );
809        assert_eq!(instruction.accounts, check_other_metas,);
810    }
811
812    #[tokio::test]
813    async fn init_mixed() {
814        let extra_meta5_literal_str = "seed_prefix";
815        let extra_meta6_literal_u64 = 28u64;
816
817        let pubkey1 = Pubkey::new_unique();
818        let mut lamports1 = 0;
819        let mut data1 = [];
820        let pubkey2 = Pubkey::new_unique();
821        let mut lamports2 = 0;
822        let mut data2 = [];
823        let pubkey3 = Pubkey::new_unique();
824        let mut lamports3 = 0;
825        let mut data3 = [];
826        let owner = Pubkey::new_unique();
827        let account_infos = [
828            AccountInfo::new(
829                &pubkey1,
830                false,
831                true,
832                &mut lamports1,
833                &mut data1,
834                &owner,
835                false,
836            ),
837            AccountInfo::new(
838                &pubkey2,
839                true,
840                false,
841                &mut lamports2,
842                &mut data2,
843                &owner,
844                false,
845            ),
846            AccountInfo::new(
847                &pubkey3,
848                false,
849                false,
850                &mut lamports3,
851                &mut data3,
852                &owner,
853                false,
854            ),
855        ];
856
857        let extra_meta1 = AccountMeta::new(Pubkey::new_unique(), false);
858        let extra_meta2 = AccountMeta::new(Pubkey::new_unique(), true);
859        let extra_meta3 = AccountMeta::new_readonly(Pubkey::new_unique(), true);
860        let extra_meta4 = AccountMeta::new_readonly(Pubkey::new_unique(), false);
861        let extra_meta5 = ExtraAccountMeta::new_with_seeds(
862            &[
863                Seed::Literal {
864                    bytes: extra_meta5_literal_str.as_bytes().to_vec(),
865                },
866                Seed::InstructionData {
867                    index: 1,
868                    length: 8, // [u8; 8]
869                },
870                Seed::InstructionData {
871                    index: 9,
872                    length: 32, // Pubkey
873                },
874                Seed::AccountKey { index: 2 },
875            ],
876            false,
877            true,
878        )
879        .unwrap();
880        let extra_meta6 = ExtraAccountMeta::new_with_seeds(
881            &[
882                Seed::Literal {
883                    bytes: extra_meta6_literal_u64.to_le_bytes().to_vec(),
884                },
885                Seed::AccountKey { index: 1 },
886                Seed::AccountKey { index: 4 },
887            ],
888            false,
889            true,
890        )
891        .unwrap();
892        let extra_meta7 = ExtraAccountMeta::new_with_pubkey_data(
893            &PubkeyData::InstructionData { index: 41 }, // After the other pubkey arg.
894            false,
895            true,
896        )
897        .unwrap();
898
899        let test_ix_required_extra_accounts = account_infos
900            .iter()
901            .map(ExtraAccountMeta::from)
902            .collect::<Vec<_>>();
903        let test_other_ix_required_extra_accounts = [
904            ExtraAccountMeta::from(&extra_meta1),
905            ExtraAccountMeta::from(&extra_meta2),
906            ExtraAccountMeta::from(&extra_meta3),
907            ExtraAccountMeta::from(&extra_meta4),
908            extra_meta5,
909            extra_meta6,
910            extra_meta7,
911        ];
912
913        let account_size = ExtraAccountMetaList::size_of(test_ix_required_extra_accounts.len())
914            .unwrap()
915            + ExtraAccountMetaList::size_of(test_other_ix_required_extra_accounts.len()).unwrap();
916        let mut buffer = vec![0; account_size];
917
918        ExtraAccountMetaList::init::<TestInstruction>(
919            &mut buffer,
920            &test_ix_required_extra_accounts,
921        )
922        .unwrap();
923        ExtraAccountMetaList::init::<TestOtherInstruction>(
924            &mut buffer,
925            &test_other_ix_required_extra_accounts,
926        )
927        .unwrap();
928
929        let mock_rpc = MockRpc::setup(&account_infos);
930
931        let program_id = Pubkey::new_unique();
932        let mut instruction = Instruction::new_with_bytes(program_id, &[], vec![]);
933        ExtraAccountMetaList::add_to_instruction::<TestInstruction, _, _>(
934            &mut instruction,
935            |pubkey| mock_rpc.get_account_data(pubkey),
936            &buffer,
937        )
938        .await
939        .unwrap();
940
941        let test_ix_check_metas = account_infos
942            .iter()
943            .map(account_info_to_meta)
944            .collect::<Vec<_>>();
945        assert_eq!(instruction.accounts, test_ix_check_metas,);
946
947        let program_id = Pubkey::new_unique();
948
949        let instruction_u8array_arg = [1, 2, 3, 4, 5, 6, 7, 8];
950        let instruction_pubkey_arg = Pubkey::new_unique();
951        let instruction_key_data_pubkey_arg = Pubkey::new_unique();
952
953        let mut instruction_data = vec![0];
954        instruction_data.extend_from_slice(&instruction_u8array_arg);
955        instruction_data.extend_from_slice(instruction_pubkey_arg.as_ref());
956        instruction_data.extend_from_slice(instruction_key_data_pubkey_arg.as_ref());
957
958        let mut instruction = Instruction::new_with_bytes(program_id, &instruction_data, vec![]);
959        ExtraAccountMetaList::add_to_instruction::<TestOtherInstruction, _, _>(
960            &mut instruction,
961            |pubkey| mock_rpc.get_account_data(pubkey),
962            &buffer,
963        )
964        .await
965        .unwrap();
966
967        let check_extra_meta5_pubkey = Pubkey::find_program_address(
968            &[
969                extra_meta5_literal_str.as_bytes(),
970                &instruction_u8array_arg,
971                instruction_pubkey_arg.as_ref(),
972                extra_meta3.pubkey.as_ref(),
973            ],
974            &program_id,
975        )
976        .0;
977
978        let check_extra_meta6_pubkey = Pubkey::find_program_address(
979            &[
980                extra_meta6_literal_u64.to_le_bytes().as_ref(),
981                extra_meta2.pubkey.as_ref(),
982                check_extra_meta5_pubkey.as_ref(), // The first PDA should be at index 4
983            ],
984            &program_id,
985        )
986        .0;
987
988        let test_other_ix_check_metas = vec![
989            extra_meta1,
990            extra_meta2,
991            extra_meta3,
992            extra_meta4,
993            AccountMeta::new(check_extra_meta5_pubkey, false),
994            AccountMeta::new(check_extra_meta6_pubkey, false),
995            AccountMeta::new(instruction_key_data_pubkey_arg, false),
996        ];
997
998        assert_eq!(
999            instruction.accounts.get(4).unwrap().pubkey,
1000            check_extra_meta5_pubkey,
1001        );
1002        assert_eq!(
1003            instruction.accounts.get(5).unwrap().pubkey,
1004            check_extra_meta6_pubkey,
1005        );
1006        assert_eq!(
1007            instruction.accounts.get(6).unwrap().pubkey,
1008            instruction_key_data_pubkey_arg,
1009        );
1010        assert_eq!(instruction.accounts, test_other_ix_check_metas,);
1011    }
1012
1013    #[tokio::test]
1014    async fn cpi_instruction() {
1015        // Say we have a program that CPIs to another program.
1016        //
1017        // Say that _other_ program will need extra account infos.
1018
1019        // This will be our program
1020        let program_id = Pubkey::new_unique();
1021        let owner = Pubkey::new_unique();
1022
1023        // Some seeds used by the program for PDAs
1024        let required_pda1_literal_string = "required_pda1";
1025        let required_pda2_literal_u32 = 4u32;
1026        let required_key_data_instruction_data = Pubkey::new_unique();
1027
1028        // Define instruction data
1029        //  - 0: u8
1030        //  - 1-8: [u8; 8]
1031        //  - 9-16: u64
1032        let instruction_u8array_arg = [1, 2, 3, 4, 5, 6, 7, 8];
1033        let instruction_u64_arg = 208u64;
1034        let mut instruction_data = vec![0];
1035        instruction_data.extend_from_slice(&instruction_u8array_arg);
1036        instruction_data.extend_from_slice(instruction_u64_arg.to_le_bytes().as_ref());
1037        instruction_data.extend_from_slice(required_key_data_instruction_data.as_ref());
1038
1039        // Define known instruction accounts
1040        let ix_accounts = vec![
1041            AccountMeta::new(Pubkey::new_unique(), false),
1042            AccountMeta::new(Pubkey::new_unique(), false),
1043        ];
1044
1045        // Define extra account metas required by the program we will CPI to
1046        let extra_meta1 = AccountMeta::new(Pubkey::new_unique(), false);
1047        let extra_meta2 = AccountMeta::new(Pubkey::new_unique(), true);
1048        let extra_meta3 = AccountMeta::new_readonly(Pubkey::new_unique(), false);
1049        let required_accounts = [
1050            ExtraAccountMeta::from(&extra_meta1),
1051            ExtraAccountMeta::from(&extra_meta2),
1052            ExtraAccountMeta::from(&extra_meta3),
1053            ExtraAccountMeta::new_with_seeds(
1054                &[
1055                    Seed::Literal {
1056                        bytes: required_pda1_literal_string.as_bytes().to_vec(),
1057                    },
1058                    Seed::InstructionData {
1059                        index: 1,
1060                        length: 8, // [u8; 8]
1061                    },
1062                    Seed::AccountKey { index: 1 },
1063                ],
1064                false,
1065                true,
1066            )
1067            .unwrap(),
1068            ExtraAccountMeta::new_with_seeds(
1069                &[
1070                    Seed::Literal {
1071                        bytes: required_pda2_literal_u32.to_le_bytes().to_vec(),
1072                    },
1073                    Seed::InstructionData {
1074                        index: 9,
1075                        length: 8, // u64
1076                    },
1077                    Seed::AccountKey { index: 5 },
1078                ],
1079                false,
1080                true,
1081            )
1082            .unwrap(),
1083            ExtraAccountMeta::new_with_seeds(
1084                &[
1085                    Seed::InstructionData {
1086                        index: 0,
1087                        length: 1, // u8
1088                    },
1089                    Seed::AccountData {
1090                        account_index: 2,
1091                        data_index: 0,
1092                        length: 8,
1093                    },
1094                ],
1095                false,
1096                true,
1097            )
1098            .unwrap(),
1099            ExtraAccountMeta::new_with_seeds(
1100                &[
1101                    Seed::AccountData {
1102                        account_index: 5,
1103                        data_index: 4,
1104                        length: 4,
1105                    }, // This one is a PDA!
1106                ],
1107                false,
1108                true,
1109            )
1110            .unwrap(),
1111            ExtraAccountMeta::new_with_pubkey_data(
1112                &PubkeyData::InstructionData { index: 17 },
1113                false,
1114                true,
1115            )
1116            .unwrap(),
1117            ExtraAccountMeta::new_with_pubkey_data(
1118                &PubkeyData::AccountData {
1119                    account_index: 6,
1120                    data_index: 0,
1121                },
1122                false,
1123                true,
1124            )
1125            .unwrap(),
1126            ExtraAccountMeta::new_with_pubkey_data(
1127                &PubkeyData::AccountData {
1128                    account_index: 7,
1129                    data_index: 8,
1130                },
1131                false,
1132                true,
1133            )
1134            .unwrap(),
1135        ];
1136
1137        // Now here we're going to build the list of account infos
1138        // We'll need to include:
1139        //  - The instruction account infos for the program to CPI to
1140        //  - The extra account infos for the program to CPI to
1141        //  - Some other arbitrary account infos our program may use
1142
1143        // First we need to manually derive each PDA
1144        let check_required_pda1_pubkey = Pubkey::find_program_address(
1145            &[
1146                required_pda1_literal_string.as_bytes(),
1147                &instruction_u8array_arg,
1148                ix_accounts.get(1).unwrap().pubkey.as_ref(), // The second account
1149            ],
1150            &program_id,
1151        )
1152        .0;
1153        let check_required_pda2_pubkey = Pubkey::find_program_address(
1154            &[
1155                required_pda2_literal_u32.to_le_bytes().as_ref(),
1156                instruction_u64_arg.to_le_bytes().as_ref(),
1157                check_required_pda1_pubkey.as_ref(), // The first PDA should be at index 5
1158            ],
1159            &program_id,
1160        )
1161        .0;
1162        let check_required_pda3_pubkey = Pubkey::find_program_address(
1163            &[
1164                &[0],    // Instruction "discriminator" (u8)
1165                &[8; 8], // The first 8 bytes of the data for account at index 2 (extra account 1)
1166            ],
1167            &program_id,
1168        )
1169        .0;
1170        let check_required_pda4_pubkey = Pubkey::find_program_address(
1171            &[
1172                &[7; 4], /* 4 bytes starting at index 4 of the data for account at index 5 (extra
1173                         * pda 1) */
1174            ],
1175            &program_id,
1176        )
1177        .0;
1178        let check_key_data1_pubkey = required_key_data_instruction_data;
1179        let check_key_data2_pubkey = Pubkey::new_from_array([8; 32]);
1180        let check_key_data3_pubkey = Pubkey::new_from_array([9; 32]);
1181
1182        // The instruction account infos for the program to CPI to
1183        let pubkey_ix_1 = ix_accounts.first().unwrap().pubkey;
1184        let mut lamports_ix_1 = 0;
1185        let mut data_ix_1 = [];
1186        let pubkey_ix_2 = ix_accounts.get(1).unwrap().pubkey;
1187        let mut lamports_ix_2 = 0;
1188        let mut data_ix_2 = [];
1189
1190        // The extra account infos for the program to CPI to
1191        let mut lamports1 = 0;
1192        let mut data1 = [8; 12];
1193        let mut lamports2 = 0;
1194        let mut data2 = [];
1195        let mut lamports3 = 0;
1196        let mut data3 = [];
1197        let mut lamports_pda1 = 0;
1198        let mut data_pda1 = [7; 12];
1199        let mut lamports_pda2 = 0;
1200        let mut data_pda2 = [8; 32];
1201        let mut lamports_pda3 = 0;
1202        let mut data_pda3 = [0; 40];
1203        data_pda3[8..].copy_from_slice(&[9; 32]); // Add pubkey data for pubkey data pubkey 3.
1204        let mut lamports_pda4 = 0;
1205        let mut data_pda4 = [];
1206        let mut data_key_data1 = [];
1207        let mut lamports_key_data1 = 0;
1208        let mut data_key_data2 = [];
1209        let mut lamports_key_data2 = 0;
1210        let mut data_key_data3 = [];
1211        let mut lamports_key_data3 = 0;
1212
1213        // Some other arbitrary account infos our program may use
1214        let pubkey_arb_1 = Pubkey::new_unique();
1215        let mut lamports_arb_1 = 0;
1216        let mut data_arb_1 = [];
1217        let pubkey_arb_2 = Pubkey::new_unique();
1218        let mut lamports_arb_2 = 0;
1219        let mut data_arb_2 = [];
1220
1221        let all_account_infos = [
1222            AccountInfo::new(
1223                &pubkey_ix_1,
1224                ix_accounts.first().unwrap().is_signer,
1225                ix_accounts.first().unwrap().is_writable,
1226                &mut lamports_ix_1,
1227                &mut data_ix_1,
1228                &owner,
1229                false,
1230            ),
1231            AccountInfo::new(
1232                &pubkey_ix_2,
1233                ix_accounts.get(1).unwrap().is_signer,
1234                ix_accounts.get(1).unwrap().is_writable,
1235                &mut lamports_ix_2,
1236                &mut data_ix_2,
1237                &owner,
1238                false,
1239            ),
1240            AccountInfo::new(
1241                &extra_meta1.pubkey,
1242                required_accounts.first().unwrap().is_signer.into(),
1243                required_accounts.first().unwrap().is_writable.into(),
1244                &mut lamports1,
1245                &mut data1,
1246                &owner,
1247                false,
1248            ),
1249            AccountInfo::new(
1250                &extra_meta2.pubkey,
1251                required_accounts.get(1).unwrap().is_signer.into(),
1252                required_accounts.get(1).unwrap().is_writable.into(),
1253                &mut lamports2,
1254                &mut data2,
1255                &owner,
1256                false,
1257            ),
1258            AccountInfo::new(
1259                &extra_meta3.pubkey,
1260                required_accounts.get(2).unwrap().is_signer.into(),
1261                required_accounts.get(2).unwrap().is_writable.into(),
1262                &mut lamports3,
1263                &mut data3,
1264                &owner,
1265                false,
1266            ),
1267            AccountInfo::new(
1268                &check_required_pda1_pubkey,
1269                required_accounts.get(3).unwrap().is_signer.into(),
1270                required_accounts.get(3).unwrap().is_writable.into(),
1271                &mut lamports_pda1,
1272                &mut data_pda1,
1273                &owner,
1274                false,
1275            ),
1276            AccountInfo::new(
1277                &check_required_pda2_pubkey,
1278                required_accounts.get(4).unwrap().is_signer.into(),
1279                required_accounts.get(4).unwrap().is_writable.into(),
1280                &mut lamports_pda2,
1281                &mut data_pda2,
1282                &owner,
1283                false,
1284            ),
1285            AccountInfo::new(
1286                &check_required_pda3_pubkey,
1287                required_accounts.get(5).unwrap().is_signer.into(),
1288                required_accounts.get(5).unwrap().is_writable.into(),
1289                &mut lamports_pda3,
1290                &mut data_pda3,
1291                &owner,
1292                false,
1293            ),
1294            AccountInfo::new(
1295                &check_required_pda4_pubkey,
1296                required_accounts.get(6).unwrap().is_signer.into(),
1297                required_accounts.get(6).unwrap().is_writable.into(),
1298                &mut lamports_pda4,
1299                &mut data_pda4,
1300                &owner,
1301                false,
1302            ),
1303            AccountInfo::new(
1304                &check_key_data1_pubkey,
1305                required_accounts.get(7).unwrap().is_signer.into(),
1306                required_accounts.get(7).unwrap().is_writable.into(),
1307                &mut lamports_key_data1,
1308                &mut data_key_data1,
1309                &owner,
1310                false,
1311            ),
1312            AccountInfo::new(
1313                &check_key_data2_pubkey,
1314                required_accounts.get(8).unwrap().is_signer.into(),
1315                required_accounts.get(8).unwrap().is_writable.into(),
1316                &mut lamports_key_data2,
1317                &mut data_key_data2,
1318                &owner,
1319                false,
1320            ),
1321            AccountInfo::new(
1322                &check_key_data3_pubkey,
1323                required_accounts.get(9).unwrap().is_signer.into(),
1324                required_accounts.get(9).unwrap().is_writable.into(),
1325                &mut lamports_key_data3,
1326                &mut data_key_data3,
1327                &owner,
1328                false,
1329            ),
1330            AccountInfo::new(
1331                &pubkey_arb_1,
1332                false,
1333                true,
1334                &mut lamports_arb_1,
1335                &mut data_arb_1,
1336                &owner,
1337                false,
1338            ),
1339            AccountInfo::new(
1340                &pubkey_arb_2,
1341                false,
1342                true,
1343                &mut lamports_arb_2,
1344                &mut data_arb_2,
1345                &owner,
1346                false,
1347            ),
1348        ];
1349
1350        // Let's use a mock RPC and set up a test instruction to check the CPI
1351        // instruction against later
1352        let rpc_account_infos = all_account_infos.clone();
1353        let mock_rpc = MockRpc::setup(&rpc_account_infos);
1354
1355        let account_size = ExtraAccountMetaList::size_of(required_accounts.len()).unwrap();
1356        let mut buffer = vec![0; account_size];
1357        ExtraAccountMetaList::init::<TestInstruction>(&mut buffer, &required_accounts).unwrap();
1358
1359        let mut instruction =
1360            Instruction::new_with_bytes(program_id, &instruction_data, ix_accounts.clone());
1361        ExtraAccountMetaList::add_to_instruction::<TestInstruction, _, _>(
1362            &mut instruction,
1363            |pubkey| mock_rpc.get_account_data(pubkey),
1364            &buffer,
1365        )
1366        .await
1367        .unwrap();
1368
1369        // Perform the account resolution for the CPI instruction
1370
1371        // Create the instruction itself
1372        let mut cpi_instruction =
1373            Instruction::new_with_bytes(program_id, &instruction_data, ix_accounts);
1374
1375        // Start with the known account infos
1376        let mut cpi_account_infos =
1377            vec![all_account_infos[0].clone(), all_account_infos[1].clone()];
1378
1379        // Mess up the ordering of the account infos to make it harder!
1380        let mut messed_account_infos = all_account_infos.clone();
1381        messed_account_infos.swap(0, 4);
1382        messed_account_infos.swap(1, 2);
1383        messed_account_infos.swap(3, 4);
1384        messed_account_infos.swap(5, 6);
1385        messed_account_infos.swap(8, 7);
1386
1387        // Resolve the rest!
1388        ExtraAccountMetaList::add_to_cpi_instruction::<TestInstruction>(
1389            &mut cpi_instruction,
1390            &mut cpi_account_infos,
1391            &buffer,
1392            &messed_account_infos,
1393        )
1394        .unwrap();
1395
1396        // Our CPI instruction should match the check instruction.
1397        assert_eq!(cpi_instruction, instruction);
1398
1399        // CPI account infos should have the instruction account infos
1400        // and the extra required account infos from the validation account,
1401        // and they should be in the correct order.
1402        // Note: The two additional arbitrary account infos for the currently
1403        // executing program won't be present in the CPI instruction's account
1404        // infos, so we will omit them (hence the `..9`).
1405        let check_account_infos = &all_account_infos[..12];
1406        assert_eq!(cpi_account_infos.len(), check_account_infos.len());
1407        for (a, b) in std::iter::zip(cpi_account_infos, check_account_infos) {
1408            assert_eq!(a.key, b.key);
1409            assert_eq!(a.is_signer, b.is_signer);
1410            assert_eq!(a.is_writable, b.is_writable);
1411        }
1412    }
1413
1414    async fn update_and_assert_metas(
1415        program_id: Pubkey,
1416        buffer: &mut Vec<u8>,
1417        updated_metas: &[ExtraAccountMeta],
1418        check_metas: &[AccountMeta],
1419    ) {
1420        // resize buffer if necessary
1421        let account_size = ExtraAccountMetaList::size_of(updated_metas.len()).unwrap();
1422        if account_size > buffer.len() {
1423            buffer.resize(account_size, 0);
1424        }
1425
1426        // update
1427        ExtraAccountMetaList::update::<TestInstruction>(buffer, updated_metas).unwrap();
1428
1429        // retrieve metas and assert
1430        let state = TlvStateBorrowed::unpack(buffer).unwrap();
1431        let unpacked_metas_pod =
1432            ExtraAccountMetaList::unpack_with_tlv_state::<TestInstruction>(&state).unwrap();
1433        assert_eq!(
1434            &*unpacked_metas_pod, updated_metas,
1435            "The ExtraAccountMetas in the buffer should match the expected ones."
1436        );
1437
1438        let mock_rpc = MockRpc::setup(&[]);
1439
1440        let mut instruction = Instruction::new_with_bytes(program_id, &[], vec![]);
1441        ExtraAccountMetaList::add_to_instruction::<TestInstruction, _, _>(
1442            &mut instruction,
1443            |pubkey| mock_rpc.get_account_data(pubkey),
1444            buffer,
1445        )
1446        .await
1447        .unwrap();
1448
1449        assert_eq!(instruction.accounts, check_metas,);
1450    }
1451
1452    #[tokio::test]
1453    async fn update_extra_account_meta_list() {
1454        let program_id = Pubkey::new_unique();
1455
1456        // Create list of initial metas
1457        let initial_metas = [
1458            ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, true).unwrap(),
1459            ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, false).unwrap(),
1460        ];
1461
1462        // initialize
1463        let initial_account_size = ExtraAccountMetaList::size_of(initial_metas.len()).unwrap();
1464        let mut buffer = vec![0; initial_account_size];
1465        ExtraAccountMetaList::init::<TestInstruction>(&mut buffer, &initial_metas).unwrap();
1466
1467        // Create updated metas list of the same size
1468        let updated_metas_1 = [
1469            ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, true).unwrap(),
1470            ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, false).unwrap(),
1471        ];
1472        let check_metas_1 = updated_metas_1
1473            .iter()
1474            .map(|e| AccountMeta::try_from(e).unwrap())
1475            .collect::<Vec<_>>();
1476        update_and_assert_metas(program_id, &mut buffer, &updated_metas_1, &check_metas_1).await;
1477
1478        // Create updated and larger list of metas
1479        let updated_metas_2 = [
1480            ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, true).unwrap(),
1481            ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, false).unwrap(),
1482            ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, true).unwrap(),
1483        ];
1484        let check_metas_2 = updated_metas_2
1485            .iter()
1486            .map(|e| AccountMeta::try_from(e).unwrap())
1487            .collect::<Vec<_>>();
1488        update_and_assert_metas(program_id, &mut buffer, &updated_metas_2, &check_metas_2).await;
1489
1490        // Create updated and smaller list of metas
1491        let updated_metas_3 =
1492            [ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, true).unwrap()];
1493        let check_metas_3 = updated_metas_3
1494            .iter()
1495            .map(|e| AccountMeta::try_from(e).unwrap())
1496            .collect::<Vec<_>>();
1497        update_and_assert_metas(program_id, &mut buffer, &updated_metas_3, &check_metas_3).await;
1498
1499        // Create updated list of metas with a simple PDA
1500        let seed_pubkey = Pubkey::new_unique();
1501        let updated_metas_4 = [
1502            ExtraAccountMeta::new_with_pubkey(&seed_pubkey, true, true).unwrap(),
1503            ExtraAccountMeta::new_with_seeds(
1504                &[
1505                    Seed::Literal {
1506                        bytes: b"seed-prefix".to_vec(),
1507                    },
1508                    Seed::AccountKey { index: 0 },
1509                ],
1510                false,
1511                true,
1512            )
1513            .unwrap(),
1514        ];
1515        let simple_pda = Pubkey::find_program_address(
1516            &[
1517                b"seed-prefix",       // Literal prefix
1518                seed_pubkey.as_ref(), // Account at index 0
1519            ],
1520            &program_id,
1521        )
1522        .0;
1523        let check_metas_4 = [
1524            AccountMeta::new(seed_pubkey, true),
1525            AccountMeta::new(simple_pda, false),
1526        ];
1527
1528        update_and_assert_metas(program_id, &mut buffer, &updated_metas_4, &check_metas_4).await;
1529    }
1530
1531    #[test]
1532    fn check_account_infos_test() {
1533        let program_id = Pubkey::new_unique();
1534        let owner = Pubkey::new_unique();
1535
1536        // Create a list of required account metas
1537        let pubkey1 = Pubkey::new_unique();
1538        let pubkey2 = Pubkey::new_unique();
1539        let required_accounts = [
1540            ExtraAccountMeta::new_with_pubkey(&pubkey1, false, true).unwrap(),
1541            ExtraAccountMeta::new_with_pubkey(&pubkey2, false, false).unwrap(),
1542            ExtraAccountMeta::new_with_seeds(
1543                &[
1544                    Seed::Literal {
1545                        bytes: b"lit_seed".to_vec(),
1546                    },
1547                    Seed::InstructionData {
1548                        index: 0,
1549                        length: 4,
1550                    },
1551                    Seed::AccountKey { index: 0 },
1552                ],
1553                false,
1554                true,
1555            )
1556            .unwrap(),
1557            ExtraAccountMeta::new_with_pubkey_data(
1558                &PubkeyData::InstructionData { index: 8 },
1559                false,
1560                true,
1561            )
1562            .unwrap(),
1563        ];
1564
1565        // Create the validation data
1566        let account_size = ExtraAccountMetaList::size_of(required_accounts.len()).unwrap();
1567        let mut buffer = vec![0; account_size];
1568        ExtraAccountMetaList::init::<TestInstruction>(&mut buffer, &required_accounts).unwrap();
1569
1570        // Create the instruction data
1571        let mut instruction_data = vec![0, 1, 2, 3, 4, 5, 6, 7];
1572        let key_data_pubkey = Pubkey::new_unique();
1573        instruction_data.extend_from_slice(key_data_pubkey.as_ref());
1574
1575        // Set up a list of the required accounts as account infos,
1576        // with two instruction accounts
1577        let pubkey_ix_1 = Pubkey::new_unique();
1578        let mut lamports_ix_1 = 0;
1579        let mut data_ix_1 = [];
1580        let pubkey_ix_2 = Pubkey::new_unique();
1581        let mut lamports_ix_2 = 0;
1582        let mut data_ix_2 = [];
1583        let mut lamports1 = 0;
1584        let mut data1 = [];
1585        let mut lamports2 = 0;
1586        let mut data2 = [];
1587        let mut lamports3 = 0;
1588        let mut data3 = [];
1589        let mut lamports4 = 0;
1590        let mut data4 = [];
1591        let pda = Pubkey::find_program_address(
1592            &[b"lit_seed", &instruction_data[..4], pubkey_ix_1.as_ref()],
1593            &program_id,
1594        )
1595        .0;
1596        let account_infos = [
1597            // Instruction account 1
1598            AccountInfo::new(
1599                &pubkey_ix_1,
1600                false,
1601                true,
1602                &mut lamports_ix_1,
1603                &mut data_ix_1,
1604                &owner,
1605                false,
1606            ),
1607            // Instruction account 2
1608            AccountInfo::new(
1609                &pubkey_ix_2,
1610                false,
1611                true,
1612                &mut lamports_ix_2,
1613                &mut data_ix_2,
1614                &owner,
1615                false,
1616            ),
1617            // Required account 1
1618            AccountInfo::new(
1619                &pubkey1,
1620                false,
1621                true,
1622                &mut lamports1,
1623                &mut data1,
1624                &owner,
1625                false,
1626            ),
1627            // Required account 2
1628            AccountInfo::new(
1629                &pubkey2,
1630                false,
1631                false,
1632                &mut lamports2,
1633                &mut data2,
1634                &owner,
1635                false,
1636            ),
1637            // Required account 3 (PDA)
1638            AccountInfo::new(&pda, false, true, &mut lamports3, &mut data3, &owner, false),
1639            // Required account 4 (pubkey data)
1640            AccountInfo::new(
1641                &key_data_pubkey,
1642                false,
1643                true,
1644                &mut lamports4,
1645                &mut data4,
1646                &owner,
1647                false,
1648            ),
1649        ];
1650
1651        // Create another list of account infos to intentionally mess up
1652        let mut messed_account_infos = account_infos.clone().to_vec();
1653        messed_account_infos.swap(0, 2);
1654        messed_account_infos.swap(1, 4);
1655        messed_account_infos.swap(3, 2);
1656        messed_account_infos.swap(5, 4);
1657
1658        // Account info check should fail for the messed list
1659        assert_eq!(
1660            ExtraAccountMetaList::check_account_infos::<TestInstruction>(
1661                &messed_account_infos,
1662                &instruction_data,
1663                &program_id,
1664                &buffer,
1665            )
1666            .unwrap_err(),
1667            AccountResolutionError::IncorrectAccount.into(),
1668        );
1669
1670        // Account info check should pass for the correct list
1671        assert_eq!(
1672            ExtraAccountMetaList::check_account_infos::<TestInstruction>(
1673                &account_infos,
1674                &instruction_data,
1675                &program_id,
1676                &buffer,
1677            ),
1678            Ok(()),
1679        );
1680    }
1681}