clone_solana_message/
compiled_keys.rs

1#[cfg(not(target_os = "solana"))]
2use crate::{
3    v0::{LoadedAddresses, MessageAddressTableLookup},
4    AddressLookupTableAccount,
5};
6use {
7    crate::MessageHeader, clone_solana_instruction::Instruction, clone_solana_pubkey::Pubkey,
8    clone_solana_sdk_ids::system_program, core::fmt, std::collections::BTreeMap,
9};
10
11/// A helper struct to collect pubkeys compiled for a set of instructions
12#[derive(Default, Debug, Clone, PartialEq, Eq)]
13pub(crate) struct CompiledKeys {
14    payer: Option<Pubkey>,
15    key_meta_map: BTreeMap<Pubkey, CompiledKeyMeta>,
16}
17
18#[cfg_attr(target_os = "solana", allow(dead_code))]
19#[derive(PartialEq, Debug, Eq, Clone)]
20pub enum CompileError {
21    AccountIndexOverflow,
22    AddressTableLookupIndexOverflow,
23    UnknownInstructionKey(Pubkey),
24}
25
26impl std::error::Error for CompileError {}
27
28impl fmt::Display for CompileError {
29    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
30        match self {
31            CompileError::AccountIndexOverflow => {
32                f.write_str("account index overflowed during compilation")
33            }
34            CompileError::AddressTableLookupIndexOverflow => {
35                f.write_str("address lookup table index overflowed during compilation")
36            }
37            CompileError::UnknownInstructionKey(key) => f.write_fmt(format_args!(
38                "encountered unknown account key `{0}` during instruction compilation",
39                key,
40            )),
41        }
42    }
43}
44
45#[derive(Default, Debug, Clone, PartialEq, Eq)]
46struct CompiledKeyMeta {
47    is_signer: bool,
48    is_writable: bool,
49    is_invoked: bool,
50    is_nonce: bool,
51}
52
53impl CompiledKeys {
54    /// Compiles the pubkeys referenced by a list of instructions and organizes by
55    /// signer/non-signer and writable/readonly.
56    pub(crate) fn compile(instructions: &[Instruction], payer: Option<Pubkey>) -> Self {
57        let mut key_meta_map = BTreeMap::<Pubkey, CompiledKeyMeta>::new();
58        for ix in instructions {
59            let meta = key_meta_map.entry(ix.program_id).or_default();
60            meta.is_invoked = true;
61            for account_meta in &ix.accounts {
62                let meta = key_meta_map.entry(account_meta.pubkey).or_default();
63                meta.is_signer |= account_meta.is_signer;
64                meta.is_writable |= account_meta.is_writable;
65            }
66        }
67        if let Some(nonce_pubkey) = get_nonce_pubkey(instructions) {
68            let meta = key_meta_map.entry(*nonce_pubkey).or_default();
69            meta.is_nonce = true;
70        }
71        if let Some(payer) = &payer {
72            let meta = key_meta_map.entry(*payer).or_default();
73            meta.is_signer = true;
74            meta.is_writable = true;
75        }
76        Self {
77            payer,
78            key_meta_map,
79        }
80    }
81
82    pub(crate) fn try_into_message_components(
83        self,
84    ) -> Result<(MessageHeader, Vec<Pubkey>), CompileError> {
85        let try_into_u8 = |num: usize| -> Result<u8, CompileError> {
86            u8::try_from(num).map_err(|_| CompileError::AccountIndexOverflow)
87        };
88
89        let Self {
90            payer,
91            mut key_meta_map,
92        } = self;
93
94        if let Some(payer) = &payer {
95            key_meta_map.remove_entry(payer);
96        }
97
98        let writable_signer_keys: Vec<Pubkey> = payer
99            .into_iter()
100            .chain(
101                key_meta_map
102                    .iter()
103                    .filter_map(|(key, meta)| (meta.is_signer && meta.is_writable).then_some(*key)),
104            )
105            .collect();
106        let readonly_signer_keys: Vec<Pubkey> = key_meta_map
107            .iter()
108            .filter_map(|(key, meta)| (meta.is_signer && !meta.is_writable).then_some(*key))
109            .collect();
110        let writable_non_signer_keys: Vec<Pubkey> = key_meta_map
111            .iter()
112            .filter_map(|(key, meta)| (!meta.is_signer && meta.is_writable).then_some(*key))
113            .collect();
114        let readonly_non_signer_keys: Vec<Pubkey> = key_meta_map
115            .iter()
116            .filter_map(|(key, meta)| (!meta.is_signer && !meta.is_writable).then_some(*key))
117            .collect();
118
119        let signers_len = writable_signer_keys
120            .len()
121            .saturating_add(readonly_signer_keys.len());
122
123        let header = MessageHeader {
124            num_required_signatures: try_into_u8(signers_len)?,
125            num_readonly_signed_accounts: try_into_u8(readonly_signer_keys.len())?,
126            num_readonly_unsigned_accounts: try_into_u8(readonly_non_signer_keys.len())?,
127        };
128
129        let static_account_keys = std::iter::empty()
130            .chain(writable_signer_keys)
131            .chain(readonly_signer_keys)
132            .chain(writable_non_signer_keys)
133            .chain(readonly_non_signer_keys)
134            .collect();
135
136        Ok((header, static_account_keys))
137    }
138
139    #[cfg(not(target_os = "solana"))]
140    pub(crate) fn try_extract_table_lookup(
141        &mut self,
142        lookup_table_account: &AddressLookupTableAccount,
143    ) -> Result<Option<(MessageAddressTableLookup, LoadedAddresses)>, CompileError> {
144        let (writable_indexes, drained_writable_keys) = self
145            .try_drain_keys_found_in_lookup_table(&lookup_table_account.addresses, |meta| {
146                !meta.is_signer && !meta.is_invoked && !meta.is_nonce && meta.is_writable
147            })?;
148        let (readonly_indexes, drained_readonly_keys) = self
149            .try_drain_keys_found_in_lookup_table(&lookup_table_account.addresses, |meta| {
150                !meta.is_signer && !meta.is_invoked && !meta.is_nonce && !meta.is_writable
151            })?;
152
153        // Don't extract lookup if no keys were found
154        if writable_indexes.is_empty() && readonly_indexes.is_empty() {
155            return Ok(None);
156        }
157
158        Ok(Some((
159            MessageAddressTableLookup {
160                account_key: lookup_table_account.key,
161                writable_indexes,
162                readonly_indexes,
163            },
164            LoadedAddresses {
165                writable: drained_writable_keys,
166                readonly: drained_readonly_keys,
167            },
168        )))
169    }
170
171    #[cfg(not(target_os = "solana"))]
172    fn try_drain_keys_found_in_lookup_table(
173        &mut self,
174        lookup_table_addresses: &[Pubkey],
175        key_meta_filter: impl Fn(&CompiledKeyMeta) -> bool,
176    ) -> Result<(Vec<u8>, Vec<Pubkey>), CompileError> {
177        let mut lookup_table_indexes = Vec::new();
178        let mut drained_keys = Vec::new();
179
180        for search_key in self
181            .key_meta_map
182            .iter()
183            .filter_map(|(key, meta)| key_meta_filter(meta).then_some(key))
184        {
185            for (key_index, key) in lookup_table_addresses.iter().enumerate() {
186                if key == search_key {
187                    let lookup_table_index = u8::try_from(key_index)
188                        .map_err(|_| CompileError::AddressTableLookupIndexOverflow)?;
189
190                    lookup_table_indexes.push(lookup_table_index);
191                    drained_keys.push(*search_key);
192                    break;
193                }
194            }
195        }
196
197        for key in &drained_keys {
198            self.key_meta_map.remove_entry(key);
199        }
200
201        Ok((lookup_table_indexes, drained_keys))
202    }
203}
204
205// inlined to avoid clone_solana_nonce dep
206const NONCED_TX_MARKER_IX_INDEX: usize = 0;
207// inlined to avoid clone_solana_system_interface and bincode deps
208const ADVANCE_NONCE_PREFIX: [u8; 4] = [4, 0, 0, 0];
209
210fn get_nonce_pubkey(instructions: &[Instruction]) -> Option<&Pubkey> {
211    let ix = instructions.get(NONCED_TX_MARKER_IX_INDEX)?;
212    if !system_program::check_id(&ix.program_id) {
213        return None;
214    }
215
216    if ix.data.get(0..4) != Some(&ADVANCE_NONCE_PREFIX[..]) {
217        return None;
218    }
219
220    ix.accounts.first().map(|meta| &meta.pubkey)
221}
222
223#[cfg(test)]
224mod tests {
225    use {
226        super::*,
227        bitflags::bitflags,
228        clone_solana_instruction::AccountMeta,
229        clone_solana_sdk_ids::sysvar::recent_blockhashes,
230        clone_solana_system_interface::instruction::{advance_nonce_account, SystemInstruction},
231    };
232
233    static_assertions::const_assert_eq!(
234        NONCED_TX_MARKER_IX_INDEX,
235        clone_solana_nonce::NONCED_TX_MARKER_IX_INDEX as usize
236    );
237
238    bitflags! {
239        #[derive(Clone, Copy)]
240        pub struct KeyFlags: u8 {
241            const SIGNER   = 0b00000001;
242            const WRITABLE = 0b00000010;
243            const INVOKED  = 0b00000100;
244            const NONCE  = 0b00001000;
245        }
246    }
247
248    impl From<KeyFlags> for CompiledKeyMeta {
249        fn from(flags: KeyFlags) -> Self {
250            Self {
251                is_signer: flags.contains(KeyFlags::SIGNER),
252                is_writable: flags.contains(KeyFlags::WRITABLE),
253                is_invoked: flags.contains(KeyFlags::INVOKED),
254                is_nonce: flags.contains(KeyFlags::NONCE),
255            }
256        }
257    }
258
259    #[test]
260    fn test_advance_nonce_ix_prefix() {
261        let advance_nonce_ix: SystemInstruction = clone_solana_bincode::limited_deserialize(
262            &ADVANCE_NONCE_PREFIX[..],
263            4, /* serialized size of AdvanceNonceAccount */
264        )
265        .unwrap();
266        assert_eq!(advance_nonce_ix, SystemInstruction::AdvanceNonceAccount);
267    }
268
269    #[test]
270    fn test_compile_with_dups() {
271        let program_id0 = Pubkey::new_unique();
272        let program_id1 = Pubkey::new_unique();
273        let program_id2 = Pubkey::new_unique();
274        let program_id3 = Pubkey::new_unique();
275        let id0 = Pubkey::new_unique();
276        let id1 = Pubkey::new_unique();
277        let id2 = Pubkey::new_unique();
278        let id3 = Pubkey::new_unique();
279        let compiled_keys = CompiledKeys::compile(
280            &[
281                Instruction::new_with_bincode(
282                    program_id0,
283                    &0,
284                    vec![
285                        AccountMeta::new_readonly(id0, false),
286                        AccountMeta::new_readonly(id1, true),
287                        AccountMeta::new(id2, false),
288                        AccountMeta::new(id3, true),
289                        // duplicate the account inputs
290                        AccountMeta::new_readonly(id0, false),
291                        AccountMeta::new_readonly(id1, true),
292                        AccountMeta::new(id2, false),
293                        AccountMeta::new(id3, true),
294                        // reference program ids
295                        AccountMeta::new_readonly(program_id0, false),
296                        AccountMeta::new_readonly(program_id1, true),
297                        AccountMeta::new(program_id2, false),
298                        AccountMeta::new(program_id3, true),
299                    ],
300                ),
301                Instruction::new_with_bincode(program_id1, &0, vec![]),
302                Instruction::new_with_bincode(program_id2, &0, vec![]),
303                Instruction::new_with_bincode(program_id3, &0, vec![]),
304            ],
305            None,
306        );
307
308        assert_eq!(
309            compiled_keys,
310            CompiledKeys {
311                payer: None,
312                key_meta_map: BTreeMap::from([
313                    (id0, KeyFlags::empty().into()),
314                    (id1, KeyFlags::SIGNER.into()),
315                    (id2, KeyFlags::WRITABLE.into()),
316                    (id3, (KeyFlags::SIGNER | KeyFlags::WRITABLE).into()),
317                    (program_id0, KeyFlags::INVOKED.into()),
318                    (program_id1, (KeyFlags::INVOKED | KeyFlags::SIGNER).into()),
319                    (program_id2, (KeyFlags::INVOKED | KeyFlags::WRITABLE).into()),
320                    (
321                        program_id3,
322                        (KeyFlags::INVOKED | KeyFlags::WRITABLE | KeyFlags::SIGNER).into()
323                    ),
324                ]),
325            }
326        );
327    }
328
329    #[test]
330    fn test_compile_with_dup_payer() {
331        let program_id = Pubkey::new_unique();
332        let payer = Pubkey::new_unique();
333        let compiled_keys = CompiledKeys::compile(
334            &[Instruction::new_with_bincode(
335                program_id,
336                &0,
337                vec![AccountMeta::new_readonly(payer, false)],
338            )],
339            Some(payer),
340        );
341        assert_eq!(
342            compiled_keys,
343            CompiledKeys {
344                payer: Some(payer),
345                key_meta_map: BTreeMap::from([
346                    (payer, (KeyFlags::SIGNER | KeyFlags::WRITABLE).into()),
347                    (program_id, KeyFlags::INVOKED.into()),
348                ]),
349            }
350        );
351    }
352
353    #[test]
354    fn test_compile_with_dup_signer_mismatch() {
355        let program_id = Pubkey::new_unique();
356        let id0 = Pubkey::new_unique();
357        let compiled_keys = CompiledKeys::compile(
358            &[Instruction::new_with_bincode(
359                program_id,
360                &0,
361                vec![AccountMeta::new(id0, false), AccountMeta::new(id0, true)],
362            )],
363            None,
364        );
365
366        // Ensure the dup writable key is a signer
367        assert_eq!(
368            compiled_keys,
369            CompiledKeys {
370                payer: None,
371                key_meta_map: BTreeMap::from([
372                    (id0, (KeyFlags::SIGNER | KeyFlags::WRITABLE).into()),
373                    (program_id, KeyFlags::INVOKED.into()),
374                ]),
375            }
376        );
377    }
378
379    #[test]
380    fn test_compile_with_dup_signer_writable_mismatch() {
381        let program_id = Pubkey::new_unique();
382        let id0 = Pubkey::new_unique();
383        let compiled_keys = CompiledKeys::compile(
384            &[Instruction::new_with_bincode(
385                program_id,
386                &0,
387                vec![
388                    AccountMeta::new_readonly(id0, true),
389                    AccountMeta::new(id0, true),
390                ],
391            )],
392            None,
393        );
394
395        // Ensure the dup signer key is writable
396        assert_eq!(
397            compiled_keys,
398            CompiledKeys {
399                payer: None,
400                key_meta_map: BTreeMap::from([
401                    (id0, (KeyFlags::SIGNER | KeyFlags::WRITABLE).into()),
402                    (program_id, KeyFlags::INVOKED.into()),
403                ]),
404            }
405        );
406    }
407
408    #[test]
409    fn test_compile_with_dup_nonsigner_writable_mismatch() {
410        let program_id = Pubkey::new_unique();
411        let id0 = Pubkey::new_unique();
412        let compiled_keys = CompiledKeys::compile(
413            &[
414                Instruction::new_with_bincode(
415                    program_id,
416                    &0,
417                    vec![
418                        AccountMeta::new_readonly(id0, false),
419                        AccountMeta::new(id0, false),
420                    ],
421                ),
422                Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, false)]),
423            ],
424            None,
425        );
426
427        // Ensure the dup nonsigner key is writable
428        assert_eq!(
429            compiled_keys,
430            CompiledKeys {
431                payer: None,
432                key_meta_map: BTreeMap::from([
433                    (id0, KeyFlags::WRITABLE.into()),
434                    (program_id, KeyFlags::INVOKED.into()),
435                ]),
436            }
437        );
438    }
439
440    #[test]
441    fn test_compile_with_nonce_instruction() {
442        let nonce_pubkey = Pubkey::new_unique();
443        let nonce_authority = Pubkey::new_unique();
444        let compiled_keys = CompiledKeys::compile(
445            &[advance_nonce_account(&nonce_pubkey, &nonce_authority)],
446            Some(nonce_authority),
447        );
448
449        assert_eq!(
450            compiled_keys,
451            CompiledKeys {
452                payer: Some(nonce_authority),
453                key_meta_map: BTreeMap::from([
454                    (nonce_pubkey, (KeyFlags::NONCE | KeyFlags::WRITABLE).into()),
455                    (
456                        nonce_authority,
457                        (KeyFlags::SIGNER | KeyFlags::WRITABLE).into()
458                    ),
459                    (system_program::id(), KeyFlags::INVOKED.into()),
460                    (recent_blockhashes::id(), CompiledKeyMeta::default())
461                ]),
462            }
463        );
464    }
465
466    #[test]
467    fn test_try_into_message_components() {
468        let keys = vec![
469            Pubkey::new_unique(),
470            Pubkey::new_unique(),
471            Pubkey::new_unique(),
472            Pubkey::new_unique(),
473        ];
474
475        let compiled_keys = CompiledKeys {
476            payer: None,
477            key_meta_map: BTreeMap::from([
478                (keys[0], (KeyFlags::SIGNER | KeyFlags::WRITABLE).into()),
479                (keys[1], KeyFlags::SIGNER.into()),
480                (keys[2], KeyFlags::WRITABLE.into()),
481                (keys[3], KeyFlags::empty().into()),
482            ]),
483        };
484
485        let result = compiled_keys.try_into_message_components();
486        assert_eq!(result.as_ref().err(), None);
487        let (header, static_keys) = result.unwrap();
488
489        assert_eq!(static_keys, keys);
490        assert_eq!(
491            header,
492            MessageHeader {
493                num_required_signatures: 2,
494                num_readonly_signed_accounts: 1,
495                num_readonly_unsigned_accounts: 1,
496            }
497        );
498    }
499
500    #[test]
501    fn test_try_into_message_components_with_too_many_keys() {
502        const TOO_MANY_KEYS: usize = 257;
503
504        for key_flags in [
505            KeyFlags::WRITABLE | KeyFlags::SIGNER,
506            KeyFlags::SIGNER,
507            // skip writable_non_signer_keys because it isn't used for creating header values
508            KeyFlags::empty(),
509        ] {
510            let test_keys = CompiledKeys {
511                payer: None,
512                key_meta_map: BTreeMap::from_iter(
513                    (0..TOO_MANY_KEYS).map(|_| (Pubkey::new_unique(), key_flags.into())),
514                ),
515            };
516
517            assert_eq!(
518                test_keys.try_into_message_components(),
519                Err(CompileError::AccountIndexOverflow)
520            );
521        }
522    }
523
524    #[test]
525    fn test_try_extract_table_lookup() {
526        let keys = vec![
527            Pubkey::new_unique(),
528            Pubkey::new_unique(),
529            Pubkey::new_unique(),
530            Pubkey::new_unique(),
531            Pubkey::new_unique(),
532            Pubkey::new_unique(),
533        ];
534
535        let mut compiled_keys = CompiledKeys {
536            payer: None,
537            key_meta_map: BTreeMap::from([
538                (keys[0], (KeyFlags::SIGNER | KeyFlags::WRITABLE).into()),
539                (keys[1], KeyFlags::SIGNER.into()),
540                (keys[2], KeyFlags::WRITABLE.into()),
541                (keys[3], KeyFlags::empty().into()),
542                (keys[4], (KeyFlags::INVOKED | KeyFlags::WRITABLE).into()),
543                (keys[5], (KeyFlags::INVOKED).into()),
544            ]),
545        };
546
547        // add some duplicates to ensure lowest index is selected
548        let addresses = [keys.clone(), keys.clone()].concat();
549        let lookup_table_account = AddressLookupTableAccount {
550            key: Pubkey::new_unique(),
551            addresses,
552        };
553
554        assert_eq!(
555            compiled_keys.try_extract_table_lookup(&lookup_table_account),
556            Ok(Some((
557                MessageAddressTableLookup {
558                    account_key: lookup_table_account.key,
559                    writable_indexes: vec![2],
560                    readonly_indexes: vec![3],
561                },
562                LoadedAddresses {
563                    writable: vec![keys[2]],
564                    readonly: vec![keys[3]],
565                },
566            )))
567        );
568
569        assert_eq!(compiled_keys.key_meta_map.len(), 4);
570        assert!(!compiled_keys.key_meta_map.contains_key(&keys[2]));
571        assert!(!compiled_keys.key_meta_map.contains_key(&keys[3]));
572    }
573
574    #[test]
575    fn test_try_extract_table_lookup_returns_none() {
576        let mut compiled_keys = CompiledKeys {
577            payer: None,
578            key_meta_map: BTreeMap::from([
579                (Pubkey::new_unique(), KeyFlags::WRITABLE.into()),
580                (Pubkey::new_unique(), KeyFlags::empty().into()),
581            ]),
582        };
583
584        let lookup_table_account = AddressLookupTableAccount {
585            key: Pubkey::new_unique(),
586            addresses: vec![],
587        };
588
589        let expected_compiled_keys = compiled_keys.clone();
590        assert_eq!(
591            compiled_keys.try_extract_table_lookup(&lookup_table_account),
592            Ok(None)
593        );
594        assert_eq!(compiled_keys, expected_compiled_keys);
595    }
596
597    #[test]
598    fn test_try_extract_table_lookup_for_invalid_table() {
599        let writable_key = Pubkey::new_unique();
600        let mut compiled_keys = CompiledKeys {
601            payer: None,
602            key_meta_map: BTreeMap::from([
603                (writable_key, KeyFlags::WRITABLE.into()),
604                (Pubkey::new_unique(), KeyFlags::empty().into()),
605            ]),
606        };
607
608        const MAX_LENGTH_WITHOUT_OVERFLOW: usize = u8::MAX as usize + 1;
609        let mut addresses = vec![Pubkey::default(); MAX_LENGTH_WITHOUT_OVERFLOW];
610        addresses.push(writable_key);
611
612        let lookup_table_account = AddressLookupTableAccount {
613            key: Pubkey::new_unique(),
614            addresses,
615        };
616
617        let expected_compiled_keys = compiled_keys.clone();
618        assert_eq!(
619            compiled_keys.try_extract_table_lookup(&lookup_table_account),
620            Err(CompileError::AddressTableLookupIndexOverflow),
621        );
622        assert_eq!(compiled_keys, expected_compiled_keys);
623    }
624
625    #[test]
626    fn test_try_drain_keys_found_in_lookup_table() {
627        let orig_keys = [
628            Pubkey::new_unique(),
629            Pubkey::new_unique(),
630            Pubkey::new_unique(),
631            Pubkey::new_unique(),
632            Pubkey::new_unique(),
633        ];
634
635        let mut compiled_keys = CompiledKeys {
636            payer: None,
637            key_meta_map: BTreeMap::from([
638                (orig_keys[0], KeyFlags::empty().into()),
639                (orig_keys[1], KeyFlags::WRITABLE.into()),
640                (orig_keys[2], KeyFlags::WRITABLE.into()),
641                (orig_keys[3], KeyFlags::empty().into()),
642                (orig_keys[4], KeyFlags::empty().into()),
643            ]),
644        };
645
646        let lookup_table_addresses = vec![
647            Pubkey::new_unique(),
648            orig_keys[0],
649            Pubkey::new_unique(),
650            orig_keys[4],
651            Pubkey::new_unique(),
652            orig_keys[2],
653            Pubkey::new_unique(),
654        ];
655
656        let drain_result = compiled_keys
657            .try_drain_keys_found_in_lookup_table(&lookup_table_addresses, |meta| {
658                !meta.is_writable
659            });
660        assert_eq!(drain_result.as_ref().err(), None);
661        let (lookup_table_indexes, drained_keys) = drain_result.unwrap();
662
663        assert_eq!(
664            compiled_keys.key_meta_map.keys().collect::<Vec<&_>>(),
665            vec![&orig_keys[1], &orig_keys[2], &orig_keys[3]]
666        );
667        assert_eq!(drained_keys, vec![orig_keys[0], orig_keys[4]]);
668        assert_eq!(lookup_table_indexes, vec![1, 3]);
669    }
670
671    #[test]
672    fn test_try_drain_keys_found_in_lookup_table_with_empty_keys() {
673        let mut compiled_keys = CompiledKeys::default();
674
675        let lookup_table_addresses = vec![
676            Pubkey::new_unique(),
677            Pubkey::new_unique(),
678            Pubkey::new_unique(),
679        ];
680
681        let drain_result =
682            compiled_keys.try_drain_keys_found_in_lookup_table(&lookup_table_addresses, |_| true);
683        assert_eq!(drain_result.as_ref().err(), None);
684        let (lookup_table_indexes, drained_keys) = drain_result.unwrap();
685
686        assert!(drained_keys.is_empty());
687        assert!(lookup_table_indexes.is_empty());
688    }
689
690    #[test]
691    fn test_try_drain_keys_found_in_lookup_table_with_empty_table() {
692        let original_keys = [
693            Pubkey::new_unique(),
694            Pubkey::new_unique(),
695            Pubkey::new_unique(),
696        ];
697
698        let mut compiled_keys = CompiledKeys {
699            payer: None,
700            key_meta_map: BTreeMap::from_iter(
701                original_keys
702                    .iter()
703                    .map(|key| (*key, CompiledKeyMeta::default())),
704            ),
705        };
706
707        let lookup_table_addresses = vec![];
708
709        let drain_result =
710            compiled_keys.try_drain_keys_found_in_lookup_table(&lookup_table_addresses, |_| true);
711        assert_eq!(drain_result.as_ref().err(), None);
712        let (lookup_table_indexes, drained_keys) = drain_result.unwrap();
713
714        assert_eq!(compiled_keys.key_meta_map.len(), original_keys.len());
715        assert!(drained_keys.is_empty());
716        assert!(lookup_table_indexes.is_empty());
717    }
718
719    #[test]
720    fn test_try_drain_keys_found_in_lookup_table_with_too_many_addresses() {
721        let key = Pubkey::new_unique();
722        let mut compiled_keys = CompiledKeys {
723            payer: None,
724            key_meta_map: BTreeMap::from([(key, CompiledKeyMeta::default())]),
725        };
726
727        const MAX_LENGTH_WITHOUT_OVERFLOW: usize = u8::MAX as usize + 1;
728        let mut lookup_table_addresses = vec![Pubkey::default(); MAX_LENGTH_WITHOUT_OVERFLOW];
729        lookup_table_addresses.push(key);
730
731        let drain_result =
732            compiled_keys.try_drain_keys_found_in_lookup_table(&lookup_table_addresses, |_| true);
733        assert_eq!(
734            drain_result.err(),
735            Some(CompileError::AddressTableLookupIndexOverflow)
736        );
737    }
738}