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