1#[cfg(test)]
37pub use self::tests::MessageBuilder;
38#[cfg(feature = "serde")]
39use serde_derive::{Deserialize, Serialize};
40#[cfg(feature = "frozen-abi")]
41use solana_frozen_abi_macro::AbiExample;
42#[cfg(feature = "std")]
43use std::collections::HashSet;
44#[cfg(feature = "wincode")]
45use {
46 crate::v1::{InstructionHeader, FIXED_HEADER_SIZE},
47 core::{mem::MaybeUninit, slice::from_raw_parts},
48 wincode::{
49 config::{Config, ConfigCore},
50 context,
51 io::{Reader, Writer},
52 len::SeqLen,
53 ReadResult, SchemaRead, SchemaReadContext, SchemaWrite, WriteResult,
54 },
55};
56use {
57 crate::{
58 compiled_instruction::CompiledInstruction,
59 compiled_keys::CompiledKeys,
60 v1::{
61 MessageError, TransactionConfig, TransactionConfigMask, MAX_ADDRESSES, MAX_HEAP_SIZE,
62 MAX_INSTRUCTIONS, MAX_SIGNATURES, MIN_HEAP_SIZE,
63 },
64 AccountKeys, CompileError, MessageHeader,
65 },
66 alloc::{collections::BTreeSet, vec::Vec},
67 core::mem::size_of,
68 solana_address::Address,
69 solana_hash::Hash,
70 solana_instruction::Instruction,
71 solana_sanitize::{Sanitize, SanitizeError},
72};
73
74#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
81#[cfg_attr(
82 feature = "serde",
83 derive(Serialize, Deserialize),
84 serde(rename_all = "camelCase")
85)]
86#[derive(Debug, Clone, PartialEq, Eq, Default)]
87pub struct Message {
88 pub header: MessageHeader,
90
91 pub config: TransactionConfig,
93
94 pub lifetime_specifier: Hash,
96
97 pub account_keys: Vec<Address>,
116
117 pub instructions: Vec<CompiledInstruction>,
119}
120
121impl Message {
122 pub fn new(
124 header: MessageHeader,
125 config: TransactionConfig,
126 lifetime_specifier: Hash,
127 account_keys: Vec<Address>,
128 instructions: Vec<CompiledInstruction>,
129 ) -> Self {
130 Self {
131 header,
132 config,
133 lifetime_specifier,
134 account_keys,
135 instructions,
136 }
137 }
138
139 pub fn try_compile(
215 payer: &Address,
216 instructions: &[Instruction],
217 recent_blockhash: Hash,
218 ) -> Result<Self, CompileError> {
219 Self::try_compile_with_config(
220 payer,
221 instructions,
222 recent_blockhash,
223 TransactionConfig::empty(),
224 )
225 }
226
227 pub fn try_compile_with_config(
304 payer: &Address,
305 instructions: &[Instruction],
306 recent_blockhash: Hash,
307 config: TransactionConfig,
308 ) -> Result<Self, CompileError> {
309 let compiled_keys = CompiledKeys::compile(instructions, Some(*payer));
310 let (header, static_keys) = compiled_keys.try_into_message_components()?;
311
312 let account_keys = AccountKeys::new(&static_keys, None);
313 let instructions = account_keys.try_compile_instructions(instructions)?;
314
315 Ok(Self {
316 header,
317 config,
318 lifetime_specifier: recent_blockhash,
319 account_keys: static_keys,
320 instructions,
321 })
322 }
323
324 pub fn fee_payer(&self) -> Option<&Address> {
326 self.account_keys.first()
327 }
328
329 pub fn is_signer(&self, index: usize) -> bool {
332 index < usize::from(self.header.num_required_signatures)
333 }
334
335 pub fn is_signer_writable(&self, index: usize) -> bool {
337 if !self.is_signer(index) {
338 return false;
339 }
340 let num_writable_signers = usize::from(self.header.num_required_signatures)
343 .saturating_sub(usize::from(self.header.num_readonly_signed_accounts));
344 index < num_writable_signers
345 }
346
347 pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
349 crate::is_key_called_as_program(&self.instructions, key_index)
350 }
351
352 #[inline(always)]
357 #[cfg(feature = "std")]
358 pub(crate) fn is_writable_index(&self, i: usize) -> bool {
359 crate::is_writable_index(i, self.header, &self.account_keys)
360 }
361
362 pub fn is_upgradeable_loader_present(&self) -> bool {
364 crate::is_upgradeable_loader_present(&self.account_keys)
365 }
366
367 #[cfg(feature = "std")]
382 pub fn is_maybe_writable(
383 &self,
384 key_index: usize,
385 reserved_account_keys: Option<&HashSet<Address>>,
386 ) -> bool {
387 crate::is_maybe_writable(
388 key_index,
389 self.header,
390 &self.account_keys,
391 &self.instructions,
392 reserved_account_keys,
393 )
394 }
395
396 pub fn demote_program_id(&self, i: usize) -> bool {
397 crate::is_program_id_write_demoted(i, &self.account_keys, &self.instructions)
398 }
399
400 #[cfg(feature = "wincode")]
402 pub fn serialize(&self) -> Vec<u8> {
403 wincode::serialize(&(crate::v1::V1_PREFIX, self)).unwrap()
404 }
405
406 #[allow(clippy::arithmetic_side_effects)]
408 #[inline(always)]
409 pub fn size(&self) -> usize {
410 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()
418 * (
419 size_of::<u8>()
420 + size_of::<u8>()
421 + size_of::<u16>()
422 ) + self
424 .instructions
425 .iter()
426 .map(|ix| {
427 (ix.accounts.len() * size_of::<u8>())
428 + ix.data.len()
429 })
430 .sum::<usize>() }
432
433 pub fn validate(&self) -> Result<(), MessageError> {
434 if self.header.num_required_signatures > MAX_SIGNATURES {
436 return Err(MessageError::TooManySignatures);
437 }
438
439 if self.instructions.len() > MAX_INSTRUCTIONS as usize {
441 return Err(MessageError::TooManyInstructions);
442 }
443
444 let num_account_keys = self.account_keys.len();
445
446 if num_account_keys > MAX_ADDRESSES as usize {
448 return Err(MessageError::TooManyAddresses);
449 }
450
451 let min_accounts = usize::from(self.header.num_required_signatures)
453 .saturating_add(usize::from(self.header.num_readonly_unsigned_accounts));
454
455 if num_account_keys < min_accounts {
456 return Err(MessageError::NotEnoughAddressesForSignatures);
457 }
458
459 if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures {
461 return Err(MessageError::ZeroSigners);
462 }
463
464 let unique_keys: BTreeSet<_> = self.account_keys.iter().collect();
466 if unique_keys.len() != num_account_keys {
467 return Err(MessageError::DuplicateAddresses);
468 }
469
470 if let Some(heap_size) = self.config.heap_size {
476 if !heap_size.is_multiple_of(1024) {
477 return Err(MessageError::InvalidHeapSize);
478 }
479
480 if !(MIN_HEAP_SIZE..=MAX_HEAP_SIZE).contains(&heap_size) {
481 return Err(MessageError::InvalidHeapSize);
482 }
483 }
484
485 let max_account_index = num_account_keys
487 .checked_sub(1)
488 .ok_or(MessageError::NotEnoughAccountKeys)?;
489
490 for instruction in &self.instructions {
491 if usize::from(instruction.program_id_index) > max_account_index {
493 return Err(MessageError::InvalidInstructionAccountIndex);
494 }
495
496 if instruction.program_id_index == 0 {
498 return Err(MessageError::InvalidInstructionAccountIndex);
499 }
500
501 if instruction.accounts.len() > u8::MAX as usize {
503 return Err(MessageError::InstructionAccountsTooLarge);
504 }
505
506 if instruction.data.len() > u16::MAX as usize {
508 return Err(MessageError::InstructionDataTooLarge);
509 }
510
511 for &account_index in &instruction.accounts {
513 if usize::from(account_index) > max_account_index {
514 return Err(MessageError::InvalidInstructionAccountIndex);
515 }
516 }
517 }
518
519 Ok(())
520 }
521}
522
523impl Sanitize for Message {
524 fn sanitize(&self) -> Result<(), SanitizeError> {
525 Ok(self.validate()?)
526 }
527}
528
529#[cfg(feature = "wincode")]
530unsafe impl<C: ConfigCore> SchemaWrite<C> for Message {
531 type Src = Self;
532
533 #[inline(always)]
534 fn size_of(src: &Self::Src) -> WriteResult<usize> {
535 Ok(src.size())
536 }
537
538 fn write(mut writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
539 let mut writer = unsafe { writer.as_trusted_for(src.size()) }?;
541 writer.write(&[
542 src.header.num_required_signatures,
543 src.header.num_readonly_signed_accounts,
544 src.header.num_readonly_unsigned_accounts,
545 ])?;
546 let mask = TransactionConfigMask::from(&src.config).0.to_le_bytes();
547 writer.write(&mask)?;
548 writer.write(src.lifetime_specifier.as_bytes())?;
549 writer.write(&[src.instructions.len() as u8, src.account_keys.len() as u8])?;
550
551 #[expect(clippy::arithmetic_side_effects)]
554 let account_keys = unsafe {
555 from_raw_parts(
556 src.account_keys.as_ptr().cast::<u8>(),
557 src.account_keys.len() * size_of::<Address>(),
558 )
559 };
560 writer.write(account_keys)?;
561
562 if let Some(value) = src.config.priority_fee {
563 writer.write(&value.to_le_bytes())?;
564 }
565 if let Some(value) = src.config.compute_unit_limit {
566 writer.write(&value.to_le_bytes())?;
567 }
568 if let Some(value) = src.config.loaded_accounts_data_size_limit {
569 writer.write(&value.to_le_bytes())?;
570 }
571 if let Some(value) = src.config.heap_size {
572 writer.write(&value.to_le_bytes())?;
573 }
574
575 for ix in &src.instructions {
576 writer.write(&[ix.program_id_index, ix.accounts.len() as u8])?;
577 writer.write(&(ix.data.len() as u16).to_le_bytes())?;
578 }
579
580 for ix in &src.instructions {
581 writer.write(&ix.accounts)?;
582 writer.write(&ix.data)?;
583 }
584
585 writer.finish()?;
586
587 Ok(())
588 }
589}
590
591#[cfg(feature = "wincode")]
593#[inline]
594#[deprecated(since = "4.1.2", note = "use `Message::serialize` instead")]
595pub fn serialize(message: &Message) -> Vec<u8> {
596 wincode::serialize(message).unwrap()
597}
598
599#[cfg(feature = "wincode")]
600unsafe impl<'de, C: Config> SchemaRead<'de, C> for Message {
601 type Dst = Message;
602
603 #[expect(clippy::arithmetic_side_effects)]
604 fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
605 let (header, lifetime_specifier, config_mask, num_instructions, num_addresses) = {
606 let mut reader = unsafe { reader.as_trusted_for(FIXED_HEADER_SIZE)? };
613 let header = <MessageHeader as SchemaRead<C>>::get(reader.by_ref())?;
614 let config_mask = TransactionConfigMask(u32::from_le_bytes(reader.take_array()?));
615 let lifetime_specifier = <Hash as SchemaRead<C>>::get(reader.by_ref())?;
616 let num_instructions = reader.take_byte()? as usize;
617 let num_addresses = reader.take_byte()? as usize;
618 (
619 header,
620 lifetime_specifier,
621 config_mask,
622 num_instructions,
623 num_addresses,
624 )
625 };
626
627 if config_mask.has_unknown_bits() || config_mask.has_invalid_priority_fee_bits() {
632 return Err(wincode::error::invalid_value(
633 "invalid transaction config mask",
634 ));
635 }
636
637 <C::LengthEncoding as SeqLen<C>>::prealloc_check::<Address>(num_addresses)?;
638 let account_keys = <Vec<Address> as SchemaReadContext<C, context::Len>>::get_with_context(
639 context::Len(num_addresses),
640 reader.by_ref(),
641 )?;
642
643 let mut config = TransactionConfig::empty();
644 if config_mask.has_priority_fee() {
645 config.priority_fee = Some(u64::from_le_bytes(reader.take_array()?));
646 }
647 if config_mask.has_compute_unit_limit() {
648 config.compute_unit_limit = Some(u32::from_le_bytes(reader.take_array()?));
649 }
650 if config_mask.has_loaded_accounts_data_size() {
651 config.loaded_accounts_data_size_limit = Some(u32::from_le_bytes(reader.take_array()?));
652 }
653 if config_mask.has_heap_size() {
654 config.heap_size = Some(u32::from_le_bytes(reader.take_array()?));
655 }
656
657 let instruction_headers = unsafe {
665 from_raw_parts(
666 reader
667 .take_borrowed(num_instructions * size_of::<InstructionHeader>())?
668 .as_ptr() as *const InstructionHeader,
669 num_instructions,
670 )
671 };
672 let mut instructions = Vec::with_capacity(num_instructions);
673 for header in instruction_headers {
674 let program_id_index = header.0;
675 let num_accounts = header.1 as usize;
676 let data_len = u16::from_le_bytes(header.2) as usize;
677
678 <C::LengthEncoding as SeqLen<C>>::prealloc_check::<u8>(num_accounts)?;
679 let accounts = <Vec<u8> as SchemaReadContext<C, context::Len>>::get_with_context(
680 context::Len(num_accounts),
681 reader.by_ref(),
682 )?;
683 <C::LengthEncoding as SeqLen<C>>::prealloc_check::<u8>(data_len)?;
684 let data = <Vec<u8> as SchemaReadContext<C, context::Len>>::get_with_context(
685 context::Len(data_len),
686 reader.by_ref(),
687 )?;
688
689 instructions.push(CompiledInstruction {
690 program_id_index,
691 accounts,
692 data,
693 });
694 }
695
696 dst.write(Message {
697 header,
698 lifetime_specifier,
699 config,
700 account_keys,
701 instructions,
702 });
703
704 Ok(())
705 }
706}
707
708#[cfg(feature = "wincode")]
711#[inline]
712pub fn deserialize(input: &[u8]) -> wincode::ReadResult<Message> {
713 wincode::deserialize(input)
714}
715
716#[cfg(test)]
717mod tests {
718 use {super::*, alloc::vec, solana_sdk_ids::bpf_loader_upgradeable};
719
720 #[derive(Debug, Clone, Default)]
726 pub struct MessageBuilder {
727 header: MessageHeader,
728 config: TransactionConfig,
729 lifetime_specifier: Option<Hash>,
730 account_keys: Vec<Address>,
731 instructions: Vec<CompiledInstruction>,
732 }
733
734 impl MessageBuilder {
735 pub fn new() -> Self {
736 Self::default()
737 }
738
739 #[must_use]
740 pub fn required_signatures(mut self, count: u8) -> Self {
741 self.header.num_required_signatures = count;
742 self
743 }
744
745 #[must_use]
746 pub fn readonly_signed_accounts(mut self, count: u8) -> Self {
747 self.header.num_readonly_signed_accounts = count;
748 self
749 }
750
751 #[must_use]
752 pub fn readonly_unsigned_accounts(mut self, count: u8) -> Self {
753 self.header.num_readonly_unsigned_accounts = count;
754 self
755 }
756
757 #[must_use]
758 pub fn lifetime_specifier(mut self, hash: Hash) -> Self {
759 self.lifetime_specifier = Some(hash);
760 self
761 }
762
763 #[must_use]
764 pub fn config(mut self, config: TransactionConfig) -> Self {
765 self.config = config;
766 self
767 }
768
769 #[must_use]
770 pub fn priority_fee(mut self, fee: u64) -> Self {
771 self.config.priority_fee = Some(fee);
772 self
773 }
774
775 #[must_use]
776 pub fn compute_unit_limit(mut self, limit: u32) -> Self {
777 self.config.compute_unit_limit = Some(limit);
778 self
779 }
780
781 #[must_use]
782 pub fn loaded_accounts_data_size_limit(mut self, limit: u32) -> Self {
783 self.config.loaded_accounts_data_size_limit = Some(limit);
784 self
785 }
786
787 #[must_use]
788 pub fn heap_size(mut self, size: u32) -> Self {
789 self.config.heap_size = Some(size);
790 self
791 }
792
793 #[must_use]
794 pub fn account(mut self, key: Address) -> Self {
795 self.account_keys.push(key);
796 self
797 }
798
799 #[must_use]
800 pub fn accounts(mut self, keys: Vec<Address>) -> Self {
801 self.account_keys = keys;
802 self
803 }
804
805 #[must_use]
806 pub fn instruction(mut self, instruction: CompiledInstruction) -> Self {
807 self.instructions.push(instruction);
808 self
809 }
810
811 #[must_use]
812 pub fn instructions(mut self, instructions: Vec<CompiledInstruction>) -> Self {
813 self.instructions = instructions;
814 self
815 }
816
817 pub fn build(self) -> Result<Message, MessageError> {
819 let lifetime_specifier = self
820 .lifetime_specifier
821 .ok_or(MessageError::MissingLifetimeSpecifier)?;
822
823 let message = Message::new(
824 self.header,
825 self.config,
826 lifetime_specifier,
827 self.account_keys,
828 self.instructions,
829 );
830
831 message.validate()?;
832
833 Ok(message)
834 }
835 }
836
837 fn create_test_message() -> Message {
838 MessageBuilder::new()
839 .required_signatures(1)
840 .readonly_unsigned_accounts(1)
841 .lifetime_specifier(Hash::new_unique())
842 .accounts(vec![
843 Address::new_unique(), Address::new_unique(), Address::new_unique(), ])
847 .compute_unit_limit(200_000)
848 .instruction(CompiledInstruction {
849 program_id_index: 1,
850 accounts: vec![0, 2],
851 data: vec![1, 2, 3, 4],
852 })
853 .build()
854 .unwrap()
855 }
856
857 #[test]
858 fn fee_payer_returns_first_account() {
859 let fee_payer = Address::new_unique();
860 let message = MessageBuilder::new()
861 .required_signatures(1)
862 .lifetime_specifier(Hash::new_unique())
863 .accounts(vec![fee_payer, Address::new_unique()])
864 .build()
865 .unwrap();
866
867 assert_eq!(message.fee_payer(), Some(&fee_payer));
868 }
869
870 #[test]
871 fn fee_payer_returns_none_for_empty_accounts() {
872 let message = Message::new(
874 MessageHeader::default(),
875 TransactionConfig::default(),
876 Hash::new_unique(),
877 vec![],
878 vec![],
879 );
880
881 assert_eq!(message.fee_payer(), None);
882 }
883
884 #[test]
885 fn is_signer_checks_signature_requirement() {
886 let message = create_test_message();
887 assert!(message.is_signer(0)); assert!(!message.is_signer(1)); assert!(!message.is_signer(2)); }
891
892 #[test]
893 fn is_signer_writable_identifies_writable_signers() {
894 let message = MessageBuilder::new()
895 .required_signatures(3)
896 .readonly_signed_accounts(1) .lifetime_specifier(Hash::new_unique())
898 .accounts(vec![
899 Address::new_unique(), Address::new_unique(), Address::new_unique(), Address::new_unique(), ])
904 .build()
905 .unwrap();
906
907 assert!(message.is_signer_writable(0));
909 assert!(message.is_signer_writable(1));
910 assert!(!message.is_signer_writable(2));
912 assert!(!message.is_signer_writable(3));
914 assert!(!message.is_signer_writable(100));
915 }
916
917 #[test]
918 fn is_signer_writable_all_writable_when_no_readonly() {
919 let message = MessageBuilder::new()
920 .required_signatures(2)
921 .readonly_signed_accounts(0) .lifetime_specifier(Hash::new_unique())
923 .accounts(vec![
924 Address::new_unique(),
925 Address::new_unique(),
926 Address::new_unique(),
927 ])
928 .build()
929 .unwrap();
930
931 assert!(message.is_signer_writable(0));
932 assert!(message.is_signer_writable(1));
933 assert!(!message.is_signer_writable(2)); }
935
936 #[test]
937 fn is_key_called_as_program_detects_program_indices() {
938 let message = create_test_message();
939 assert!(message.is_key_called_as_program(1));
941 assert!(!message.is_key_called_as_program(0));
942 assert!(!message.is_key_called_as_program(2));
943 assert!(!message.is_key_called_as_program(256));
945 assert!(!message.is_key_called_as_program(10_000));
946 }
947
948 #[test]
949 fn is_upgradeable_loader_present_detects_loader() {
950 let message = create_test_message();
951 assert!(!message.is_upgradeable_loader_present());
952
953 let mut message_with_loader = create_test_message();
954 message_with_loader
955 .account_keys
956 .push(bpf_loader_upgradeable::id());
957 assert!(message_with_loader.is_upgradeable_loader_present());
958 }
959
960 #[test]
961 fn is_writable_index_respects_header_layout() {
962 let message = create_test_message();
963 assert!(message.is_writable_index(0)); assert!(message.is_writable_index(1)); assert!(!message.is_writable_index(2)); }
968
969 #[test]
970 fn is_writable_index_handles_mixed_signer_permissions() {
971 let mut message = create_test_message();
972 message.header.num_required_signatures = 2;
974 message.header.num_readonly_signed_accounts = 1;
975 message.header.num_readonly_unsigned_accounts = 1;
976 message.account_keys = vec![
977 Address::new_unique(), Address::new_unique(), Address::new_unique(), ];
981 message.instructions[0].program_id_index = 2;
982 message.instructions[0].accounts = vec![0, 1];
983
984 assert!(message.sanitize().is_ok());
985 assert!(message.is_writable_index(0)); assert!(!message.is_writable_index(1)); assert!(!message.is_writable_index(2)); assert!(!message.is_writable_index(999)); }
990
991 #[test]
992 fn is_maybe_writable_returns_false_for_readonly_index() {
993 let message = create_test_message();
994 assert!(!message.is_writable_index(2));
996 assert!(!message.is_maybe_writable(2, None));
997 assert!(!message.is_maybe_writable(2, Some(&HashSet::new())));
999 }
1000
1001 #[test]
1002 fn is_maybe_writable_demotes_reserved_accounts() {
1003 let message = create_test_message();
1004 let reserved = HashSet::from([message.account_keys[0]]);
1005 assert!(message.is_writable_index(0));
1007 assert!(!message.is_maybe_writable(0, Some(&reserved)));
1008 }
1009
1010 #[test]
1011 fn is_maybe_writable_demotes_programs_without_upgradeable_loader() {
1012 let message = create_test_message();
1013 assert!(message.is_writable_index(1));
1015 assert!(message.is_key_called_as_program(1));
1016 assert!(!message.is_upgradeable_loader_present());
1017 assert!(!message.is_maybe_writable(1, None));
1018 }
1019
1020 #[test]
1021 fn is_maybe_writable_preserves_programs_with_upgradeable_loader() {
1022 let mut message = create_test_message();
1023 message.account_keys.push(bpf_loader_upgradeable::id());
1025
1026 assert!(message.sanitize().is_ok());
1027 assert!(message.is_writable_index(1));
1028 assert!(message.is_key_called_as_program(1));
1029 assert!(message.is_upgradeable_loader_present());
1030 assert!(message.is_maybe_writable(1, None));
1032 }
1033
1034 #[test]
1035 fn sanitize_accepts_valid_message() {
1036 let message = create_test_message();
1037 assert!(message.sanitize().is_ok());
1038 }
1039
1040 #[test]
1041 fn sanitize_rejects_zero_signers() {
1042 let mut message = create_test_message();
1043 message.header.num_required_signatures = 0;
1044 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1045 }
1046
1047 #[test]
1048 fn sanitize_rejects_over_12_signatures() {
1049 let mut message = create_test_message();
1050 message.header.num_required_signatures = MAX_SIGNATURES + 1;
1051 message.account_keys = (0..MAX_SIGNATURES + 1)
1052 .map(|_| Address::new_unique())
1053 .collect();
1054 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1055 }
1056
1057 #[test]
1058 fn sanitize_rejects_over_64_addresses() {
1059 let mut message = create_test_message();
1060 message.account_keys = (0..65).map(|_| Address::new_unique()).collect();
1061 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1062 }
1063
1064 #[test]
1065 fn sanitize_rejects_over_64_instructions() {
1066 let mut message = create_test_message();
1067 message.instructions = (0..65) .map(|_| CompiledInstruction {
1069 program_id_index: 1,
1070 accounts: vec![0],
1071 data: vec![],
1072 })
1073 .collect();
1074 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1075 }
1076
1077 #[test]
1078 fn sanitize_rejects_insufficient_accounts_for_header() {
1079 let mut message = create_test_message();
1080 message.header.num_readonly_unsigned_accounts = 10;
1083 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1084 }
1085
1086 #[test]
1087 fn sanitize_rejects_all_signers_readonly() {
1088 let mut message = create_test_message();
1089 message.header.num_readonly_signed_accounts = 1; assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1091 }
1092
1093 #[test]
1094 fn sanitize_rejects_duplicate_addresses() {
1095 let mut message = create_test_message();
1096 let dup = message.account_keys[0];
1097 message.account_keys[1] = dup;
1098 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1099 }
1100
1101 #[test]
1102 fn sanitize_rejects_unaligned_heap_size() {
1103 let mut message = create_test_message();
1104 message.config.heap_size = Some(1025); assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1106 }
1107
1108 #[test]
1109 fn sanitize_rejects_heap_size_below_minimum() {
1110 let mut message = create_test_message();
1111 message.config.heap_size = Some(MIN_HEAP_SIZE - 1);
1112 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1113 }
1114
1115 #[test]
1116 fn sanitize_rejects_heap_size_above_maximum() {
1117 let mut message = create_test_message();
1118 message.config.heap_size = Some(MAX_HEAP_SIZE + 1);
1119 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1120 }
1121
1122 #[test]
1123 fn sanitize_accepts_minimum_heap_size() {
1124 let mut message = create_test_message();
1125 message.config.heap_size = Some(MIN_HEAP_SIZE);
1126 assert!(message.sanitize().is_ok());
1127 }
1128
1129 #[test]
1130 fn sanitize_accepts_maximum_heap_size() {
1131 let mut message = create_test_message();
1132 message.config.heap_size = Some(MAX_HEAP_SIZE);
1133 assert!(message.sanitize().is_ok());
1134 }
1135
1136 #[test]
1137 fn sanitize_accepts_aligned_heap_size() {
1138 let mut message = create_test_message();
1139 message.config.heap_size = Some(65536); assert!(message.sanitize().is_ok());
1141 }
1142
1143 #[test]
1144 fn sanitize_rejects_invalid_program_id_index() {
1145 let mut message = create_test_message();
1146 message.instructions[0].program_id_index = 99;
1147 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1148 }
1149
1150 #[test]
1151 fn sanitize_rejects_fee_payer_as_program() {
1152 let mut message = create_test_message();
1153 message.instructions[0].program_id_index = 0;
1154 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1155 }
1156
1157 #[test]
1158 fn sanitize_rejects_instruction_with_too_many_accounts() {
1159 let mut message = create_test_message();
1160 message.instructions[0].accounts = vec![0u8; (u8::MAX as usize) + 1];
1161 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1162 }
1163
1164 #[test]
1165 fn sanitize_rejects_invalid_instruction_account_index() {
1166 let mut message = create_test_message();
1167 message.instructions[0].accounts = vec![0, 99]; assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1169 }
1170
1171 #[test]
1172 fn sanitize_accepts_64_addresses() {
1173 let mut message = create_test_message();
1174 message.account_keys = (0..MAX_ADDRESSES).map(|_| Address::new_unique()).collect();
1175 message.header.num_required_signatures = 1;
1176 message.header.num_readonly_signed_accounts = 0;
1177 message.header.num_readonly_unsigned_accounts = 1;
1178 message.instructions[0].program_id_index = 1;
1179 message.instructions[0].accounts = vec![0, 2];
1180 assert!(message.sanitize().is_ok());
1181 }
1182
1183 #[test]
1184 fn sanitize_accepts_64_instructions() {
1185 let mut message = create_test_message();
1186 message.instructions = (0..MAX_INSTRUCTIONS)
1187 .map(|_| CompiledInstruction {
1188 program_id_index: 1,
1189 accounts: vec![0, 2],
1190 data: vec![1, 2, 3],
1191 })
1192 .collect();
1193 assert!(message.sanitize().is_ok());
1194 }
1195
1196 #[test]
1197 fn size_matches_serialized_length() {
1198 let test_cases = [
1199 MessageBuilder::new()
1201 .required_signatures(1)
1202 .lifetime_specifier(Hash::new_unique())
1203 .accounts(vec![Address::new_unique()])
1204 .build()
1205 .unwrap(),
1206 MessageBuilder::new()
1208 .required_signatures(1)
1209 .lifetime_specifier(Hash::new_unique())
1210 .accounts(vec![Address::new_unique(), Address::new_unique()])
1211 .priority_fee(1000)
1212 .compute_unit_limit(200_000)
1213 .instruction(CompiledInstruction {
1214 program_id_index: 1,
1215 accounts: vec![0],
1216 data: vec![1, 2, 3, 4],
1217 })
1218 .build()
1219 .unwrap(),
1220 MessageBuilder::new()
1222 .required_signatures(2)
1223 .readonly_signed_accounts(1)
1224 .readonly_unsigned_accounts(1)
1225 .lifetime_specifier(Hash::new_unique())
1226 .accounts(vec![
1227 Address::new_unique(),
1228 Address::new_unique(),
1229 Address::new_unique(),
1230 Address::new_unique(),
1231 ])
1232 .heap_size(65536)
1233 .instructions(vec![
1234 CompiledInstruction {
1235 program_id_index: 2,
1236 accounts: vec![0, 1],
1237 data: vec![],
1238 },
1239 CompiledInstruction {
1240 program_id_index: 3,
1241 accounts: vec![0, 1, 2],
1242 data: vec![0xAA; 100],
1243 },
1244 ])
1245 .build()
1246 .unwrap(),
1247 ];
1248
1249 for message in &test_cases {
1250 assert_eq!(message.size(), wincode::serialize(message).unwrap().len());
1251 }
1252 }
1253
1254 #[test]
1255 fn byte_layout_without_config() {
1256 let fee_payer = Address::new_from_array([1u8; 32]);
1257 let program = Address::new_from_array([2u8; 32]);
1258 let blockhash = Hash::new_from_array([0xAB; 32]);
1259
1260 let message = MessageBuilder::new()
1261 .required_signatures(1)
1262 .lifetime_specifier(blockhash)
1263 .accounts(vec![fee_payer, program])
1264 .instruction(CompiledInstruction {
1265 program_id_index: 1,
1266 accounts: vec![0],
1267 data: vec![0xDE, 0xAD],
1268 })
1269 .build()
1270 .unwrap();
1271
1272 let bytes = wincode::serialize(&message).unwrap();
1273
1274 let mut expected = vec![1, 0, 0];
1280 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);
1293 }
1294
1295 #[test]
1296 fn byte_layout_with_config() {
1297 let fee_payer = Address::new_from_array([1u8; 32]);
1298 let program = Address::new_from_array([2u8; 32]);
1299 let blockhash = Hash::new_from_array([0xBB; 32]);
1300
1301 let message = MessageBuilder::new()
1302 .required_signatures(1)
1303 .lifetime_specifier(blockhash)
1304 .accounts(vec![fee_payer, program])
1305 .priority_fee(0x0102030405060708u64)
1306 .compute_unit_limit(0x11223344u32)
1307 .instruction(CompiledInstruction {
1308 program_id_index: 1,
1309 accounts: vec![],
1310 data: vec![],
1311 })
1312 .build()
1313 .unwrap();
1314
1315 let bytes = wincode::serialize(&message).unwrap();
1316
1317 let mut expected = vec![1, 0, 0];
1318 expected.extend_from_slice(&7u32.to_le_bytes());
1320 expected.extend_from_slice(&[0xBB; 32]);
1321 expected.push(1);
1322 expected.push(2);
1323 expected.extend_from_slice(&[1u8; 32]);
1324 expected.extend_from_slice(&[2u8; 32]);
1325 expected.extend_from_slice(&0x0102030405060708u64.to_le_bytes());
1327 expected.extend_from_slice(&0x11223344u32.to_le_bytes());
1329 expected.push(1); expected.push(0); expected.extend_from_slice(&0u16.to_le_bytes()); assert_eq!(bytes, expected);
1334 }
1335
1336 #[test]
1337 fn roundtrip_preserves_all_config_fields() {
1338 let message = MessageBuilder::new()
1339 .required_signatures(1)
1340 .lifetime_specifier(Hash::new_unique())
1341 .accounts(vec![Address::new_unique(), Address::new_unique()])
1342 .priority_fee(1000)
1343 .compute_unit_limit(200_000)
1344 .loaded_accounts_data_size_limit(1_000_000)
1345 .heap_size(65536)
1346 .instruction(CompiledInstruction {
1347 program_id_index: 1,
1348 accounts: vec![0],
1349 data: vec![],
1350 })
1351 .build()
1352 .unwrap();
1353
1354 let serialized = wincode::serialize(&message).unwrap();
1355 let deserialized = deserialize(&serialized).unwrap();
1356 assert_eq!(message.config, deserialized.config);
1357 }
1358
1359 #[test]
1360 fn deserialize_rejects_unknown_config_mask_bits() {
1361 let message = MessageBuilder::default()
1362 .required_signatures(1)
1363 .lifetime_specifier(Hash::new_unique())
1364 .accounts(vec![Address::new_unique(), Address::new_unique()])
1365 .instruction(CompiledInstruction {
1366 program_id_index: 1,
1367 accounts: vec![0],
1368 data: vec![],
1369 })
1370 .build()
1371 .unwrap();
1372
1373 let mut serialized = wincode::serialize(&message).unwrap();
1374 assert!(deserialize(&serialized).is_ok());
1376
1377 let unknown_bit = 1u32 << TransactionConfigMask::KNOWN_BITS.trailing_ones();
1381 let mask = u32::from_le_bytes(serialized[3..7].try_into().unwrap()) | unknown_bit;
1382 serialized[3..7].copy_from_slice(&mask.to_le_bytes());
1383 assert!(deserialize(&serialized).is_err());
1384 }
1385
1386 #[test]
1387 fn deserialize_rejects_partial_priority_fee_bits() {
1388 let message = MessageBuilder::default()
1389 .required_signatures(1)
1390 .lifetime_specifier(Hash::new_unique())
1391 .accounts(vec![Address::new_unique(), Address::new_unique()])
1392 .instruction(CompiledInstruction {
1393 program_id_index: 1,
1394 accounts: vec![0],
1395 data: vec![],
1396 })
1397 .build()
1398 .unwrap();
1399
1400 let mut serialized = wincode::serialize(&message).unwrap();
1401 serialized[3] |= 0b1;
1403 assert!(deserialize(&serialized).is_err());
1404 }
1405}