1#[cfg(test)]
38pub use self::tests::MessageBuilder;
39#[cfg(feature = "serde")]
40use serde_derive::{Deserialize, Serialize};
41#[cfg(feature = "frozen-abi")]
42use solana_frozen_abi_macro::{AbiExample, StableAbi, StableAbiSample};
43#[cfg(feature = "std")]
44use std::collections::HashSet;
45#[cfg(feature = "wincode")]
46use {
47 crate::v1::{WireInstructionHeader, FIXED_HEADER_SIZE},
48 core::{mem::MaybeUninit, slice::from_raw_parts},
49 wincode::{
50 config::{Config, ConfigCore},
51 context,
52 io::{Reader, Writer},
53 len::SeqLen,
54 ReadResult, SchemaRead, SchemaReadContext, SchemaWrite, WriteResult,
55 },
56};
57use {
58 crate::{
59 compiled_instruction::CompiledInstruction,
60 compiled_keys::CompiledKeys,
61 v1::{
62 MessageError, TransactionConfig, TransactionConfigMask, MAX_ADDRESSES, MAX_HEAP_SIZE,
63 MAX_INSTRUCTIONS, MAX_SIGNATURES, MIN_HEAP_SIZE,
64 },
65 AccountKeys, AddressSet, CompileError, MessageHeader,
66 },
67 alloc::{collections::BTreeSet, vec::Vec},
68 core::mem::size_of,
69 solana_address::Address,
70 solana_hash::Hash,
71 solana_instruction::Instruction,
72 solana_sanitize::{Sanitize, SanitizeError},
73};
74
75#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
82#[cfg_attr(
83 feature = "serde",
84 derive(Serialize, Deserialize),
85 serde(rename_all = "camelCase")
86)]
87#[derive(Debug, Clone, PartialEq, Eq, Default)]
88pub struct Message {
89 pub header: MessageHeader,
91
92 pub config: TransactionConfig,
94
95 pub lifetime_specifier: Hash,
97
98 pub account_keys: Vec<Address>,
117
118 pub instructions: Vec<CompiledInstruction>,
120}
121
122impl Message {
123 pub fn new(
125 header: MessageHeader,
126 config: TransactionConfig,
127 lifetime_specifier: Hash,
128 account_keys: Vec<Address>,
129 instructions: Vec<CompiledInstruction>,
130 ) -> Self {
131 Self {
132 header,
133 config,
134 lifetime_specifier,
135 account_keys,
136 instructions,
137 }
138 }
139
140 pub fn try_compile(
216 payer: &Address,
217 instructions: &[Instruction],
218 recent_blockhash: Hash,
219 ) -> Result<Self, CompileError> {
220 Self::try_compile_with_config(
221 payer,
222 instructions,
223 recent_blockhash,
224 TransactionConfig::empty(),
225 )
226 }
227
228 pub fn try_compile_with_config(
305 payer: &Address,
306 instructions: &[Instruction],
307 recent_blockhash: Hash,
308 config: TransactionConfig,
309 ) -> Result<Self, CompileError> {
310 let compiled_keys = CompiledKeys::compile(instructions, Some(*payer));
311 let (header, static_keys) = compiled_keys.try_into_message_components()?;
312
313 let account_keys = AccountKeys::new(&static_keys, None);
314 let instructions = account_keys.try_compile_instructions(instructions)?;
315
316 Ok(Self {
317 header,
318 config,
319 lifetime_specifier: recent_blockhash,
320 account_keys: static_keys,
321 instructions,
322 })
323 }
324
325 pub fn fee_payer(&self) -> Option<&Address> {
327 self.account_keys.first()
328 }
329
330 pub fn is_signer(&self, index: usize) -> bool {
333 index < usize::from(self.header.num_required_signatures)
334 }
335
336 pub fn is_signer_writable(&self, index: usize) -> bool {
338 if !self.is_signer(index) {
339 return false;
340 }
341 let num_writable_signers = usize::from(self.header.num_required_signatures)
344 .saturating_sub(usize::from(self.header.num_readonly_signed_accounts));
345 index < num_writable_signers
346 }
347
348 pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
350 crate::is_key_called_as_program(&self.instructions, key_index)
351 }
352
353 #[inline(always)]
358 #[cfg(feature = "std")]
359 pub(crate) fn is_writable_index(&self, i: usize) -> bool {
360 crate::is_writable_index(i, self.header, &self.account_keys)
361 }
362
363 pub fn is_upgradeable_loader_present(&self) -> bool {
365 crate::is_upgradeable_loader_present(&self.account_keys)
366 }
367
368 #[cfg(feature = "std")]
383 #[deprecated(
384 since = "4.4.0",
385 note = "Use `is_maybe_writable_with_reserved_addresses` instead"
386 )]
387 pub fn is_maybe_writable(
388 &self,
389 key_index: usize,
390 reserved_account_keys: Option<&HashSet<Address>>,
391 ) -> bool {
392 self.is_maybe_writable_with_reserved_addresses(key_index, reserved_account_keys)
393 }
394
395 pub fn is_maybe_writable_with_reserved_addresses(
410 &self,
411 key_index: usize,
412 reserved_addresses: Option<&impl AddressSet>,
413 ) -> bool {
414 crate::is_maybe_writable(
415 key_index,
416 self.header,
417 &self.account_keys,
418 &self.instructions,
419 reserved_addresses,
420 )
421 }
422
423 pub fn demote_program_id(&self, i: usize) -> bool {
424 crate::is_program_id_write_demoted(i, &self.account_keys, &self.instructions)
425 }
426
427 #[cfg(feature = "wincode")]
429 pub fn serialize(&self) -> Vec<u8> {
430 wincode::serialize(&(crate::v1::V1_PREFIX, self)).unwrap()
431 }
432
433 #[allow(clippy::arithmetic_side_effects)]
435 #[inline(always)]
436 pub fn size(&self) -> usize {
437 size_of::<MessageHeader>() + size_of::<TransactionConfigMask>() + size_of::<Hash>() + size_of::<u8>() + size_of::<u8>() + self.account_keys.len() * size_of::<Address>() + self.config.size() + self.instructions.len()
445 * (
446 size_of::<u8>()
447 + size_of::<u8>()
448 + size_of::<u16>()
449 ) + self
451 .instructions
452 .iter()
453 .map(|ix| {
454 (ix.accounts.len() * size_of::<u8>())
455 + ix.data.len()
456 })
457 .sum::<usize>() }
459
460 pub fn validate(&self) -> Result<(), MessageError> {
461 if self.header.num_required_signatures > MAX_SIGNATURES {
463 return Err(MessageError::TooManySignatures);
464 }
465
466 if self.instructions.len() > MAX_INSTRUCTIONS as usize {
468 return Err(MessageError::TooManyInstructions);
469 }
470
471 let num_account_keys = self.account_keys.len();
472
473 if num_account_keys > MAX_ADDRESSES as usize {
475 return Err(MessageError::TooManyAddresses);
476 }
477
478 let min_accounts = usize::from(self.header.num_required_signatures)
480 .saturating_add(usize::from(self.header.num_readonly_unsigned_accounts));
481
482 if num_account_keys < min_accounts {
483 return Err(MessageError::NotEnoughAddressesForSignatures);
484 }
485
486 if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures {
488 return Err(MessageError::ZeroSigners);
489 }
490
491 let unique_keys: BTreeSet<_> = self.account_keys.iter().collect();
493 if unique_keys.len() != num_account_keys {
494 return Err(MessageError::DuplicateAddresses);
495 }
496
497 if let Some(heap_size) = self.config.heap_size {
503 if !heap_size.is_multiple_of(1024) {
504 return Err(MessageError::InvalidHeapSize);
505 }
506
507 if !(MIN_HEAP_SIZE..=MAX_HEAP_SIZE).contains(&heap_size) {
508 return Err(MessageError::InvalidHeapSize);
509 }
510 }
511
512 let max_account_index = num_account_keys
514 .checked_sub(1)
515 .ok_or(MessageError::NotEnoughAccountKeys)?;
516
517 for instruction in &self.instructions {
518 if usize::from(instruction.program_id_index) > max_account_index {
520 return Err(MessageError::InvalidInstructionAccountIndex);
521 }
522
523 if instruction.program_id_index == 0 {
525 return Err(MessageError::InvalidInstructionAccountIndex);
526 }
527
528 if instruction.accounts.len() > u8::MAX as usize {
530 return Err(MessageError::InstructionAccountsTooLarge);
531 }
532
533 if instruction.data.len() > u16::MAX as usize {
535 return Err(MessageError::InstructionDataTooLarge);
536 }
537
538 for &account_index in &instruction.accounts {
540 if usize::from(account_index) > max_account_index {
541 return Err(MessageError::InvalidInstructionAccountIndex);
542 }
543 }
544 }
545
546 Ok(())
547 }
548}
549
550impl Sanitize for Message {
551 fn sanitize(&self) -> Result<(), SanitizeError> {
552 Ok(self.validate()?)
553 }
554}
555
556#[cfg(feature = "wincode")]
557unsafe impl<C: ConfigCore> SchemaWrite<C> for Message {
558 type Src = Self;
559
560 #[inline(always)]
561 fn size_of(src: &Self::Src) -> WriteResult<usize> {
562 Ok(src.size())
563 }
564
565 fn write(mut writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
566 let mut writer = unsafe { writer.as_trusted_for(src.size()) }?;
568 writer.write(&[
569 src.header.num_required_signatures,
570 src.header.num_readonly_signed_accounts,
571 src.header.num_readonly_unsigned_accounts,
572 ])?;
573 let mask = TransactionConfigMask::from(&src.config).0.to_le_bytes();
574 writer.write(&mask)?;
575 writer.write(src.lifetime_specifier.as_bytes())?;
576 writer.write(&[src.instructions.len() as u8, src.account_keys.len() as u8])?;
577
578 #[expect(clippy::arithmetic_side_effects)]
581 let account_keys = unsafe {
582 from_raw_parts(
583 src.account_keys.as_ptr().cast::<u8>(),
584 src.account_keys.len() * size_of::<Address>(),
585 )
586 };
587 writer.write(account_keys)?;
588
589 if let Some(value) = src.config.priority_fee {
590 writer.write(&value.to_le_bytes())?;
591 }
592 if let Some(value) = src.config.compute_unit_limit {
593 writer.write(&value.to_le_bytes())?;
594 }
595 if let Some(value) = src.config.loaded_accounts_data_size_limit {
596 writer.write(&value.to_le_bytes())?;
597 }
598 if let Some(value) = src.config.heap_size {
599 writer.write(&value.to_le_bytes())?;
600 }
601
602 for ix in &src.instructions {
603 writer.write(&[ix.program_id_index, ix.accounts.len() as u8])?;
604 writer.write(&(ix.data.len() as u16).to_le_bytes())?;
605 }
606
607 for ix in &src.instructions {
608 writer.write(&ix.accounts)?;
609 writer.write(&ix.data)?;
610 }
611
612 writer.finish()?;
613
614 Ok(())
615 }
616}
617
618#[cfg(feature = "wincode")]
620#[inline]
621#[deprecated(since = "4.1.2", note = "use `Message::serialize` instead")]
622pub fn serialize(message: &Message) -> Vec<u8> {
623 wincode::serialize(message).unwrap()
624}
625
626#[cfg(feature = "wincode")]
627unsafe impl<'de, C: Config> SchemaRead<'de, C> for Message {
628 type Dst = Message;
629
630 #[expect(clippy::arithmetic_side_effects)]
631 fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
632 let (header, lifetime_specifier, config_mask, num_instructions, num_addresses) = {
633 let mut reader = unsafe { reader.as_trusted_for(FIXED_HEADER_SIZE)? };
640 let header = <MessageHeader as SchemaRead<C>>::get(reader.by_ref())?;
641 let config_mask = TransactionConfigMask(u32::from_le_bytes(reader.take_array()?));
642 let lifetime_specifier = <Hash as SchemaRead<C>>::get(reader.by_ref())?;
643 let num_instructions = reader.take_byte()? as usize;
644 let num_addresses = reader.take_byte()? as usize;
645 (
646 header,
647 lifetime_specifier,
648 config_mask,
649 num_instructions,
650 num_addresses,
651 )
652 };
653
654 if config_mask.has_unknown_bits() || config_mask.has_invalid_priority_fee_bits() {
659 return Err(wincode::error::invalid_value(
660 "invalid transaction config mask",
661 ));
662 }
663
664 <C::LengthEncoding as SeqLen<C>>::prealloc_check::<Address>(num_addresses)?;
665 let account_keys = <Vec<Address> as SchemaReadContext<C, context::Len>>::get_with_context(
666 context::Len(num_addresses),
667 reader.by_ref(),
668 )?;
669
670 let mut config = TransactionConfig::empty();
671 if config_mask.has_priority_fee() {
672 config.priority_fee = Some(u64::from_le_bytes(reader.take_array()?));
673 }
674 if config_mask.has_compute_unit_limit() {
675 config.compute_unit_limit = Some(u32::from_le_bytes(reader.take_array()?));
676 }
677 if config_mask.has_loaded_accounts_data_size() {
678 config.loaded_accounts_data_size_limit = Some(u32::from_le_bytes(reader.take_array()?));
679 }
680 if config_mask.has_heap_size() {
681 config.heap_size = Some(u32::from_le_bytes(reader.take_array()?));
682 }
683
684 let instruction_headers = unsafe {
692 from_raw_parts(
693 reader
694 .take_borrowed(num_instructions * size_of::<WireInstructionHeader>())?
695 .as_ptr() as *const WireInstructionHeader,
696 num_instructions,
697 )
698 };
699 let mut instructions = Vec::with_capacity(num_instructions);
700 for header in instruction_headers {
701 let program_id_index = header.program_id_index;
702 let num_accounts = header.num_accounts as usize;
703 let data_len = u16::from_le_bytes(header.data_len) as usize;
704
705 <C::LengthEncoding as SeqLen<C>>::prealloc_check::<u8>(num_accounts)?;
706 let accounts = <Vec<u8> as SchemaReadContext<C, context::Len>>::get_with_context(
707 context::Len(num_accounts),
708 reader.by_ref(),
709 )?;
710 <C::LengthEncoding as SeqLen<C>>::prealloc_check::<u8>(data_len)?;
711 let data = <Vec<u8> as SchemaReadContext<C, context::Len>>::get_with_context(
712 context::Len(data_len),
713 reader.by_ref(),
714 )?;
715
716 instructions.push(CompiledInstruction {
717 program_id_index,
718 accounts,
719 data,
720 });
721 }
722
723 dst.write(Message {
724 header,
725 lifetime_specifier,
726 config,
727 account_keys,
728 instructions,
729 });
730
731 Ok(())
732 }
733}
734
735#[cfg(feature = "wincode")]
738#[inline]
739pub fn deserialize(input: &[u8]) -> wincode::ReadResult<Message> {
740 wincode::deserialize(input)
741}
742
743#[cfg(test)]
744mod tests {
745 use {super::*, alloc::vec, solana_sdk_ids::bpf_loader_upgradeable};
746
747 #[derive(Debug, Clone, Default)]
753 pub struct MessageBuilder {
754 header: MessageHeader,
755 config: TransactionConfig,
756 lifetime_specifier: Option<Hash>,
757 account_keys: Vec<Address>,
758 instructions: Vec<CompiledInstruction>,
759 }
760
761 impl MessageBuilder {
762 pub fn new() -> Self {
763 Self::default()
764 }
765
766 #[must_use]
767 pub fn required_signatures(mut self, count: u8) -> Self {
768 self.header.num_required_signatures = count;
769 self
770 }
771
772 #[must_use]
773 pub fn readonly_signed_accounts(mut self, count: u8) -> Self {
774 self.header.num_readonly_signed_accounts = count;
775 self
776 }
777
778 #[must_use]
779 pub fn readonly_unsigned_accounts(mut self, count: u8) -> Self {
780 self.header.num_readonly_unsigned_accounts = count;
781 self
782 }
783
784 #[must_use]
785 pub fn lifetime_specifier(mut self, hash: Hash) -> Self {
786 self.lifetime_specifier = Some(hash);
787 self
788 }
789
790 #[must_use]
791 pub fn config(mut self, config: TransactionConfig) -> Self {
792 self.config = config;
793 self
794 }
795
796 #[must_use]
797 pub fn priority_fee(mut self, fee: u64) -> Self {
798 self.config.priority_fee = Some(fee);
799 self
800 }
801
802 #[must_use]
803 pub fn compute_unit_limit(mut self, limit: u32) -> Self {
804 self.config.compute_unit_limit = Some(limit);
805 self
806 }
807
808 #[must_use]
809 pub fn loaded_accounts_data_size_limit(mut self, limit: u32) -> Self {
810 self.config.loaded_accounts_data_size_limit = Some(limit);
811 self
812 }
813
814 #[must_use]
815 pub fn heap_size(mut self, size: u32) -> Self {
816 self.config.heap_size = Some(size);
817 self
818 }
819
820 #[must_use]
821 pub fn account(mut self, key: Address) -> Self {
822 self.account_keys.push(key);
823 self
824 }
825
826 #[must_use]
827 pub fn accounts(mut self, keys: Vec<Address>) -> Self {
828 self.account_keys = keys;
829 self
830 }
831
832 #[must_use]
833 pub fn instruction(mut self, instruction: CompiledInstruction) -> Self {
834 self.instructions.push(instruction);
835 self
836 }
837
838 #[must_use]
839 pub fn instructions(mut self, instructions: Vec<CompiledInstruction>) -> Self {
840 self.instructions = instructions;
841 self
842 }
843
844 pub fn build(self) -> Result<Message, MessageError> {
846 let lifetime_specifier = self
847 .lifetime_specifier
848 .ok_or(MessageError::MissingLifetimeSpecifier)?;
849
850 let message = Message::new(
851 self.header,
852 self.config,
853 lifetime_specifier,
854 self.account_keys,
855 self.instructions,
856 );
857
858 message.validate()?;
859
860 Ok(message)
861 }
862 }
863
864 fn create_test_message() -> Message {
865 MessageBuilder::new()
866 .required_signatures(1)
867 .readonly_unsigned_accounts(1)
868 .lifetime_specifier(Hash::new_unique())
869 .accounts(vec![
870 Address::new_unique(), Address::new_unique(), Address::new_unique(), ])
874 .compute_unit_limit(200_000)
875 .instruction(CompiledInstruction {
876 program_id_index: 1,
877 accounts: vec![0, 2],
878 data: vec![1, 2, 3, 4],
879 })
880 .build()
881 .unwrap()
882 }
883
884 #[test]
885 fn fee_payer_returns_first_account() {
886 let fee_payer = Address::new_unique();
887 let message = MessageBuilder::new()
888 .required_signatures(1)
889 .lifetime_specifier(Hash::new_unique())
890 .accounts(vec![fee_payer, Address::new_unique()])
891 .build()
892 .unwrap();
893
894 assert_eq!(message.fee_payer(), Some(&fee_payer));
895 }
896
897 #[test]
898 fn fee_payer_returns_none_for_empty_accounts() {
899 let message = Message::new(
901 MessageHeader::default(),
902 TransactionConfig::default(),
903 Hash::new_unique(),
904 vec![],
905 vec![],
906 );
907
908 assert_eq!(message.fee_payer(), None);
909 }
910
911 #[test]
912 fn is_signer_checks_signature_requirement() {
913 let message = create_test_message();
914 assert!(message.is_signer(0)); assert!(!message.is_signer(1)); assert!(!message.is_signer(2)); }
918
919 #[test]
920 fn is_signer_writable_identifies_writable_signers() {
921 let message = MessageBuilder::new()
922 .required_signatures(3)
923 .readonly_signed_accounts(1) .lifetime_specifier(Hash::new_unique())
925 .accounts(vec![
926 Address::new_unique(), Address::new_unique(), Address::new_unique(), Address::new_unique(), ])
931 .build()
932 .unwrap();
933
934 assert!(message.is_signer_writable(0));
936 assert!(message.is_signer_writable(1));
937 assert!(!message.is_signer_writable(2));
939 assert!(!message.is_signer_writable(3));
941 assert!(!message.is_signer_writable(100));
942 }
943
944 #[test]
945 fn is_signer_writable_all_writable_when_no_readonly() {
946 let message = MessageBuilder::new()
947 .required_signatures(2)
948 .readonly_signed_accounts(0) .lifetime_specifier(Hash::new_unique())
950 .accounts(vec![
951 Address::new_unique(),
952 Address::new_unique(),
953 Address::new_unique(),
954 ])
955 .build()
956 .unwrap();
957
958 assert!(message.is_signer_writable(0));
959 assert!(message.is_signer_writable(1));
960 assert!(!message.is_signer_writable(2)); }
962
963 #[test]
964 fn is_key_called_as_program_detects_program_indices() {
965 let message = create_test_message();
966 assert!(message.is_key_called_as_program(1));
968 assert!(!message.is_key_called_as_program(0));
969 assert!(!message.is_key_called_as_program(2));
970 assert!(!message.is_key_called_as_program(256));
972 assert!(!message.is_key_called_as_program(10_000));
973 }
974
975 #[test]
976 fn is_upgradeable_loader_present_detects_loader() {
977 let message = create_test_message();
978 assert!(!message.is_upgradeable_loader_present());
979
980 let mut message_with_loader = create_test_message();
981 message_with_loader
982 .account_keys
983 .push(bpf_loader_upgradeable::id());
984 assert!(message_with_loader.is_upgradeable_loader_present());
985 }
986
987 #[test]
988 fn is_writable_index_respects_header_layout() {
989 let message = create_test_message();
990 assert!(message.is_writable_index(0)); assert!(message.is_writable_index(1)); assert!(!message.is_writable_index(2)); }
995
996 #[test]
997 fn is_writable_index_handles_mixed_signer_permissions() {
998 let mut message = create_test_message();
999 message.header.num_required_signatures = 2;
1001 message.header.num_readonly_signed_accounts = 1;
1002 message.header.num_readonly_unsigned_accounts = 1;
1003 message.account_keys = vec![
1004 Address::new_unique(), Address::new_unique(), Address::new_unique(), ];
1008 message.instructions[0].program_id_index = 2;
1009 message.instructions[0].accounts = vec![0, 1];
1010
1011 assert!(message.sanitize().is_ok());
1012 assert!(message.is_writable_index(0)); assert!(!message.is_writable_index(1)); assert!(!message.is_writable_index(2)); assert!(!message.is_writable_index(999)); }
1017
1018 #[test]
1019 #[allow(deprecated)]
1020 fn is_maybe_writable_returns_false_for_readonly_index() {
1021 let message = create_test_message();
1022 assert!(!message.is_writable_index(2));
1024 assert!(!message.is_maybe_writable(2, None));
1025 assert!(!message.is_maybe_writable(2, Some(&HashSet::new())));
1027 }
1028
1029 #[test]
1030 #[allow(deprecated)]
1031 fn is_maybe_writable_demotes_reserved_accounts() {
1032 let message = create_test_message();
1033 let reserved = HashSet::from([message.account_keys[0]]);
1034 assert!(message.is_writable_index(0));
1036 assert!(!message.is_maybe_writable(0, Some(&reserved)));
1037 }
1038
1039 #[test]
1040 #[allow(deprecated)]
1041 fn is_maybe_writable_demotes_programs_without_upgradeable_loader() {
1042 let message = create_test_message();
1043 assert!(message.is_writable_index(1));
1045 assert!(message.is_key_called_as_program(1));
1046 assert!(!message.is_upgradeable_loader_present());
1047 assert!(!message.is_maybe_writable(1, None));
1048 }
1049
1050 #[test]
1051 #[allow(deprecated)]
1052 fn is_maybe_writable_preserves_programs_with_upgradeable_loader() {
1053 let mut message = create_test_message();
1054 message.account_keys.push(bpf_loader_upgradeable::id());
1056
1057 assert!(message.sanitize().is_ok());
1058 assert!(message.is_writable_index(1));
1059 assert!(message.is_key_called_as_program(1));
1060 assert!(message.is_upgradeable_loader_present());
1061 assert!(message.is_maybe_writable(1, None));
1063 }
1064
1065 #[test]
1066 fn sanitize_accepts_valid_message() {
1067 let message = create_test_message();
1068 assert!(message.sanitize().is_ok());
1069 }
1070
1071 #[test]
1072 fn sanitize_rejects_zero_signers() {
1073 let mut message = create_test_message();
1074 message.header.num_required_signatures = 0;
1075 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1076 }
1077
1078 #[test]
1079 fn sanitize_rejects_over_12_signatures() {
1080 let mut message = create_test_message();
1081 message.header.num_required_signatures = MAX_SIGNATURES + 1;
1082 message.account_keys = (0..MAX_SIGNATURES + 1)
1083 .map(|_| Address::new_unique())
1084 .collect();
1085 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1086 }
1087
1088 #[test]
1089 fn sanitize_rejects_over_64_addresses() {
1090 let mut message = create_test_message();
1091 message.account_keys = (0..65).map(|_| Address::new_unique()).collect();
1092 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1093 }
1094
1095 #[test]
1096 fn sanitize_rejects_over_64_instructions() {
1097 let mut message = create_test_message();
1098 message.instructions = (0..65) .map(|_| CompiledInstruction {
1100 program_id_index: 1,
1101 accounts: vec![0],
1102 data: vec![],
1103 })
1104 .collect();
1105 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1106 }
1107
1108 #[test]
1109 fn sanitize_rejects_insufficient_accounts_for_header() {
1110 let mut message = create_test_message();
1111 message.header.num_readonly_unsigned_accounts = 10;
1114 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1115 }
1116
1117 #[test]
1118 fn sanitize_rejects_all_signers_readonly() {
1119 let mut message = create_test_message();
1120 message.header.num_readonly_signed_accounts = 1; assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1122 }
1123
1124 #[test]
1125 fn sanitize_rejects_duplicate_addresses() {
1126 let mut message = create_test_message();
1127 let dup = message.account_keys[0];
1128 message.account_keys[1] = dup;
1129 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1130 }
1131
1132 #[test]
1133 fn sanitize_rejects_unaligned_heap_size() {
1134 let mut message = create_test_message();
1135 message.config.heap_size = Some(1025); assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1137 }
1138
1139 #[test]
1140 fn sanitize_rejects_heap_size_below_minimum() {
1141 let mut message = create_test_message();
1142 message.config.heap_size = Some(MIN_HEAP_SIZE - 1);
1143 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1144 }
1145
1146 #[test]
1147 fn sanitize_rejects_heap_size_above_maximum() {
1148 let mut message = create_test_message();
1149 message.config.heap_size = Some(MAX_HEAP_SIZE + 1);
1150 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1151 }
1152
1153 #[test]
1154 fn sanitize_accepts_minimum_heap_size() {
1155 let mut message = create_test_message();
1156 message.config.heap_size = Some(MIN_HEAP_SIZE);
1157 assert!(message.sanitize().is_ok());
1158 }
1159
1160 #[test]
1161 fn sanitize_accepts_maximum_heap_size() {
1162 let mut message = create_test_message();
1163 message.config.heap_size = Some(MAX_HEAP_SIZE);
1164 assert!(message.sanitize().is_ok());
1165 }
1166
1167 #[test]
1168 fn sanitize_accepts_aligned_heap_size() {
1169 let mut message = create_test_message();
1170 message.config.heap_size = Some(65536); assert!(message.sanitize().is_ok());
1172 }
1173
1174 #[test]
1175 fn sanitize_rejects_invalid_program_id_index() {
1176 let mut message = create_test_message();
1177 message.instructions[0].program_id_index = 99;
1178 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1179 }
1180
1181 #[test]
1182 fn sanitize_rejects_fee_payer_as_program() {
1183 let mut message = create_test_message();
1184 message.instructions[0].program_id_index = 0;
1185 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1186 }
1187
1188 #[test]
1189 fn sanitize_rejects_instruction_with_too_many_accounts() {
1190 let mut message = create_test_message();
1191 message.instructions[0].accounts = vec![0u8; (u8::MAX as usize) + 1];
1192 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1193 }
1194
1195 #[test]
1196 fn sanitize_rejects_invalid_instruction_account_index() {
1197 let mut message = create_test_message();
1198 message.instructions[0].accounts = vec![0, 99]; assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1200 }
1201
1202 #[test]
1203 fn sanitize_accepts_64_addresses() {
1204 let mut message = create_test_message();
1205 message.account_keys = (0..MAX_ADDRESSES).map(|_| Address::new_unique()).collect();
1206 message.header.num_required_signatures = 1;
1207 message.header.num_readonly_signed_accounts = 0;
1208 message.header.num_readonly_unsigned_accounts = 1;
1209 message.instructions[0].program_id_index = 1;
1210 message.instructions[0].accounts = vec![0, 2];
1211 assert!(message.sanitize().is_ok());
1212 }
1213
1214 #[test]
1215 fn sanitize_accepts_64_instructions() {
1216 let mut message = create_test_message();
1217 message.instructions = (0..MAX_INSTRUCTIONS)
1218 .map(|_| CompiledInstruction {
1219 program_id_index: 1,
1220 accounts: vec![0, 2],
1221 data: vec![1, 2, 3],
1222 })
1223 .collect();
1224 assert!(message.sanitize().is_ok());
1225 }
1226
1227 #[test]
1228 fn size_matches_serialized_length() {
1229 let test_cases = [
1230 MessageBuilder::new()
1232 .required_signatures(1)
1233 .lifetime_specifier(Hash::new_unique())
1234 .accounts(vec![Address::new_unique()])
1235 .build()
1236 .unwrap(),
1237 MessageBuilder::new()
1239 .required_signatures(1)
1240 .lifetime_specifier(Hash::new_unique())
1241 .accounts(vec![Address::new_unique(), Address::new_unique()])
1242 .priority_fee(1000)
1243 .compute_unit_limit(200_000)
1244 .instruction(CompiledInstruction {
1245 program_id_index: 1,
1246 accounts: vec![0],
1247 data: vec![1, 2, 3, 4],
1248 })
1249 .build()
1250 .unwrap(),
1251 MessageBuilder::new()
1253 .required_signatures(2)
1254 .readonly_signed_accounts(1)
1255 .readonly_unsigned_accounts(1)
1256 .lifetime_specifier(Hash::new_unique())
1257 .accounts(vec![
1258 Address::new_unique(),
1259 Address::new_unique(),
1260 Address::new_unique(),
1261 Address::new_unique(),
1262 ])
1263 .heap_size(65536)
1264 .instructions(vec![
1265 CompiledInstruction {
1266 program_id_index: 2,
1267 accounts: vec![0, 1],
1268 data: vec![],
1269 },
1270 CompiledInstruction {
1271 program_id_index: 3,
1272 accounts: vec![0, 1, 2],
1273 data: vec![0xAA; 100],
1274 },
1275 ])
1276 .build()
1277 .unwrap(),
1278 ];
1279
1280 for message in &test_cases {
1281 assert_eq!(message.size(), wincode::serialize(message).unwrap().len());
1282 }
1283 }
1284
1285 #[test]
1286 fn byte_layout_without_config() {
1287 let fee_payer = Address::new_from_array([1u8; 32]);
1288 let program = Address::new_from_array([2u8; 32]);
1289 let blockhash = Hash::new_from_array([0xAB; 32]);
1290
1291 let message = MessageBuilder::new()
1292 .required_signatures(1)
1293 .lifetime_specifier(blockhash)
1294 .accounts(vec![fee_payer, program])
1295 .instruction(CompiledInstruction {
1296 program_id_index: 1,
1297 accounts: vec![0],
1298 data: vec![0xDE, 0xAD],
1299 })
1300 .build()
1301 .unwrap();
1302
1303 let bytes = wincode::serialize(&message).unwrap();
1304
1305 let mut expected = vec![1, 0, 0];
1311 expected.extend_from_slice(&0u32.to_le_bytes()); expected.extend_from_slice(&[0xAB; 32]); expected.push(1); expected.push(2); expected.extend_from_slice(&[1u8; 32]); expected.extend_from_slice(&[2u8; 32]); expected.push(1); expected.push(1); expected.extend_from_slice(&2u16.to_le_bytes()); expected.push(0); expected.extend_from_slice(&[0xDE, 0xAD]); assert_eq!(bytes, expected);
1324 }
1325
1326 #[test]
1327 fn byte_layout_with_config() {
1328 let fee_payer = Address::new_from_array([1u8; 32]);
1329 let program = Address::new_from_array([2u8; 32]);
1330 let blockhash = Hash::new_from_array([0xBB; 32]);
1331
1332 let message = MessageBuilder::new()
1333 .required_signatures(1)
1334 .lifetime_specifier(blockhash)
1335 .accounts(vec![fee_payer, program])
1336 .priority_fee(0x0102030405060708u64)
1337 .compute_unit_limit(0x11223344u32)
1338 .instruction(CompiledInstruction {
1339 program_id_index: 1,
1340 accounts: vec![],
1341 data: vec![],
1342 })
1343 .build()
1344 .unwrap();
1345
1346 let bytes = wincode::serialize(&message).unwrap();
1347
1348 let mut expected = vec![1, 0, 0];
1349 expected.extend_from_slice(&7u32.to_le_bytes());
1351 expected.extend_from_slice(&[0xBB; 32]);
1352 expected.push(1);
1353 expected.push(2);
1354 expected.extend_from_slice(&[1u8; 32]);
1355 expected.extend_from_slice(&[2u8; 32]);
1356 expected.extend_from_slice(&0x0102030405060708u64.to_le_bytes());
1358 expected.extend_from_slice(&0x11223344u32.to_le_bytes());
1360 expected.push(1); expected.push(0); expected.extend_from_slice(&0u16.to_le_bytes()); assert_eq!(bytes, expected);
1365 }
1366
1367 #[test]
1368 fn roundtrip_preserves_all_config_fields() {
1369 let message = MessageBuilder::new()
1370 .required_signatures(1)
1371 .lifetime_specifier(Hash::new_unique())
1372 .accounts(vec![Address::new_unique(), Address::new_unique()])
1373 .priority_fee(1000)
1374 .compute_unit_limit(200_000)
1375 .loaded_accounts_data_size_limit(1_000_000)
1376 .heap_size(65536)
1377 .instruction(CompiledInstruction {
1378 program_id_index: 1,
1379 accounts: vec![0],
1380 data: vec![],
1381 })
1382 .build()
1383 .unwrap();
1384
1385 let serialized = wincode::serialize(&message).unwrap();
1386 let deserialized = deserialize(&serialized).unwrap();
1387 assert_eq!(message.config, deserialized.config);
1388 }
1389
1390 #[test]
1391 fn deserialize_rejects_unknown_config_mask_bits() {
1392 let message = MessageBuilder::default()
1393 .required_signatures(1)
1394 .lifetime_specifier(Hash::new_unique())
1395 .accounts(vec![Address::new_unique(), Address::new_unique()])
1396 .instruction(CompiledInstruction {
1397 program_id_index: 1,
1398 accounts: vec![0],
1399 data: vec![],
1400 })
1401 .build()
1402 .unwrap();
1403
1404 let mut serialized = wincode::serialize(&message).unwrap();
1405 assert!(deserialize(&serialized).is_ok());
1407
1408 let unknown_bit = 1u32 << TransactionConfigMask::KNOWN_BITS.trailing_ones();
1412 let mask = u32::from_le_bytes(serialized[3..7].try_into().unwrap()) | unknown_bit;
1413 serialized[3..7].copy_from_slice(&mask.to_le_bytes());
1414 assert!(deserialize(&serialized).is_err());
1415 }
1416
1417 #[test]
1418 fn deserialize_rejects_partial_priority_fee_bits() {
1419 let message = MessageBuilder::default()
1420 .required_signatures(1)
1421 .lifetime_specifier(Hash::new_unique())
1422 .accounts(vec![Address::new_unique(), Address::new_unique()])
1423 .instruction(CompiledInstruction {
1424 program_id_index: 1,
1425 accounts: vec![0],
1426 data: vec![],
1427 })
1428 .build()
1429 .unwrap();
1430
1431 let mut serialized = wincode::serialize(&message).unwrap();
1432 serialized[3] |= 0b1;
1434 assert!(deserialize(&serialized).is_err());
1435 }
1436}