1pub use loaded::*;
13#[cfg(feature = "serde")]
14use serde_derive::{Deserialize, Serialize};
15#[cfg(feature = "frozen-abi")]
16use solana_frozen_abi_macro::AbiExample;
17use {
18 crate::{
19 compiled_instruction::CompiledInstruction,
20 compiled_keys::{CompileError, CompiledKeys},
21 AccountKeys, AddressLookupTableAccount, MessageHeader,
22 },
23 solana_hash::Hash,
24 solana_instruction::Instruction,
25 solana_pubkey::Pubkey,
26 solana_sanitize::SanitizeError,
27 solana_sdk_ids::bpf_loader_upgradeable,
28 std::collections::HashSet,
29};
30
31mod loaded;
32
33#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
36#[cfg_attr(
37 feature = "serde",
38 derive(Deserialize, Serialize),
39 serde(rename_all = "camelCase")
40)]
41#[derive(Default, Debug, PartialEq, Eq, Clone)]
42pub struct MessageAddressTableLookup {
43 pub account_key: Pubkey,
45 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
47 pub writable_indexes: Vec<u8>,
48 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
50 pub readonly_indexes: Vec<u8>,
51}
52
53#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
61#[cfg_attr(
62 feature = "serde",
63 derive(Deserialize, Serialize),
64 serde(rename_all = "camelCase")
65)]
66#[derive(Default, Debug, PartialEq, Eq, Clone)]
67pub struct Message {
68 pub header: MessageHeader,
72
73 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
75 pub account_keys: Vec<Pubkey>,
76
77 pub recent_blockhash: Hash,
79
80 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
94 pub instructions: Vec<CompiledInstruction>,
95
96 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
99 pub address_table_lookups: Vec<MessageAddressTableLookup>,
100}
101
102impl Message {
103 pub fn sanitize(&self) -> Result<(), SanitizeError> {
105 let num_static_account_keys = self.account_keys.len();
106 if usize::from(self.header.num_required_signatures)
107 .saturating_add(usize::from(self.header.num_readonly_unsigned_accounts))
108 > num_static_account_keys
109 {
110 return Err(SanitizeError::IndexOutOfBounds);
111 }
112
113 if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures {
115 return Err(SanitizeError::InvalidValue);
116 }
117
118 let num_dynamic_account_keys = {
119 let mut total_lookup_keys: usize = 0;
120 for lookup in &self.address_table_lookups {
121 let num_lookup_indexes = lookup
122 .writable_indexes
123 .len()
124 .saturating_add(lookup.readonly_indexes.len());
125
126 if num_lookup_indexes == 0 {
128 return Err(SanitizeError::InvalidValue);
129 }
130
131 total_lookup_keys = total_lookup_keys.saturating_add(num_lookup_indexes);
132 }
133 total_lookup_keys
134 };
135
136 if num_static_account_keys == 0 {
140 return Err(SanitizeError::InvalidValue);
141 }
142
143 let total_account_keys = num_static_account_keys.saturating_add(num_dynamic_account_keys);
148 if total_account_keys > 256 {
149 return Err(SanitizeError::IndexOutOfBounds);
150 }
151
152 let max_account_ix = total_account_keys
155 .checked_sub(1)
156 .expect("message doesn't contain any account keys");
157
158 let max_program_id_ix =
162 num_static_account_keys
165 .checked_sub(1)
166 .expect("message doesn't contain any static account keys");
167
168 for ci in &self.instructions {
169 if usize::from(ci.program_id_index) > max_program_id_ix {
170 return Err(SanitizeError::IndexOutOfBounds);
171 }
172 if ci.program_id_index == 0 {
174 return Err(SanitizeError::IndexOutOfBounds);
175 }
176 for ai in &ci.accounts {
177 if usize::from(*ai) > max_account_ix {
178 return Err(SanitizeError::IndexOutOfBounds);
179 }
180 }
181 }
182
183 Ok(())
184 }
185}
186
187impl Message {
188 pub fn try_compile(
266 payer: &Pubkey,
267 instructions: &[Instruction],
268 address_lookup_table_accounts: &[AddressLookupTableAccount],
269 recent_blockhash: Hash,
270 ) -> Result<Self, CompileError> {
271 let mut compiled_keys = CompiledKeys::compile(instructions, Some(*payer));
272
273 let mut address_table_lookups = Vec::with_capacity(address_lookup_table_accounts.len());
274 let mut loaded_addresses_list = Vec::with_capacity(address_lookup_table_accounts.len());
275 for lookup_table_account in address_lookup_table_accounts {
276 if let Some((lookup, loaded_addresses)) =
277 compiled_keys.try_extract_table_lookup(lookup_table_account)?
278 {
279 address_table_lookups.push(lookup);
280 loaded_addresses_list.push(loaded_addresses);
281 }
282 }
283
284 let (header, static_keys) = compiled_keys.try_into_message_components()?;
285 let dynamic_keys = loaded_addresses_list.into_iter().collect();
286 let account_keys = AccountKeys::new(&static_keys, Some(&dynamic_keys));
287 let instructions = account_keys.try_compile_instructions(instructions)?;
288
289 Ok(Self {
290 header,
291 account_keys: static_keys,
292 recent_blockhash,
293 instructions,
294 address_table_lookups,
295 })
296 }
297
298 #[cfg(feature = "bincode")]
299 pub fn serialize(&self) -> Vec<u8> {
301 bincode::serialize(&(crate::MESSAGE_VERSION_PREFIX, self)).unwrap()
302 }
303
304 pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
306 if let Ok(key_index) = u8::try_from(key_index) {
307 self.instructions
308 .iter()
309 .any(|ix| ix.program_id_index == key_index)
310 } else {
311 false
312 }
313 }
314
315 fn is_writable_index(&self, key_index: usize) -> bool {
318 let header = &self.header;
319 let num_account_keys = self.account_keys.len();
320 let num_signed_accounts = usize::from(header.num_required_signatures);
321 if key_index >= num_account_keys {
322 let loaded_addresses_index = key_index.saturating_sub(num_account_keys);
323 let num_writable_dynamic_addresses = self
324 .address_table_lookups
325 .iter()
326 .map(|lookup| lookup.writable_indexes.len())
327 .sum();
328 loaded_addresses_index < num_writable_dynamic_addresses
329 } else if key_index >= num_signed_accounts {
330 let num_unsigned_accounts = num_account_keys.saturating_sub(num_signed_accounts);
331 let num_writable_unsigned_accounts = num_unsigned_accounts
332 .saturating_sub(usize::from(header.num_readonly_unsigned_accounts));
333 let unsigned_account_index = key_index.saturating_sub(num_signed_accounts);
334 unsigned_account_index < num_writable_unsigned_accounts
335 } else {
336 let num_writable_signed_accounts = num_signed_accounts
337 .saturating_sub(usize::from(header.num_readonly_signed_accounts));
338 key_index < num_writable_signed_accounts
339 }
340 }
341
342 fn is_upgradeable_loader_in_static_keys(&self) -> bool {
344 self.account_keys
345 .iter()
346 .any(|&key| key == bpf_loader_upgradeable::id())
347 }
348
349 pub fn is_maybe_writable(
355 &self,
356 key_index: usize,
357 reserved_account_keys: Option<&HashSet<Pubkey>>,
358 ) -> bool {
359 self.is_writable_index(key_index)
360 && !self.is_account_maybe_reserved(key_index, reserved_account_keys)
361 && !{
362 self.is_key_called_as_program(key_index)
364 && !self.is_upgradeable_loader_in_static_keys()
365 }
366 }
367
368 fn is_account_maybe_reserved(
372 &self,
373 key_index: usize,
374 reserved_account_keys: Option<&HashSet<Pubkey>>,
375 ) -> bool {
376 let mut is_maybe_reserved = false;
377 if let Some(reserved_account_keys) = reserved_account_keys {
378 if let Some(key) = self.account_keys.get(key_index) {
379 is_maybe_reserved = reserved_account_keys.contains(key);
380 }
381 }
382 is_maybe_reserved
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use {super::*, crate::VersionedMessage, solana_instruction::AccountMeta};
389
390 #[test]
391 fn test_sanitize() {
392 assert!(Message {
393 header: MessageHeader {
394 num_required_signatures: 1,
395 ..MessageHeader::default()
396 },
397 account_keys: vec![Pubkey::new_unique()],
398 ..Message::default()
399 }
400 .sanitize()
401 .is_ok());
402 }
403
404 #[test]
405 fn test_sanitize_with_instruction() {
406 assert!(Message {
407 header: MessageHeader {
408 num_required_signatures: 1,
409 ..MessageHeader::default()
410 },
411 account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()],
412 instructions: vec![CompiledInstruction {
413 program_id_index: 1,
414 accounts: vec![0],
415 data: vec![]
416 }],
417 ..Message::default()
418 }
419 .sanitize()
420 .is_ok());
421 }
422
423 #[test]
424 fn test_sanitize_with_table_lookup() {
425 assert!(Message {
426 header: MessageHeader {
427 num_required_signatures: 1,
428 ..MessageHeader::default()
429 },
430 account_keys: vec![Pubkey::new_unique()],
431 address_table_lookups: vec![MessageAddressTableLookup {
432 account_key: Pubkey::new_unique(),
433 writable_indexes: vec![1, 2, 3],
434 readonly_indexes: vec![0],
435 }],
436 ..Message::default()
437 }
438 .sanitize()
439 .is_ok());
440 }
441
442 #[test]
443 fn test_sanitize_with_table_lookup_and_ix_with_dynamic_program_id() {
444 let message = Message {
445 header: MessageHeader {
446 num_required_signatures: 1,
447 ..MessageHeader::default()
448 },
449 account_keys: vec![Pubkey::new_unique()],
450 address_table_lookups: vec![MessageAddressTableLookup {
451 account_key: Pubkey::new_unique(),
452 writable_indexes: vec![1, 2, 3],
453 readonly_indexes: vec![0],
454 }],
455 instructions: vec![CompiledInstruction {
456 program_id_index: 4,
457 accounts: vec![0, 1, 2, 3],
458 data: vec![],
459 }],
460 ..Message::default()
461 };
462
463 assert!(message.sanitize().is_err());
464 }
465
466 #[test]
467 fn test_sanitize_with_table_lookup_and_ix_with_static_program_id() {
468 assert!(Message {
469 header: MessageHeader {
470 num_required_signatures: 1,
471 ..MessageHeader::default()
472 },
473 account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()],
474 address_table_lookups: vec![MessageAddressTableLookup {
475 account_key: Pubkey::new_unique(),
476 writable_indexes: vec![1, 2, 3],
477 readonly_indexes: vec![0],
478 }],
479 instructions: vec![CompiledInstruction {
480 program_id_index: 1,
481 accounts: vec![2, 3, 4, 5],
482 data: vec![]
483 }],
484 ..Message::default()
485 }
486 .sanitize()
487 .is_ok());
488 }
489
490 #[test]
491 fn test_sanitize_without_signer() {
492 assert!(Message {
493 header: MessageHeader::default(),
494 account_keys: vec![Pubkey::new_unique()],
495 ..Message::default()
496 }
497 .sanitize()
498 .is_err());
499 }
500
501 #[test]
502 fn test_sanitize_without_writable_signer() {
503 assert!(Message {
504 header: MessageHeader {
505 num_required_signatures: 1,
506 num_readonly_signed_accounts: 1,
507 ..MessageHeader::default()
508 },
509 account_keys: vec![Pubkey::new_unique()],
510 ..Message::default()
511 }
512 .sanitize()
513 .is_err());
514 }
515
516 #[test]
517 fn test_sanitize_with_empty_table_lookup() {
518 assert!(Message {
519 header: MessageHeader {
520 num_required_signatures: 1,
521 ..MessageHeader::default()
522 },
523 account_keys: vec![Pubkey::new_unique()],
524 address_table_lookups: vec![MessageAddressTableLookup {
525 account_key: Pubkey::new_unique(),
526 writable_indexes: vec![],
527 readonly_indexes: vec![],
528 }],
529 ..Message::default()
530 }
531 .sanitize()
532 .is_err());
533 }
534
535 #[test]
536 fn test_sanitize_with_max_account_keys() {
537 assert!(Message {
538 header: MessageHeader {
539 num_required_signatures: 1,
540 ..MessageHeader::default()
541 },
542 account_keys: (0..=u8::MAX).map(|_| Pubkey::new_unique()).collect(),
543 ..Message::default()
544 }
545 .sanitize()
546 .is_ok());
547 }
548
549 #[test]
550 fn test_sanitize_with_too_many_account_keys() {
551 assert!(Message {
552 header: MessageHeader {
553 num_required_signatures: 1,
554 ..MessageHeader::default()
555 },
556 account_keys: (0..=256).map(|_| Pubkey::new_unique()).collect(),
557 ..Message::default()
558 }
559 .sanitize()
560 .is_err());
561 }
562
563 #[test]
564 fn test_sanitize_with_max_table_loaded_keys() {
565 assert!(Message {
566 header: MessageHeader {
567 num_required_signatures: 1,
568 ..MessageHeader::default()
569 },
570 account_keys: vec![Pubkey::new_unique()],
571 address_table_lookups: vec![MessageAddressTableLookup {
572 account_key: Pubkey::new_unique(),
573 writable_indexes: (0..=254).step_by(2).collect(),
574 readonly_indexes: (1..=254).step_by(2).collect(),
575 }],
576 ..Message::default()
577 }
578 .sanitize()
579 .is_ok());
580 }
581
582 #[test]
583 fn test_sanitize_with_too_many_table_loaded_keys() {
584 assert!(Message {
585 header: MessageHeader {
586 num_required_signatures: 1,
587 ..MessageHeader::default()
588 },
589 account_keys: vec![Pubkey::new_unique()],
590 address_table_lookups: vec![MessageAddressTableLookup {
591 account_key: Pubkey::new_unique(),
592 writable_indexes: (0..=255).step_by(2).collect(),
593 readonly_indexes: (1..=255).step_by(2).collect(),
594 }],
595 ..Message::default()
596 }
597 .sanitize()
598 .is_err());
599 }
600
601 #[test]
602 fn test_sanitize_with_invalid_ix_program_id() {
603 let message = Message {
604 header: MessageHeader {
605 num_required_signatures: 1,
606 ..MessageHeader::default()
607 },
608 account_keys: vec![Pubkey::new_unique()],
609 address_table_lookups: vec![MessageAddressTableLookup {
610 account_key: Pubkey::new_unique(),
611 writable_indexes: vec![0],
612 readonly_indexes: vec![],
613 }],
614 instructions: vec![CompiledInstruction {
615 program_id_index: 2,
616 accounts: vec![],
617 data: vec![],
618 }],
619 ..Message::default()
620 };
621
622 assert!(message.sanitize().is_err());
623 }
624
625 #[test]
626 fn test_sanitize_with_invalid_ix_account() {
627 assert!(Message {
628 header: MessageHeader {
629 num_required_signatures: 1,
630 ..MessageHeader::default()
631 },
632 account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()],
633 address_table_lookups: vec![MessageAddressTableLookup {
634 account_key: Pubkey::new_unique(),
635 writable_indexes: vec![],
636 readonly_indexes: vec![0],
637 }],
638 instructions: vec![CompiledInstruction {
639 program_id_index: 1,
640 accounts: vec![3],
641 data: vec![]
642 }],
643 ..Message::default()
644 }
645 .sanitize()
646 .is_err());
647 }
648
649 #[test]
650 fn test_serialize() {
651 let message = Message::default();
652 let versioned_msg = VersionedMessage::V0(message.clone());
653 assert_eq!(message.serialize(), versioned_msg.serialize());
654 }
655
656 #[test]
657 fn test_try_compile() {
658 let mut keys = vec![];
659 keys.resize_with(7, Pubkey::new_unique);
660
661 let payer = keys[0];
662 let program_id = keys[6];
663 let instructions = vec![Instruction {
664 program_id,
665 accounts: vec![
666 AccountMeta::new(keys[1], true),
667 AccountMeta::new_readonly(keys[2], true),
668 AccountMeta::new(keys[3], false),
669 AccountMeta::new(keys[4], false), AccountMeta::new_readonly(keys[5], false), ],
672 data: vec![],
673 }];
674 let address_lookup_table_accounts = vec![
675 AddressLookupTableAccount {
676 key: Pubkey::new_unique(),
677 addresses: vec![keys[4], keys[5], keys[6]],
678 },
679 AddressLookupTableAccount {
680 key: Pubkey::new_unique(),
681 addresses: vec![],
682 },
683 ];
684
685 let recent_blockhash = Hash::new_unique();
686 assert_eq!(
687 Message::try_compile(
688 &payer,
689 &instructions,
690 &address_lookup_table_accounts,
691 recent_blockhash
692 ),
693 Ok(Message {
694 header: MessageHeader {
695 num_required_signatures: 3,
696 num_readonly_signed_accounts: 1,
697 num_readonly_unsigned_accounts: 1
698 },
699 recent_blockhash,
700 account_keys: vec![keys[0], keys[1], keys[2], keys[3], program_id],
701 instructions: vec![CompiledInstruction {
702 program_id_index: 4,
703 accounts: vec![1, 2, 3, 5, 6],
704 data: vec![],
705 },],
706 address_table_lookups: vec![MessageAddressTableLookup {
707 account_key: address_lookup_table_accounts[0].key,
708 writable_indexes: vec![0],
709 readonly_indexes: vec![1],
710 }],
711 })
712 );
713 }
714
715 #[test]
716 fn test_is_maybe_writable() {
717 let key0 = Pubkey::new_unique();
718 let key1 = Pubkey::new_unique();
719 let key2 = Pubkey::new_unique();
720 let key3 = Pubkey::new_unique();
721 let key4 = Pubkey::new_unique();
722 let key5 = Pubkey::new_unique();
723
724 let message = Message {
725 header: MessageHeader {
726 num_required_signatures: 3,
727 num_readonly_signed_accounts: 2,
728 num_readonly_unsigned_accounts: 1,
729 },
730 account_keys: vec![key0, key1, key2, key3, key4, key5],
731 address_table_lookups: vec![MessageAddressTableLookup {
732 account_key: Pubkey::new_unique(),
733 writable_indexes: vec![0],
734 readonly_indexes: vec![1],
735 }],
736 ..Message::default()
737 };
738
739 let reserved_account_keys = HashSet::from([key3]);
740
741 assert!(message.is_maybe_writable(0, Some(&reserved_account_keys)));
742 assert!(!message.is_maybe_writable(1, Some(&reserved_account_keys)));
743 assert!(!message.is_maybe_writable(2, Some(&reserved_account_keys)));
744 assert!(!message.is_maybe_writable(3, Some(&reserved_account_keys)));
745 assert!(message.is_maybe_writable(3, None));
746 assert!(message.is_maybe_writable(4, Some(&reserved_account_keys)));
747 assert!(!message.is_maybe_writable(5, Some(&reserved_account_keys)));
748 assert!(message.is_maybe_writable(6, Some(&reserved_account_keys)));
749 assert!(!message.is_maybe_writable(7, Some(&reserved_account_keys)));
750 assert!(!message.is_maybe_writable(8, Some(&reserved_account_keys)));
751 }
752
753 #[test]
754 fn test_is_account_maybe_reserved() {
755 let key0 = Pubkey::new_unique();
756 let key1 = Pubkey::new_unique();
757
758 let message = Message {
759 account_keys: vec![key0, key1],
760 address_table_lookups: vec![MessageAddressTableLookup {
761 account_key: Pubkey::new_unique(),
762 writable_indexes: vec![0],
763 readonly_indexes: vec![1],
764 }],
765 ..Message::default()
766 };
767
768 let reserved_account_keys = HashSet::from([key1]);
769
770 assert!(!message.is_account_maybe_reserved(0, Some(&reserved_account_keys)));
771 assert!(message.is_account_maybe_reserved(1, Some(&reserved_account_keys)));
772 assert!(!message.is_account_maybe_reserved(2, Some(&reserved_account_keys)));
773 assert!(!message.is_account_maybe_reserved(3, Some(&reserved_account_keys)));
774 assert!(!message.is_account_maybe_reserved(4, Some(&reserved_account_keys)));
775 assert!(!message.is_account_maybe_reserved(0, None));
776 assert!(!message.is_account_maybe_reserved(1, None));
777 assert!(!message.is_account_maybe_reserved(2, None));
778 assert!(!message.is_account_maybe_reserved(3, None));
779 assert!(!message.is_account_maybe_reserved(4, None));
780 }
781}