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