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