1pub use loaded::*;
13#[cfg(feature = "serde")]
14use serde_derive::{Deserialize, Serialize};
15#[cfg(feature = "frozen-abi")]
16use solana_frozen_abi_macro::{frozen_abi, AbiExample, StableAbi, StableAbiSample};
17use {
18 crate::{
19 compiled_instruction::CompiledInstruction,
20 compiled_keys::{CompileError, CompiledKeys},
21 AccountKeys, AddressLookupTableAccount, MessageHeader,
22 },
23 alloc::vec::Vec,
24 solana_address::Address,
25 solana_hash::Hash,
26 solana_instruction::Instruction,
27 solana_sanitize::SanitizeError,
28};
29#[cfg(feature = "std")]
30use {solana_sdk_ids::bpf_loader_upgradeable, std::collections::HashSet};
31#[cfg(feature = "wincode")]
32use {
33 solana_short_vec::ShortU16,
34 wincode::{containers, SchemaRead, SchemaWrite},
35};
36
37mod loaded;
38
39#[cfg_attr(
42 feature = "frozen-abi",
43 derive(AbiExample, StableAbi, StableAbiSample),
44 frozen_abi(
45 abi_digest = "BgfDMK6KNGWbvzMmSAcwMsMoL25jmE7x7CCzCeYtMnqa",
46 abi_serializer = ["bincode", "wincode"],
47 test_roundtrip = "eq_and_wire"
48 )
49)]
50#[cfg_attr(
51 feature = "serde",
52 derive(Deserialize, Serialize),
53 serde(rename_all = "camelCase")
54)]
55#[cfg_attr(feature = "wincode", derive(SchemaWrite, SchemaRead))]
56#[derive(Default, Debug, PartialEq, Eq, Clone)]
57pub struct MessageAddressTableLookup {
58 pub account_key: Address,
60 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
62 #[cfg_attr(feature = "wincode", wincode(with = "containers::Vec<_, ShortU16>"))]
63 pub writable_indexes: Vec<u8>,
64 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
66 #[cfg_attr(feature = "wincode", wincode(with = "containers::Vec<_, ShortU16>"))]
67 pub readonly_indexes: Vec<u8>,
68}
69
70#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
78#[cfg_attr(
79 feature = "serde",
80 derive(Deserialize, Serialize),
81 serde(rename_all = "camelCase")
82)]
83#[cfg_attr(feature = "wincode", derive(SchemaWrite, SchemaRead))]
84#[derive(Default, Debug, PartialEq, Eq, Clone)]
85pub struct Message {
86 pub header: MessageHeader,
90
91 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
93 #[cfg_attr(feature = "wincode", wincode(with = "containers::Vec<_, ShortU16>"))]
94 pub account_keys: Vec<Address>,
95
96 pub recent_blockhash: Hash,
98
99 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
113 #[cfg_attr(feature = "wincode", wincode(with = "containers::Vec<_, ShortU16>"))]
114 pub instructions: Vec<CompiledInstruction>,
115
116 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
119 #[cfg_attr(feature = "wincode", wincode(with = "containers::Vec<_, ShortU16>"))]
120 pub address_table_lookups: Vec<MessageAddressTableLookup>,
121}
122
123impl Message {
124 pub fn sanitize(&self) -> Result<(), SanitizeError> {
126 let num_static_account_keys = self.account_keys.len();
127 if usize::from(self.header.num_required_signatures)
128 .saturating_add(usize::from(self.header.num_readonly_unsigned_accounts))
129 > num_static_account_keys
130 {
131 return Err(SanitizeError::IndexOutOfBounds);
132 }
133
134 if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures {
136 return Err(SanitizeError::InvalidValue);
137 }
138
139 let num_dynamic_account_keys = {
140 let mut total_lookup_keys: usize = 0;
141 for lookup in &self.address_table_lookups {
142 let num_lookup_indexes = lookup
143 .writable_indexes
144 .len()
145 .saturating_add(lookup.readonly_indexes.len());
146
147 if num_lookup_indexes == 0 {
149 return Err(SanitizeError::InvalidValue);
150 }
151
152 total_lookup_keys = total_lookup_keys.saturating_add(num_lookup_indexes);
153 }
154 total_lookup_keys
155 };
156
157 if num_static_account_keys == 0 {
161 return Err(SanitizeError::InvalidValue);
162 }
163
164 let total_account_keys = num_static_account_keys.saturating_add(num_dynamic_account_keys);
169 if total_account_keys > 256 {
170 return Err(SanitizeError::IndexOutOfBounds);
171 }
172
173 let max_account_ix = total_account_keys
176 .checked_sub(1)
177 .expect("message doesn't contain any account keys");
178
179 let max_program_id_ix =
183 num_static_account_keys
186 .checked_sub(1)
187 .expect("message doesn't contain any static account keys");
188
189 for ci in &self.instructions {
190 if usize::from(ci.program_id_index) > max_program_id_ix {
191 return Err(SanitizeError::IndexOutOfBounds);
192 }
193 if ci.program_id_index == 0 {
195 return Err(SanitizeError::IndexOutOfBounds);
196 }
197 for ai in &ci.accounts {
198 if usize::from(*ai) > max_account_ix {
199 return Err(SanitizeError::IndexOutOfBounds);
200 }
201 }
202 }
203
204 Ok(())
205 }
206}
207
208impl Message {
209 pub fn try_compile(
307 payer: &Address,
308 instructions: &[Instruction],
309 address_lookup_table_accounts: &[AddressLookupTableAccount],
310 recent_blockhash: Hash,
311 ) -> Result<Self, CompileError> {
312 let mut compiled_keys = CompiledKeys::compile(instructions, Some(*payer));
313
314 let mut address_table_lookups = Vec::with_capacity(address_lookup_table_accounts.len());
315 let mut loaded_addresses_list = Vec::with_capacity(address_lookup_table_accounts.len());
316 for lookup_table_account in address_lookup_table_accounts {
317 if let Some((lookup, loaded_addresses)) =
318 compiled_keys.try_extract_table_lookup(lookup_table_account)?
319 {
320 address_table_lookups.push(lookup);
321 loaded_addresses_list.push(loaded_addresses);
322 }
323 }
324
325 let (header, static_keys) = compiled_keys.try_into_message_components()?;
326 let dynamic_keys = loaded_addresses_list.into_iter().collect();
327 let account_keys = AccountKeys::new(&static_keys, Some(&dynamic_keys));
328 let instructions = account_keys.try_compile_instructions(instructions)?;
329
330 Ok(Self {
331 header,
332 account_keys: static_keys,
333 recent_blockhash,
334 instructions,
335 address_table_lookups,
336 })
337 }
338
339 #[cfg(feature = "wincode")]
340 pub fn serialize(&self) -> Vec<u8> {
342 wincode::serialize(&(crate::MESSAGE_VERSION_PREFIX, self)).unwrap()
343 }
344
345 pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
347 if let Ok(key_index) = u8::try_from(key_index) {
348 self.instructions
349 .iter()
350 .any(|ix| ix.program_id_index == key_index)
351 } else {
352 false
353 }
354 }
355
356 #[cfg(feature = "std")]
359 fn is_writable_index(&self, key_index: usize) -> bool {
360 let header = &self.header;
361 let num_account_keys = self.account_keys.len();
362 let num_signed_accounts = usize::from(header.num_required_signatures);
363 if key_index >= num_account_keys {
364 let loaded_addresses_index = key_index.saturating_sub(num_account_keys);
365 let num_writable_dynamic_addresses = self
366 .address_table_lookups
367 .iter()
368 .map(|lookup| lookup.writable_indexes.len())
369 .sum();
370 loaded_addresses_index < num_writable_dynamic_addresses
371 } else if key_index >= num_signed_accounts {
372 let num_unsigned_accounts = num_account_keys.saturating_sub(num_signed_accounts);
373 let num_writable_unsigned_accounts = num_unsigned_accounts
374 .saturating_sub(usize::from(header.num_readonly_unsigned_accounts));
375 let unsigned_account_index = key_index.saturating_sub(num_signed_accounts);
376 unsigned_account_index < num_writable_unsigned_accounts
377 } else {
378 let num_writable_signed_accounts = num_signed_accounts
379 .saturating_sub(usize::from(header.num_readonly_signed_accounts));
380 key_index < num_writable_signed_accounts
381 }
382 }
383
384 #[cfg(feature = "std")]
386 fn is_upgradeable_loader_in_static_keys(&self) -> bool {
387 self.account_keys
388 .iter()
389 .any(|&key| key == bpf_loader_upgradeable::id())
390 }
391
392 #[cfg(feature = "std")]
398 pub fn is_maybe_writable(
399 &self,
400 key_index: usize,
401 reserved_account_keys: Option<&HashSet<Address>>,
402 ) -> bool {
403 self.is_writable_index(key_index)
404 && !self.is_account_maybe_reserved(key_index, reserved_account_keys)
405 && !{
406 self.is_key_called_as_program(key_index)
408 && !self.is_upgradeable_loader_in_static_keys()
409 }
410 }
411
412 #[cfg(feature = "std")]
416 fn is_account_maybe_reserved(
417 &self,
418 key_index: usize,
419 reserved_account_keys: Option<&HashSet<Address>>,
420 ) -> bool {
421 let mut is_maybe_reserved = false;
422 if let Some(reserved_account_keys) = reserved_account_keys {
423 if let Some(key) = self.account_keys.get(key_index) {
424 is_maybe_reserved = reserved_account_keys.contains(key);
425 }
426 }
427 is_maybe_reserved
428 }
429}
430
431#[cfg(test)]
432mod tests {
433 use {super::*, crate::VersionedMessage, alloc::vec, solana_instruction::AccountMeta};
434
435 #[test]
436 fn test_sanitize() {
437 assert!(Message {
438 header: MessageHeader {
439 num_required_signatures: 1,
440 ..MessageHeader::default()
441 },
442 account_keys: vec![Address::new_unique()],
443 ..Message::default()
444 }
445 .sanitize()
446 .is_ok());
447 }
448
449 #[test]
450 fn test_sanitize_with_instruction() {
451 assert!(Message {
452 header: MessageHeader {
453 num_required_signatures: 1,
454 ..MessageHeader::default()
455 },
456 account_keys: vec![Address::new_unique(), Address::new_unique()],
457 instructions: vec![CompiledInstruction {
458 program_id_index: 1,
459 accounts: vec![0],
460 data: vec![]
461 }],
462 ..Message::default()
463 }
464 .sanitize()
465 .is_ok());
466 }
467
468 #[test]
469 fn test_sanitize_with_table_lookup() {
470 assert!(Message {
471 header: MessageHeader {
472 num_required_signatures: 1,
473 ..MessageHeader::default()
474 },
475 account_keys: vec![Address::new_unique()],
476 address_table_lookups: vec![MessageAddressTableLookup {
477 account_key: Address::new_unique(),
478 writable_indexes: vec![1, 2, 3],
479 readonly_indexes: vec![0],
480 }],
481 ..Message::default()
482 }
483 .sanitize()
484 .is_ok());
485 }
486
487 #[test]
488 fn test_sanitize_with_table_lookup_and_ix_with_dynamic_program_id() {
489 let message = Message {
490 header: MessageHeader {
491 num_required_signatures: 1,
492 ..MessageHeader::default()
493 },
494 account_keys: vec![Address::new_unique()],
495 address_table_lookups: vec![MessageAddressTableLookup {
496 account_key: Address::new_unique(),
497 writable_indexes: vec![1, 2, 3],
498 readonly_indexes: vec![0],
499 }],
500 instructions: vec![CompiledInstruction {
501 program_id_index: 4,
502 accounts: vec![0, 1, 2, 3],
503 data: vec![],
504 }],
505 ..Message::default()
506 };
507
508 assert!(message.sanitize().is_err());
509 }
510
511 #[test]
512 fn test_sanitize_with_table_lookup_and_ix_with_static_program_id() {
513 assert!(Message {
514 header: MessageHeader {
515 num_required_signatures: 1,
516 ..MessageHeader::default()
517 },
518 account_keys: vec![Address::new_unique(), Address::new_unique()],
519 address_table_lookups: vec![MessageAddressTableLookup {
520 account_key: Address::new_unique(),
521 writable_indexes: vec![1, 2, 3],
522 readonly_indexes: vec![0],
523 }],
524 instructions: vec![CompiledInstruction {
525 program_id_index: 1,
526 accounts: vec![2, 3, 4, 5],
527 data: vec![]
528 }],
529 ..Message::default()
530 }
531 .sanitize()
532 .is_ok());
533 }
534
535 #[test]
536 fn test_sanitize_without_signer() {
537 assert!(Message {
538 header: MessageHeader::default(),
539 account_keys: vec![Address::new_unique()],
540 ..Message::default()
541 }
542 .sanitize()
543 .is_err());
544 }
545
546 #[test]
547 fn test_sanitize_without_writable_signer() {
548 assert!(Message {
549 header: MessageHeader {
550 num_required_signatures: 1,
551 num_readonly_signed_accounts: 1,
552 ..MessageHeader::default()
553 },
554 account_keys: vec![Address::new_unique()],
555 ..Message::default()
556 }
557 .sanitize()
558 .is_err());
559 }
560
561 #[test]
562 fn test_sanitize_with_empty_table_lookup() {
563 assert!(Message {
564 header: MessageHeader {
565 num_required_signatures: 1,
566 ..MessageHeader::default()
567 },
568 account_keys: vec![Address::new_unique()],
569 address_table_lookups: vec![MessageAddressTableLookup {
570 account_key: Address::new_unique(),
571 writable_indexes: vec![],
572 readonly_indexes: vec![],
573 }],
574 ..Message::default()
575 }
576 .sanitize()
577 .is_err());
578 }
579
580 #[test]
581 fn test_sanitize_with_max_account_keys() {
582 assert!(Message {
583 header: MessageHeader {
584 num_required_signatures: 1,
585 ..MessageHeader::default()
586 },
587 account_keys: (0..=u8::MAX).map(|_| Address::new_unique()).collect(),
588 ..Message::default()
589 }
590 .sanitize()
591 .is_ok());
592 }
593
594 #[test]
595 fn test_sanitize_with_too_many_account_keys() {
596 assert!(Message {
597 header: MessageHeader {
598 num_required_signatures: 1,
599 ..MessageHeader::default()
600 },
601 account_keys: (0..=256).map(|_| Address::new_unique()).collect(),
602 ..Message::default()
603 }
604 .sanitize()
605 .is_err());
606 }
607
608 #[test]
609 fn test_sanitize_with_max_table_loaded_keys() {
610 assert!(Message {
611 header: MessageHeader {
612 num_required_signatures: 1,
613 ..MessageHeader::default()
614 },
615 account_keys: vec![Address::new_unique()],
616 address_table_lookups: vec![MessageAddressTableLookup {
617 account_key: Address::new_unique(),
618 writable_indexes: (0..=254).step_by(2).collect(),
619 readonly_indexes: (1..=254).step_by(2).collect(),
620 }],
621 ..Message::default()
622 }
623 .sanitize()
624 .is_ok());
625 }
626
627 #[test]
628 fn test_sanitize_with_too_many_table_loaded_keys() {
629 assert!(Message {
630 header: MessageHeader {
631 num_required_signatures: 1,
632 ..MessageHeader::default()
633 },
634 account_keys: vec![Address::new_unique()],
635 address_table_lookups: vec![MessageAddressTableLookup {
636 account_key: Address::new_unique(),
637 writable_indexes: (0..=255).step_by(2).collect(),
638 readonly_indexes: (1..=255).step_by(2).collect(),
639 }],
640 ..Message::default()
641 }
642 .sanitize()
643 .is_err());
644 }
645
646 #[test]
647 fn test_sanitize_with_invalid_ix_program_id() {
648 let message = Message {
649 header: MessageHeader {
650 num_required_signatures: 1,
651 ..MessageHeader::default()
652 },
653 account_keys: vec![Address::new_unique()],
654 address_table_lookups: vec![MessageAddressTableLookup {
655 account_key: Address::new_unique(),
656 writable_indexes: vec![0],
657 readonly_indexes: vec![],
658 }],
659 instructions: vec![CompiledInstruction {
660 program_id_index: 2,
661 accounts: vec![],
662 data: vec![],
663 }],
664 ..Message::default()
665 };
666
667 assert!(message.sanitize().is_err());
668 }
669
670 #[test]
671 fn test_sanitize_with_invalid_ix_account() {
672 assert!(Message {
673 header: MessageHeader {
674 num_required_signatures: 1,
675 ..MessageHeader::default()
676 },
677 account_keys: vec![Address::new_unique(), Address::new_unique()],
678 address_table_lookups: vec![MessageAddressTableLookup {
679 account_key: Address::new_unique(),
680 writable_indexes: vec![],
681 readonly_indexes: vec![0],
682 }],
683 instructions: vec![CompiledInstruction {
684 program_id_index: 1,
685 accounts: vec![3],
686 data: vec![]
687 }],
688 ..Message::default()
689 }
690 .sanitize()
691 .is_err());
692 }
693
694 #[test]
695 fn test_serialize() {
696 let message = Message::default();
697 let versioned_msg = VersionedMessage::V0(message.clone());
698 assert_eq!(message.serialize(), versioned_msg.serialize());
699 }
700
701 #[test]
702 fn test_try_compile() {
703 let mut keys = vec![];
704 keys.resize_with(7, Address::new_unique);
705
706 let payer = keys[0];
707 let program_id = keys[6];
708 let instructions = vec![Instruction {
709 program_id,
710 accounts: vec![
711 AccountMeta::new(keys[1], true),
712 AccountMeta::new_readonly(keys[2], true),
713 AccountMeta::new(keys[3], false),
714 AccountMeta::new(keys[4], false), AccountMeta::new_readonly(keys[5], false), ],
717 data: vec![],
718 }];
719 let address_lookup_table_accounts = vec![
720 AddressLookupTableAccount {
721 key: Address::new_unique(),
722 addresses: vec![keys[4], keys[5], keys[6]],
723 },
724 AddressLookupTableAccount {
725 key: Address::new_unique(),
726 addresses: vec![],
727 },
728 ];
729
730 let recent_blockhash = Hash::new_unique();
731 assert_eq!(
732 Message::try_compile(
733 &payer,
734 &instructions,
735 &address_lookup_table_accounts,
736 recent_blockhash
737 ),
738 Ok(Message {
739 header: MessageHeader {
740 num_required_signatures: 3,
741 num_readonly_signed_accounts: 1,
742 num_readonly_unsigned_accounts: 1
743 },
744 recent_blockhash,
745 account_keys: vec![keys[0], keys[1], keys[2], keys[3], program_id],
746 instructions: vec![CompiledInstruction {
747 program_id_index: 4,
748 accounts: vec![1, 2, 3, 5, 6],
749 data: vec![],
750 },],
751 address_table_lookups: vec![MessageAddressTableLookup {
752 account_key: address_lookup_table_accounts[0].key,
753 writable_indexes: vec![0],
754 readonly_indexes: vec![1],
755 }],
756 })
757 );
758 }
759
760 #[test]
761 fn test_is_maybe_writable() {
762 let key0 = Address::new_unique();
763 let key1 = Address::new_unique();
764 let key2 = Address::new_unique();
765 let key3 = Address::new_unique();
766 let key4 = Address::new_unique();
767 let key5 = Address::new_unique();
768
769 let message = Message {
770 header: MessageHeader {
771 num_required_signatures: 3,
772 num_readonly_signed_accounts: 2,
773 num_readonly_unsigned_accounts: 1,
774 },
775 account_keys: vec![key0, key1, key2, key3, key4, key5],
776 address_table_lookups: vec![MessageAddressTableLookup {
777 account_key: Address::new_unique(),
778 writable_indexes: vec![0],
779 readonly_indexes: vec![1],
780 }],
781 ..Message::default()
782 };
783
784 let reserved_account_keys = HashSet::from([key3]);
785
786 assert!(message.is_maybe_writable(0, Some(&reserved_account_keys)));
787 assert!(!message.is_maybe_writable(1, Some(&reserved_account_keys)));
788 assert!(!message.is_maybe_writable(2, Some(&reserved_account_keys)));
789 assert!(!message.is_maybe_writable(3, Some(&reserved_account_keys)));
790 assert!(message.is_maybe_writable(3, None));
791 assert!(message.is_maybe_writable(4, Some(&reserved_account_keys)));
792 assert!(!message.is_maybe_writable(5, Some(&reserved_account_keys)));
793 assert!(message.is_maybe_writable(6, Some(&reserved_account_keys)));
794 assert!(!message.is_maybe_writable(7, Some(&reserved_account_keys)));
795 assert!(!message.is_maybe_writable(8, Some(&reserved_account_keys)));
796 }
797
798 #[test]
799 fn test_is_account_maybe_reserved() {
800 let key0 = Address::new_unique();
801 let key1 = Address::new_unique();
802
803 let message = Message {
804 account_keys: vec![key0, key1],
805 address_table_lookups: vec![MessageAddressTableLookup {
806 account_key: Address::new_unique(),
807 writable_indexes: vec![0],
808 readonly_indexes: vec![1],
809 }],
810 ..Message::default()
811 };
812
813 let reserved_account_keys = HashSet::from([key1]);
814
815 assert!(!message.is_account_maybe_reserved(0, Some(&reserved_account_keys)));
816 assert!(message.is_account_maybe_reserved(1, Some(&reserved_account_keys)));
817 assert!(!message.is_account_maybe_reserved(2, Some(&reserved_account_keys)));
818 assert!(!message.is_account_maybe_reserved(3, Some(&reserved_account_keys)));
819 assert!(!message.is_account_maybe_reserved(4, Some(&reserved_account_keys)));
820 assert!(!message.is_account_maybe_reserved(0, None));
821 assert!(!message.is_account_maybe_reserved(1, None));
822 assert!(!message.is_account_maybe_reserved(2, None));
823 assert!(!message.is_account_maybe_reserved(3, None));
824 assert!(!message.is_account_maybe_reserved(4, None));
825 }
826}