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