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