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, 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 pub fn is_maybe_writable(
384 &self,
385 key_index: usize,
386 reserved_account_keys: Option<&HashSet<Address>>,
387 ) -> bool {
388 crate::is_maybe_writable(
389 key_index,
390 self.header,
391 &self.account_keys,
392 &self.instructions,
393 reserved_account_keys,
394 )
395 }
396
397 pub fn demote_program_id(&self, i: usize) -> bool {
398 crate::is_program_id_write_demoted(i, &self.account_keys, &self.instructions)
399 }
400
401 #[cfg(feature = "wincode")]
403 pub fn serialize(&self) -> Vec<u8> {
404 wincode::serialize(&(crate::v1::V1_PREFIX, self)).unwrap()
405 }
406
407 #[allow(clippy::arithmetic_side_effects)]
409 #[inline(always)]
410 pub fn size(&self) -> usize {
411 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()
419 * (
420 size_of::<u8>()
421 + size_of::<u8>()
422 + size_of::<u16>()
423 ) + self
425 .instructions
426 .iter()
427 .map(|ix| {
428 (ix.accounts.len() * size_of::<u8>())
429 + ix.data.len()
430 })
431 .sum::<usize>() }
433
434 pub fn validate(&self) -> Result<(), MessageError> {
435 if self.header.num_required_signatures > MAX_SIGNATURES {
437 return Err(MessageError::TooManySignatures);
438 }
439
440 if self.instructions.len() > MAX_INSTRUCTIONS as usize {
442 return Err(MessageError::TooManyInstructions);
443 }
444
445 let num_account_keys = self.account_keys.len();
446
447 if num_account_keys > MAX_ADDRESSES as usize {
449 return Err(MessageError::TooManyAddresses);
450 }
451
452 let min_accounts = usize::from(self.header.num_required_signatures)
454 .saturating_add(usize::from(self.header.num_readonly_unsigned_accounts));
455
456 if num_account_keys < min_accounts {
457 return Err(MessageError::NotEnoughAddressesForSignatures);
458 }
459
460 if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures {
462 return Err(MessageError::ZeroSigners);
463 }
464
465 let unique_keys: BTreeSet<_> = self.account_keys.iter().collect();
467 if unique_keys.len() != num_account_keys {
468 return Err(MessageError::DuplicateAddresses);
469 }
470
471 if let Some(heap_size) = self.config.heap_size {
477 if !heap_size.is_multiple_of(1024) {
478 return Err(MessageError::InvalidHeapSize);
479 }
480
481 if !(MIN_HEAP_SIZE..=MAX_HEAP_SIZE).contains(&heap_size) {
482 return Err(MessageError::InvalidHeapSize);
483 }
484 }
485
486 let max_account_index = num_account_keys
488 .checked_sub(1)
489 .ok_or(MessageError::NotEnoughAccountKeys)?;
490
491 for instruction in &self.instructions {
492 if usize::from(instruction.program_id_index) > max_account_index {
494 return Err(MessageError::InvalidInstructionAccountIndex);
495 }
496
497 if instruction.program_id_index == 0 {
499 return Err(MessageError::InvalidInstructionAccountIndex);
500 }
501
502 if instruction.accounts.len() > u8::MAX as usize {
504 return Err(MessageError::InstructionAccountsTooLarge);
505 }
506
507 if instruction.data.len() > u16::MAX as usize {
509 return Err(MessageError::InstructionDataTooLarge);
510 }
511
512 for &account_index in &instruction.accounts {
514 if usize::from(account_index) > max_account_index {
515 return Err(MessageError::InvalidInstructionAccountIndex);
516 }
517 }
518 }
519
520 Ok(())
521 }
522}
523
524impl Sanitize for Message {
525 fn sanitize(&self) -> Result<(), SanitizeError> {
526 Ok(self.validate()?)
527 }
528}
529
530#[cfg(feature = "wincode")]
531unsafe impl<C: ConfigCore> SchemaWrite<C> for Message {
532 type Src = Self;
533
534 #[inline(always)]
535 fn size_of(src: &Self::Src) -> WriteResult<usize> {
536 Ok(src.size())
537 }
538
539 fn write(mut writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
540 let mut writer = unsafe { writer.as_trusted_for(src.size()) }?;
542 writer.write(&[
543 src.header.num_required_signatures,
544 src.header.num_readonly_signed_accounts,
545 src.header.num_readonly_unsigned_accounts,
546 ])?;
547 let mask = TransactionConfigMask::from(&src.config).0.to_le_bytes();
548 writer.write(&mask)?;
549 writer.write(src.lifetime_specifier.as_bytes())?;
550 writer.write(&[src.instructions.len() as u8, src.account_keys.len() as u8])?;
551
552 #[expect(clippy::arithmetic_side_effects)]
555 let account_keys = unsafe {
556 from_raw_parts(
557 src.account_keys.as_ptr().cast::<u8>(),
558 src.account_keys.len() * size_of::<Address>(),
559 )
560 };
561 writer.write(account_keys)?;
562
563 if let Some(value) = src.config.priority_fee {
564 writer.write(&value.to_le_bytes())?;
565 }
566 if let Some(value) = src.config.compute_unit_limit {
567 writer.write(&value.to_le_bytes())?;
568 }
569 if let Some(value) = src.config.loaded_accounts_data_size_limit {
570 writer.write(&value.to_le_bytes())?;
571 }
572 if let Some(value) = src.config.heap_size {
573 writer.write(&value.to_le_bytes())?;
574 }
575
576 for ix in &src.instructions {
577 writer.write(&[ix.program_id_index, ix.accounts.len() as u8])?;
578 writer.write(&(ix.data.len() as u16).to_le_bytes())?;
579 }
580
581 for ix in &src.instructions {
582 writer.write(&ix.accounts)?;
583 writer.write(&ix.data)?;
584 }
585
586 writer.finish()?;
587
588 Ok(())
589 }
590}
591
592#[cfg(feature = "wincode")]
594#[inline]
595#[deprecated(since = "4.1.2", note = "use `Message::serialize` instead")]
596pub fn serialize(message: &Message) -> Vec<u8> {
597 wincode::serialize(message).unwrap()
598}
599
600#[cfg(feature = "wincode")]
601unsafe impl<'de, C: Config> SchemaRead<'de, C> for Message {
602 type Dst = Message;
603
604 #[expect(clippy::arithmetic_side_effects)]
605 fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
606 let (header, lifetime_specifier, config_mask, num_instructions, num_addresses) = {
607 let mut reader = unsafe { reader.as_trusted_for(FIXED_HEADER_SIZE)? };
614 let header = <MessageHeader as SchemaRead<C>>::get(reader.by_ref())?;
615 let config_mask = TransactionConfigMask(u32::from_le_bytes(reader.take_array()?));
616 let lifetime_specifier = <Hash as SchemaRead<C>>::get(reader.by_ref())?;
617 let num_instructions = reader.take_byte()? as usize;
618 let num_addresses = reader.take_byte()? as usize;
619 (
620 header,
621 lifetime_specifier,
622 config_mask,
623 num_instructions,
624 num_addresses,
625 )
626 };
627
628 if config_mask.has_unknown_bits() || config_mask.has_invalid_priority_fee_bits() {
633 return Err(wincode::error::invalid_value(
634 "invalid transaction config mask",
635 ));
636 }
637
638 <C::LengthEncoding as SeqLen<C>>::prealloc_check::<Address>(num_addresses)?;
639 let account_keys = <Vec<Address> as SchemaReadContext<C, context::Len>>::get_with_context(
640 context::Len(num_addresses),
641 reader.by_ref(),
642 )?;
643
644 let mut config = TransactionConfig::empty();
645 if config_mask.has_priority_fee() {
646 config.priority_fee = Some(u64::from_le_bytes(reader.take_array()?));
647 }
648 if config_mask.has_compute_unit_limit() {
649 config.compute_unit_limit = Some(u32::from_le_bytes(reader.take_array()?));
650 }
651 if config_mask.has_loaded_accounts_data_size() {
652 config.loaded_accounts_data_size_limit = Some(u32::from_le_bytes(reader.take_array()?));
653 }
654 if config_mask.has_heap_size() {
655 config.heap_size = Some(u32::from_le_bytes(reader.take_array()?));
656 }
657
658 let instruction_headers = unsafe {
666 from_raw_parts(
667 reader
668 .take_borrowed(num_instructions * size_of::<WireInstructionHeader>())?
669 .as_ptr() as *const WireInstructionHeader,
670 num_instructions,
671 )
672 };
673 let mut instructions = Vec::with_capacity(num_instructions);
674 for header in instruction_headers {
675 let program_id_index = header.program_id_index;
676 let num_accounts = header.num_accounts as usize;
677 let data_len = u16::from_le_bytes(header.data_len) as usize;
678
679 <C::LengthEncoding as SeqLen<C>>::prealloc_check::<u8>(num_accounts)?;
680 let accounts = <Vec<u8> as SchemaReadContext<C, context::Len>>::get_with_context(
681 context::Len(num_accounts),
682 reader.by_ref(),
683 )?;
684 <C::LengthEncoding as SeqLen<C>>::prealloc_check::<u8>(data_len)?;
685 let data = <Vec<u8> as SchemaReadContext<C, context::Len>>::get_with_context(
686 context::Len(data_len),
687 reader.by_ref(),
688 )?;
689
690 instructions.push(CompiledInstruction {
691 program_id_index,
692 accounts,
693 data,
694 });
695 }
696
697 dst.write(Message {
698 header,
699 lifetime_specifier,
700 config,
701 account_keys,
702 instructions,
703 });
704
705 Ok(())
706 }
707}
708
709#[cfg(feature = "wincode")]
712#[inline]
713pub fn deserialize(input: &[u8]) -> wincode::ReadResult<Message> {
714 wincode::deserialize(input)
715}
716
717#[cfg(test)]
718mod tests {
719 use {super::*, alloc::vec, solana_sdk_ids::bpf_loader_upgradeable};
720
721 #[derive(Debug, Clone, Default)]
727 pub struct MessageBuilder {
728 header: MessageHeader,
729 config: TransactionConfig,
730 lifetime_specifier: Option<Hash>,
731 account_keys: Vec<Address>,
732 instructions: Vec<CompiledInstruction>,
733 }
734
735 impl MessageBuilder {
736 pub fn new() -> Self {
737 Self::default()
738 }
739
740 #[must_use]
741 pub fn required_signatures(mut self, count: u8) -> Self {
742 self.header.num_required_signatures = count;
743 self
744 }
745
746 #[must_use]
747 pub fn readonly_signed_accounts(mut self, count: u8) -> Self {
748 self.header.num_readonly_signed_accounts = count;
749 self
750 }
751
752 #[must_use]
753 pub fn readonly_unsigned_accounts(mut self, count: u8) -> Self {
754 self.header.num_readonly_unsigned_accounts = count;
755 self
756 }
757
758 #[must_use]
759 pub fn lifetime_specifier(mut self, hash: Hash) -> Self {
760 self.lifetime_specifier = Some(hash);
761 self
762 }
763
764 #[must_use]
765 pub fn config(mut self, config: TransactionConfig) -> Self {
766 self.config = config;
767 self
768 }
769
770 #[must_use]
771 pub fn priority_fee(mut self, fee: u64) -> Self {
772 self.config.priority_fee = Some(fee);
773 self
774 }
775
776 #[must_use]
777 pub fn compute_unit_limit(mut self, limit: u32) -> Self {
778 self.config.compute_unit_limit = Some(limit);
779 self
780 }
781
782 #[must_use]
783 pub fn loaded_accounts_data_size_limit(mut self, limit: u32) -> Self {
784 self.config.loaded_accounts_data_size_limit = Some(limit);
785 self
786 }
787
788 #[must_use]
789 pub fn heap_size(mut self, size: u32) -> Self {
790 self.config.heap_size = Some(size);
791 self
792 }
793
794 #[must_use]
795 pub fn account(mut self, key: Address) -> Self {
796 self.account_keys.push(key);
797 self
798 }
799
800 #[must_use]
801 pub fn accounts(mut self, keys: Vec<Address>) -> Self {
802 self.account_keys = keys;
803 self
804 }
805
806 #[must_use]
807 pub fn instruction(mut self, instruction: CompiledInstruction) -> Self {
808 self.instructions.push(instruction);
809 self
810 }
811
812 #[must_use]
813 pub fn instructions(mut self, instructions: Vec<CompiledInstruction>) -> Self {
814 self.instructions = instructions;
815 self
816 }
817
818 pub fn build(self) -> Result<Message, MessageError> {
820 let lifetime_specifier = self
821 .lifetime_specifier
822 .ok_or(MessageError::MissingLifetimeSpecifier)?;
823
824 let message = Message::new(
825 self.header,
826 self.config,
827 lifetime_specifier,
828 self.account_keys,
829 self.instructions,
830 );
831
832 message.validate()?;
833
834 Ok(message)
835 }
836 }
837
838 fn create_test_message() -> Message {
839 MessageBuilder::new()
840 .required_signatures(1)
841 .readonly_unsigned_accounts(1)
842 .lifetime_specifier(Hash::new_unique())
843 .accounts(vec![
844 Address::new_unique(), Address::new_unique(), Address::new_unique(), ])
848 .compute_unit_limit(200_000)
849 .instruction(CompiledInstruction {
850 program_id_index: 1,
851 accounts: vec![0, 2],
852 data: vec![1, 2, 3, 4],
853 })
854 .build()
855 .unwrap()
856 }
857
858 #[test]
859 fn fee_payer_returns_first_account() {
860 let fee_payer = Address::new_unique();
861 let message = MessageBuilder::new()
862 .required_signatures(1)
863 .lifetime_specifier(Hash::new_unique())
864 .accounts(vec![fee_payer, Address::new_unique()])
865 .build()
866 .unwrap();
867
868 assert_eq!(message.fee_payer(), Some(&fee_payer));
869 }
870
871 #[test]
872 fn fee_payer_returns_none_for_empty_accounts() {
873 let message = Message::new(
875 MessageHeader::default(),
876 TransactionConfig::default(),
877 Hash::new_unique(),
878 vec![],
879 vec![],
880 );
881
882 assert_eq!(message.fee_payer(), None);
883 }
884
885 #[test]
886 fn is_signer_checks_signature_requirement() {
887 let message = create_test_message();
888 assert!(message.is_signer(0)); assert!(!message.is_signer(1)); assert!(!message.is_signer(2)); }
892
893 #[test]
894 fn is_signer_writable_identifies_writable_signers() {
895 let message = MessageBuilder::new()
896 .required_signatures(3)
897 .readonly_signed_accounts(1) .lifetime_specifier(Hash::new_unique())
899 .accounts(vec![
900 Address::new_unique(), Address::new_unique(), Address::new_unique(), Address::new_unique(), ])
905 .build()
906 .unwrap();
907
908 assert!(message.is_signer_writable(0));
910 assert!(message.is_signer_writable(1));
911 assert!(!message.is_signer_writable(2));
913 assert!(!message.is_signer_writable(3));
915 assert!(!message.is_signer_writable(100));
916 }
917
918 #[test]
919 fn is_signer_writable_all_writable_when_no_readonly() {
920 let message = MessageBuilder::new()
921 .required_signatures(2)
922 .readonly_signed_accounts(0) .lifetime_specifier(Hash::new_unique())
924 .accounts(vec![
925 Address::new_unique(),
926 Address::new_unique(),
927 Address::new_unique(),
928 ])
929 .build()
930 .unwrap();
931
932 assert!(message.is_signer_writable(0));
933 assert!(message.is_signer_writable(1));
934 assert!(!message.is_signer_writable(2)); }
936
937 #[test]
938 fn is_key_called_as_program_detects_program_indices() {
939 let message = create_test_message();
940 assert!(message.is_key_called_as_program(1));
942 assert!(!message.is_key_called_as_program(0));
943 assert!(!message.is_key_called_as_program(2));
944 assert!(!message.is_key_called_as_program(256));
946 assert!(!message.is_key_called_as_program(10_000));
947 }
948
949 #[test]
950 fn is_upgradeable_loader_present_detects_loader() {
951 let message = create_test_message();
952 assert!(!message.is_upgradeable_loader_present());
953
954 let mut message_with_loader = create_test_message();
955 message_with_loader
956 .account_keys
957 .push(bpf_loader_upgradeable::id());
958 assert!(message_with_loader.is_upgradeable_loader_present());
959 }
960
961 #[test]
962 fn is_writable_index_respects_header_layout() {
963 let message = create_test_message();
964 assert!(message.is_writable_index(0)); assert!(message.is_writable_index(1)); assert!(!message.is_writable_index(2)); }
969
970 #[test]
971 fn is_writable_index_handles_mixed_signer_permissions() {
972 let mut message = create_test_message();
973 message.header.num_required_signatures = 2;
975 message.header.num_readonly_signed_accounts = 1;
976 message.header.num_readonly_unsigned_accounts = 1;
977 message.account_keys = vec![
978 Address::new_unique(), Address::new_unique(), Address::new_unique(), ];
982 message.instructions[0].program_id_index = 2;
983 message.instructions[0].accounts = vec![0, 1];
984
985 assert!(message.sanitize().is_ok());
986 assert!(message.is_writable_index(0)); assert!(!message.is_writable_index(1)); assert!(!message.is_writable_index(2)); assert!(!message.is_writable_index(999)); }
991
992 #[test]
993 fn is_maybe_writable_returns_false_for_readonly_index() {
994 let message = create_test_message();
995 assert!(!message.is_writable_index(2));
997 assert!(!message.is_maybe_writable(2, None));
998 assert!(!message.is_maybe_writable(2, Some(&HashSet::new())));
1000 }
1001
1002 #[test]
1003 fn is_maybe_writable_demotes_reserved_accounts() {
1004 let message = create_test_message();
1005 let reserved = HashSet::from([message.account_keys[0]]);
1006 assert!(message.is_writable_index(0));
1008 assert!(!message.is_maybe_writable(0, Some(&reserved)));
1009 }
1010
1011 #[test]
1012 fn is_maybe_writable_demotes_programs_without_upgradeable_loader() {
1013 let message = create_test_message();
1014 assert!(message.is_writable_index(1));
1016 assert!(message.is_key_called_as_program(1));
1017 assert!(!message.is_upgradeable_loader_present());
1018 assert!(!message.is_maybe_writable(1, None));
1019 }
1020
1021 #[test]
1022 fn is_maybe_writable_preserves_programs_with_upgradeable_loader() {
1023 let mut message = create_test_message();
1024 message.account_keys.push(bpf_loader_upgradeable::id());
1026
1027 assert!(message.sanitize().is_ok());
1028 assert!(message.is_writable_index(1));
1029 assert!(message.is_key_called_as_program(1));
1030 assert!(message.is_upgradeable_loader_present());
1031 assert!(message.is_maybe_writable(1, None));
1033 }
1034
1035 #[test]
1036 fn sanitize_accepts_valid_message() {
1037 let message = create_test_message();
1038 assert!(message.sanitize().is_ok());
1039 }
1040
1041 #[test]
1042 fn sanitize_rejects_zero_signers() {
1043 let mut message = create_test_message();
1044 message.header.num_required_signatures = 0;
1045 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1046 }
1047
1048 #[test]
1049 fn sanitize_rejects_over_12_signatures() {
1050 let mut message = create_test_message();
1051 message.header.num_required_signatures = MAX_SIGNATURES + 1;
1052 message.account_keys = (0..MAX_SIGNATURES + 1)
1053 .map(|_| Address::new_unique())
1054 .collect();
1055 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1056 }
1057
1058 #[test]
1059 fn sanitize_rejects_over_64_addresses() {
1060 let mut message = create_test_message();
1061 message.account_keys = (0..65).map(|_| Address::new_unique()).collect();
1062 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1063 }
1064
1065 #[test]
1066 fn sanitize_rejects_over_64_instructions() {
1067 let mut message = create_test_message();
1068 message.instructions = (0..65) .map(|_| CompiledInstruction {
1070 program_id_index: 1,
1071 accounts: vec![0],
1072 data: vec![],
1073 })
1074 .collect();
1075 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1076 }
1077
1078 #[test]
1079 fn sanitize_rejects_insufficient_accounts_for_header() {
1080 let mut message = create_test_message();
1081 message.header.num_readonly_unsigned_accounts = 10;
1084 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1085 }
1086
1087 #[test]
1088 fn sanitize_rejects_all_signers_readonly() {
1089 let mut message = create_test_message();
1090 message.header.num_readonly_signed_accounts = 1; assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1092 }
1093
1094 #[test]
1095 fn sanitize_rejects_duplicate_addresses() {
1096 let mut message = create_test_message();
1097 let dup = message.account_keys[0];
1098 message.account_keys[1] = dup;
1099 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1100 }
1101
1102 #[test]
1103 fn sanitize_rejects_unaligned_heap_size() {
1104 let mut message = create_test_message();
1105 message.config.heap_size = Some(1025); assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1107 }
1108
1109 #[test]
1110 fn sanitize_rejects_heap_size_below_minimum() {
1111 let mut message = create_test_message();
1112 message.config.heap_size = Some(MIN_HEAP_SIZE - 1);
1113 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1114 }
1115
1116 #[test]
1117 fn sanitize_rejects_heap_size_above_maximum() {
1118 let mut message = create_test_message();
1119 message.config.heap_size = Some(MAX_HEAP_SIZE + 1);
1120 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1121 }
1122
1123 #[test]
1124 fn sanitize_accepts_minimum_heap_size() {
1125 let mut message = create_test_message();
1126 message.config.heap_size = Some(MIN_HEAP_SIZE);
1127 assert!(message.sanitize().is_ok());
1128 }
1129
1130 #[test]
1131 fn sanitize_accepts_maximum_heap_size() {
1132 let mut message = create_test_message();
1133 message.config.heap_size = Some(MAX_HEAP_SIZE);
1134 assert!(message.sanitize().is_ok());
1135 }
1136
1137 #[test]
1138 fn sanitize_accepts_aligned_heap_size() {
1139 let mut message = create_test_message();
1140 message.config.heap_size = Some(65536); assert!(message.sanitize().is_ok());
1142 }
1143
1144 #[test]
1145 fn sanitize_rejects_invalid_program_id_index() {
1146 let mut message = create_test_message();
1147 message.instructions[0].program_id_index = 99;
1148 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1149 }
1150
1151 #[test]
1152 fn sanitize_rejects_fee_payer_as_program() {
1153 let mut message = create_test_message();
1154 message.instructions[0].program_id_index = 0;
1155 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1156 }
1157
1158 #[test]
1159 fn sanitize_rejects_instruction_with_too_many_accounts() {
1160 let mut message = create_test_message();
1161 message.instructions[0].accounts = vec![0u8; (u8::MAX as usize) + 1];
1162 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1163 }
1164
1165 #[test]
1166 fn sanitize_rejects_invalid_instruction_account_index() {
1167 let mut message = create_test_message();
1168 message.instructions[0].accounts = vec![0, 99]; assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1170 }
1171
1172 #[test]
1173 fn sanitize_accepts_64_addresses() {
1174 let mut message = create_test_message();
1175 message.account_keys = (0..MAX_ADDRESSES).map(|_| Address::new_unique()).collect();
1176 message.header.num_required_signatures = 1;
1177 message.header.num_readonly_signed_accounts = 0;
1178 message.header.num_readonly_unsigned_accounts = 1;
1179 message.instructions[0].program_id_index = 1;
1180 message.instructions[0].accounts = vec![0, 2];
1181 assert!(message.sanitize().is_ok());
1182 }
1183
1184 #[test]
1185 fn sanitize_accepts_64_instructions() {
1186 let mut message = create_test_message();
1187 message.instructions = (0..MAX_INSTRUCTIONS)
1188 .map(|_| CompiledInstruction {
1189 program_id_index: 1,
1190 accounts: vec![0, 2],
1191 data: vec![1, 2, 3],
1192 })
1193 .collect();
1194 assert!(message.sanitize().is_ok());
1195 }
1196
1197 #[test]
1198 fn size_matches_serialized_length() {
1199 let test_cases = [
1200 MessageBuilder::new()
1202 .required_signatures(1)
1203 .lifetime_specifier(Hash::new_unique())
1204 .accounts(vec![Address::new_unique()])
1205 .build()
1206 .unwrap(),
1207 MessageBuilder::new()
1209 .required_signatures(1)
1210 .lifetime_specifier(Hash::new_unique())
1211 .accounts(vec![Address::new_unique(), Address::new_unique()])
1212 .priority_fee(1000)
1213 .compute_unit_limit(200_000)
1214 .instruction(CompiledInstruction {
1215 program_id_index: 1,
1216 accounts: vec![0],
1217 data: vec![1, 2, 3, 4],
1218 })
1219 .build()
1220 .unwrap(),
1221 MessageBuilder::new()
1223 .required_signatures(2)
1224 .readonly_signed_accounts(1)
1225 .readonly_unsigned_accounts(1)
1226 .lifetime_specifier(Hash::new_unique())
1227 .accounts(vec![
1228 Address::new_unique(),
1229 Address::new_unique(),
1230 Address::new_unique(),
1231 Address::new_unique(),
1232 ])
1233 .heap_size(65536)
1234 .instructions(vec![
1235 CompiledInstruction {
1236 program_id_index: 2,
1237 accounts: vec![0, 1],
1238 data: vec![],
1239 },
1240 CompiledInstruction {
1241 program_id_index: 3,
1242 accounts: vec![0, 1, 2],
1243 data: vec![0xAA; 100],
1244 },
1245 ])
1246 .build()
1247 .unwrap(),
1248 ];
1249
1250 for message in &test_cases {
1251 assert_eq!(message.size(), wincode::serialize(message).unwrap().len());
1252 }
1253 }
1254
1255 #[test]
1256 fn byte_layout_without_config() {
1257 let fee_payer = Address::new_from_array([1u8; 32]);
1258 let program = Address::new_from_array([2u8; 32]);
1259 let blockhash = Hash::new_from_array([0xAB; 32]);
1260
1261 let message = MessageBuilder::new()
1262 .required_signatures(1)
1263 .lifetime_specifier(blockhash)
1264 .accounts(vec![fee_payer, program])
1265 .instruction(CompiledInstruction {
1266 program_id_index: 1,
1267 accounts: vec![0],
1268 data: vec![0xDE, 0xAD],
1269 })
1270 .build()
1271 .unwrap();
1272
1273 let bytes = wincode::serialize(&message).unwrap();
1274
1275 let mut expected = vec![1, 0, 0];
1281 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);
1294 }
1295
1296 #[test]
1297 fn byte_layout_with_config() {
1298 let fee_payer = Address::new_from_array([1u8; 32]);
1299 let program = Address::new_from_array([2u8; 32]);
1300 let blockhash = Hash::new_from_array([0xBB; 32]);
1301
1302 let message = MessageBuilder::new()
1303 .required_signatures(1)
1304 .lifetime_specifier(blockhash)
1305 .accounts(vec![fee_payer, program])
1306 .priority_fee(0x0102030405060708u64)
1307 .compute_unit_limit(0x11223344u32)
1308 .instruction(CompiledInstruction {
1309 program_id_index: 1,
1310 accounts: vec![],
1311 data: vec![],
1312 })
1313 .build()
1314 .unwrap();
1315
1316 let bytes = wincode::serialize(&message).unwrap();
1317
1318 let mut expected = vec![1, 0, 0];
1319 expected.extend_from_slice(&7u32.to_le_bytes());
1321 expected.extend_from_slice(&[0xBB; 32]);
1322 expected.push(1);
1323 expected.push(2);
1324 expected.extend_from_slice(&[1u8; 32]);
1325 expected.extend_from_slice(&[2u8; 32]);
1326 expected.extend_from_slice(&0x0102030405060708u64.to_le_bytes());
1328 expected.extend_from_slice(&0x11223344u32.to_le_bytes());
1330 expected.push(1); expected.push(0); expected.extend_from_slice(&0u16.to_le_bytes()); assert_eq!(bytes, expected);
1335 }
1336
1337 #[test]
1338 fn roundtrip_preserves_all_config_fields() {
1339 let message = MessageBuilder::new()
1340 .required_signatures(1)
1341 .lifetime_specifier(Hash::new_unique())
1342 .accounts(vec![Address::new_unique(), Address::new_unique()])
1343 .priority_fee(1000)
1344 .compute_unit_limit(200_000)
1345 .loaded_accounts_data_size_limit(1_000_000)
1346 .heap_size(65536)
1347 .instruction(CompiledInstruction {
1348 program_id_index: 1,
1349 accounts: vec![0],
1350 data: vec![],
1351 })
1352 .build()
1353 .unwrap();
1354
1355 let serialized = wincode::serialize(&message).unwrap();
1356 let deserialized = deserialize(&serialized).unwrap();
1357 assert_eq!(message.config, deserialized.config);
1358 }
1359
1360 #[test]
1361 fn deserialize_rejects_unknown_config_mask_bits() {
1362 let message = MessageBuilder::default()
1363 .required_signatures(1)
1364 .lifetime_specifier(Hash::new_unique())
1365 .accounts(vec![Address::new_unique(), Address::new_unique()])
1366 .instruction(CompiledInstruction {
1367 program_id_index: 1,
1368 accounts: vec![0],
1369 data: vec![],
1370 })
1371 .build()
1372 .unwrap();
1373
1374 let mut serialized = wincode::serialize(&message).unwrap();
1375 assert!(deserialize(&serialized).is_ok());
1377
1378 let unknown_bit = 1u32 << TransactionConfigMask::KNOWN_BITS.trailing_ones();
1382 let mask = u32::from_le_bytes(serialized[3..7].try_into().unwrap()) | unknown_bit;
1383 serialized[3..7].copy_from_slice(&mask.to_le_bytes());
1384 assert!(deserialize(&serialized).is_err());
1385 }
1386
1387 #[test]
1388 fn deserialize_rejects_partial_priority_fee_bits() {
1389 let message = MessageBuilder::default()
1390 .required_signatures(1)
1391 .lifetime_specifier(Hash::new_unique())
1392 .accounts(vec![Address::new_unique(), Address::new_unique()])
1393 .instruction(CompiledInstruction {
1394 program_id_index: 1,
1395 accounts: vec![0],
1396 data: vec![],
1397 })
1398 .build()
1399 .unwrap();
1400
1401 let mut serialized = wincode::serialize(&message).unwrap();
1402 serialized[3] |= 0b1;
1404 assert!(deserialize(&serialized).is_err());
1405 }
1406}