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