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