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