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 = "wincode")]
43use {
44 crate::v1::{InstructionHeader, FIXED_HEADER_SIZE},
45 core::{mem::MaybeUninit, slice::from_raw_parts},
46 wincode::{
47 config::{Config, ConfigCore},
48 context,
49 io::{Reader, Writer},
50 len::SeqLen,
51 ReadResult, SchemaRead, SchemaReadContext, SchemaWrite, WriteResult,
52 },
53};
54use {
55 crate::{
56 compiled_instruction::CompiledInstruction,
57 compiled_keys::CompiledKeys,
58 v1::{
59 MessageError, TransactionConfig, TransactionConfigMask, MAX_ADDRESSES, MAX_HEAP_SIZE,
60 MAX_INSTRUCTIONS, MAX_SIGNATURES, MIN_HEAP_SIZE,
61 },
62 AccountKeys, CompileError, MessageHeader,
63 },
64 core::mem::size_of,
65 solana_address::Address,
66 solana_hash::Hash,
67 solana_instruction::Instruction,
68 solana_sanitize::{Sanitize, SanitizeError},
69 std::collections::HashSet,
70};
71
72#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
79#[cfg_attr(
80 feature = "serde",
81 derive(Serialize, Deserialize),
82 serde(rename_all = "camelCase")
83)]
84#[derive(Debug, Clone, PartialEq, Eq, Default)]
85pub struct Message {
86 pub header: MessageHeader,
88
89 pub config: TransactionConfig,
91
92 pub lifetime_specifier: Hash,
94
95 pub account_keys: Vec<Address>,
114
115 pub instructions: Vec<CompiledInstruction>,
117}
118
119impl Message {
120 pub fn new(
122 header: MessageHeader,
123 config: TransactionConfig,
124 lifetime_specifier: Hash,
125 account_keys: Vec<Address>,
126 instructions: Vec<CompiledInstruction>,
127 ) -> Self {
128 Self {
129 header,
130 config,
131 lifetime_specifier,
132 account_keys,
133 instructions,
134 }
135 }
136
137 pub fn try_compile(
212 payer: &Address,
213 instructions: &[Instruction],
214 recent_blockhash: Hash,
215 ) -> Result<Self, CompileError> {
216 Self::try_compile_with_config(
217 payer,
218 instructions,
219 recent_blockhash,
220 TransactionConfig::empty(),
221 )
222 }
223
224 pub fn try_compile_with_config(
300 payer: &Address,
301 instructions: &[Instruction],
302 recent_blockhash: Hash,
303 config: TransactionConfig,
304 ) -> Result<Self, CompileError> {
305 let compiled_keys = CompiledKeys::compile(instructions, Some(*payer));
306 let (header, static_keys) = compiled_keys.try_into_message_components()?;
307
308 let account_keys = AccountKeys::new(&static_keys, None);
309 let instructions = account_keys.try_compile_instructions(instructions)?;
310
311 Ok(Self {
312 header,
313 config,
314 lifetime_specifier: recent_blockhash,
315 account_keys: static_keys,
316 instructions,
317 })
318 }
319
320 pub fn fee_payer(&self) -> Option<&Address> {
322 self.account_keys.first()
323 }
324
325 pub fn is_signer(&self, index: usize) -> bool {
328 index < usize::from(self.header.num_required_signatures)
329 }
330
331 pub fn is_signer_writable(&self, index: usize) -> bool {
333 if !self.is_signer(index) {
334 return false;
335 }
336 let num_writable_signers = usize::from(self.header.num_required_signatures)
339 .saturating_sub(usize::from(self.header.num_readonly_signed_accounts));
340 index < num_writable_signers
341 }
342
343 pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
345 crate::is_key_called_as_program(&self.instructions, key_index)
346 }
347
348 #[inline(always)]
353 pub(crate) fn is_writable_index(&self, i: usize) -> bool {
354 crate::is_writable_index(i, self.header, &self.account_keys)
355 }
356
357 pub fn is_upgradeable_loader_present(&self) -> bool {
359 crate::is_upgradeable_loader_present(&self.account_keys)
360 }
361
362 pub fn is_maybe_writable(
377 &self,
378 key_index: usize,
379 reserved_account_keys: Option<&HashSet<Address>>,
380 ) -> bool {
381 crate::is_maybe_writable(
382 key_index,
383 self.header,
384 &self.account_keys,
385 &self.instructions,
386 reserved_account_keys,
387 )
388 }
389
390 pub fn demote_program_id(&self, i: usize) -> bool {
391 crate::is_program_id_write_demoted(i, &self.account_keys, &self.instructions)
392 }
393
394 #[cfg(feature = "wincode")]
396 pub fn serialize(&self) -> Vec<u8> {
397 wincode::serialize(&(crate::v1::V1_PREFIX, self)).unwrap()
398 }
399
400 #[allow(clippy::arithmetic_side_effects)]
402 #[inline(always)]
403 pub fn size(&self) -> usize {
404 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()
412 * (
413 size_of::<u8>()
414 + size_of::<u8>()
415 + size_of::<u16>()
416 ) + self
418 .instructions
419 .iter()
420 .map(|ix| {
421 (ix.accounts.len() * size_of::<u8>())
422 + ix.data.len()
423 })
424 .sum::<usize>() }
426
427 pub fn validate(&self) -> Result<(), MessageError> {
428 if self.header.num_required_signatures > MAX_SIGNATURES {
430 return Err(MessageError::TooManySignatures);
431 }
432
433 if self.instructions.len() > MAX_INSTRUCTIONS as usize {
435 return Err(MessageError::TooManyInstructions);
436 }
437
438 let num_account_keys = self.account_keys.len();
439
440 if num_account_keys > MAX_ADDRESSES as usize {
442 return Err(MessageError::TooManyAddresses);
443 }
444
445 let min_accounts = usize::from(self.header.num_required_signatures)
447 .saturating_add(usize::from(self.header.num_readonly_unsigned_accounts));
448
449 if num_account_keys < min_accounts {
450 return Err(MessageError::NotEnoughAddressesForSignatures);
451 }
452
453 if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures {
455 return Err(MessageError::ZeroSigners);
456 }
457
458 let unique_keys: HashSet<_> = self.account_keys.iter().collect();
460 if unique_keys.len() != num_account_keys {
461 return Err(MessageError::DuplicateAddresses);
462 }
463
464 let mask: TransactionConfigMask = self.config.into();
466
467 if mask.has_invalid_priority_fee_bits() {
468 return Err(MessageError::InvalidConfigMask);
469 }
470
471 if let Some(heap_size) = self.config.heap_size {
473 if heap_size % 1024 != 0 {
474 return Err(MessageError::InvalidHeapSize);
475 }
476
477 if !(MIN_HEAP_SIZE..=MAX_HEAP_SIZE).contains(&heap_size) {
478 return Err(MessageError::InvalidHeapSize);
479 }
480 }
481
482 let max_account_index = num_account_keys
484 .checked_sub(1)
485 .ok_or(MessageError::NotEnoughAccountKeys)?;
486
487 for instruction in &self.instructions {
488 if usize::from(instruction.program_id_index) > max_account_index {
490 return Err(MessageError::InvalidInstructionAccountIndex);
491 }
492
493 if instruction.program_id_index == 0 {
495 return Err(MessageError::InvalidInstructionAccountIndex);
496 }
497
498 if instruction.accounts.len() > u8::MAX as usize {
500 return Err(MessageError::InstructionAccountsTooLarge);
501 }
502
503 if instruction.data.len() > u16::MAX as usize {
505 return Err(MessageError::InstructionDataTooLarge);
506 }
507
508 for &account_index in &instruction.accounts {
510 if usize::from(account_index) > max_account_index {
511 return Err(MessageError::InvalidInstructionAccountIndex);
512 }
513 }
514 }
515
516 Ok(())
517 }
518}
519
520impl Sanitize for Message {
521 fn sanitize(&self) -> Result<(), SanitizeError> {
522 Ok(self.validate()?)
523 }
524}
525
526#[cfg(feature = "wincode")]
527unsafe impl<C: ConfigCore> SchemaWrite<C> for Message {
528 type Src = Self;
529
530 #[inline(always)]
531 fn size_of(src: &Self::Src) -> WriteResult<usize> {
532 Ok(src.size())
533 }
534
535 fn write(mut writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
536 let mut writer = unsafe { writer.as_trusted_for(src.size()) }?;
538 writer.write(&[
539 src.header.num_required_signatures,
540 src.header.num_readonly_signed_accounts,
541 src.header.num_readonly_unsigned_accounts,
542 ])?;
543 let mask = TransactionConfigMask::from(&src.config).0.to_le_bytes();
544 writer.write(&mask)?;
545 writer.write(src.lifetime_specifier.as_bytes())?;
546 writer.write(&[src.instructions.len() as u8, src.account_keys.len() as u8])?;
547
548 #[expect(clippy::arithmetic_side_effects)]
551 let account_keys = unsafe {
552 from_raw_parts(
553 src.account_keys.as_ptr().cast::<u8>(),
554 src.account_keys.len() * size_of::<Address>(),
555 )
556 };
557 writer.write(account_keys)?;
558
559 if let Some(value) = src.config.priority_fee {
560 writer.write(&value.to_le_bytes())?;
561 }
562 if let Some(value) = src.config.compute_unit_limit {
563 writer.write(&value.to_le_bytes())?;
564 }
565 if let Some(value) = src.config.loaded_accounts_data_size_limit {
566 writer.write(&value.to_le_bytes())?;
567 }
568 if let Some(value) = src.config.heap_size {
569 writer.write(&value.to_le_bytes())?;
570 }
571
572 for ix in &src.instructions {
573 writer.write(&[ix.program_id_index, ix.accounts.len() as u8])?;
574 writer.write(&(ix.data.len() as u16).to_le_bytes())?;
575 }
576
577 for ix in &src.instructions {
578 writer.write(&ix.accounts)?;
579 writer.write(&ix.data)?;
580 }
581
582 writer.finish()?;
583
584 Ok(())
585 }
586}
587
588#[cfg(feature = "wincode")]
590#[inline]
591#[deprecated(since = "4.1.2", note = "use `Message::serialize` instead")]
592pub fn serialize(message: &Message) -> Vec<u8> {
593 wincode::serialize(message).unwrap()
594}
595
596#[cfg(feature = "wincode")]
597unsafe impl<'de, C: Config> SchemaRead<'de, C> for Message {
598 type Dst = Message;
599
600 #[expect(clippy::arithmetic_side_effects)]
601 fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
602 let (header, lifetime_specifier, config_mask, num_instructions, num_addresses) = {
603 let mut reader = unsafe { reader.as_trusted_for(FIXED_HEADER_SIZE)? };
610 let header = <MessageHeader as SchemaRead<C>>::get(reader.by_ref())?;
611 let config_mask = TransactionConfigMask(u32::from_le_bytes(reader.take_array()?));
612 let lifetime_specifier = <Hash as SchemaRead<C>>::get(reader.by_ref())?;
613 let num_instructions = reader.take_byte()? as usize;
614 let num_addresses = reader.take_byte()? as usize;
615 (
616 header,
617 lifetime_specifier,
618 config_mask,
619 num_instructions,
620 num_addresses,
621 )
622 };
623
624 <C::LengthEncoding as SeqLen<C>>::prealloc_check::<Address>(num_addresses)?;
625 let account_keys = <Vec<Address> as SchemaReadContext<C, context::Len>>::get_with_context(
626 context::Len(num_addresses),
627 reader.by_ref(),
628 )?;
629
630 let mut config = TransactionConfig::empty();
631 if config_mask.has_priority_fee() {
632 config.priority_fee = Some(u64::from_le_bytes(reader.take_array()?));
633 }
634 if config_mask.has_compute_unit_limit() {
635 config.compute_unit_limit = Some(u32::from_le_bytes(reader.take_array()?));
636 }
637 if config_mask.has_loaded_accounts_data_size() {
638 config.loaded_accounts_data_size_limit = Some(u32::from_le_bytes(reader.take_array()?));
639 }
640 if config_mask.has_heap_size() {
641 config.heap_size = Some(u32::from_le_bytes(reader.take_array()?));
642 }
643
644 let instruction_headers = unsafe {
652 from_raw_parts(
653 reader
654 .take_borrowed(num_instructions * size_of::<InstructionHeader>())?
655 .as_ptr() as *const InstructionHeader,
656 num_instructions,
657 )
658 };
659 let mut instructions = Vec::with_capacity(num_instructions);
660 for header in instruction_headers {
661 let program_id_index = header.0;
662 let num_accounts = header.1 as usize;
663 let data_len = u16::from_le_bytes(header.2) as usize;
664
665 <C::LengthEncoding as SeqLen<C>>::prealloc_check::<u8>(num_accounts)?;
666 let accounts = <Vec<u8> as SchemaReadContext<C, context::Len>>::get_with_context(
667 context::Len(num_accounts),
668 reader.by_ref(),
669 )?;
670 <C::LengthEncoding as SeqLen<C>>::prealloc_check::<u8>(data_len)?;
671 let data = <Vec<u8> as SchemaReadContext<C, context::Len>>::get_with_context(
672 context::Len(data_len),
673 reader.by_ref(),
674 )?;
675
676 instructions.push(CompiledInstruction {
677 program_id_index,
678 accounts,
679 data,
680 });
681 }
682
683 dst.write(Message {
684 header,
685 lifetime_specifier,
686 config,
687 account_keys,
688 instructions,
689 });
690
691 Ok(())
692 }
693}
694
695#[cfg(feature = "wincode")]
698#[inline]
699pub fn deserialize(input: &[u8]) -> wincode::ReadResult<Message> {
700 wincode::deserialize(input)
701}
702
703#[cfg(test)]
704mod tests {
705 use {super::*, solana_sdk_ids::bpf_loader_upgradeable};
706
707 #[derive(Debug, Clone, Default)]
713 pub struct MessageBuilder {
714 header: MessageHeader,
715 config: TransactionConfig,
716 lifetime_specifier: Option<Hash>,
717 account_keys: Vec<Address>,
718 instructions: Vec<CompiledInstruction>,
719 }
720
721 impl MessageBuilder {
722 pub fn new() -> Self {
723 Self::default()
724 }
725
726 #[must_use]
727 pub fn required_signatures(mut self, count: u8) -> Self {
728 self.header.num_required_signatures = count;
729 self
730 }
731
732 #[must_use]
733 pub fn readonly_signed_accounts(mut self, count: u8) -> Self {
734 self.header.num_readonly_signed_accounts = count;
735 self
736 }
737
738 #[must_use]
739 pub fn readonly_unsigned_accounts(mut self, count: u8) -> Self {
740 self.header.num_readonly_unsigned_accounts = count;
741 self
742 }
743
744 #[must_use]
745 pub fn lifetime_specifier(mut self, hash: Hash) -> Self {
746 self.lifetime_specifier = Some(hash);
747 self
748 }
749
750 #[must_use]
751 pub fn config(mut self, config: TransactionConfig) -> Self {
752 self.config = config;
753 self
754 }
755
756 #[must_use]
757 pub fn priority_fee(mut self, fee: u64) -> Self {
758 self.config.priority_fee = Some(fee);
759 self
760 }
761
762 #[must_use]
763 pub fn compute_unit_limit(mut self, limit: u32) -> Self {
764 self.config.compute_unit_limit = Some(limit);
765 self
766 }
767
768 #[must_use]
769 pub fn loaded_accounts_data_size_limit(mut self, limit: u32) -> Self {
770 self.config.loaded_accounts_data_size_limit = Some(limit);
771 self
772 }
773
774 #[must_use]
775 pub fn heap_size(mut self, size: u32) -> Self {
776 self.config.heap_size = Some(size);
777 self
778 }
779
780 #[must_use]
781 pub fn account(mut self, key: Address) -> Self {
782 self.account_keys.push(key);
783 self
784 }
785
786 #[must_use]
787 pub fn accounts(mut self, keys: Vec<Address>) -> Self {
788 self.account_keys = keys;
789 self
790 }
791
792 #[must_use]
793 pub fn instruction(mut self, instruction: CompiledInstruction) -> Self {
794 self.instructions.push(instruction);
795 self
796 }
797
798 #[must_use]
799 pub fn instructions(mut self, instructions: Vec<CompiledInstruction>) -> Self {
800 self.instructions = instructions;
801 self
802 }
803
804 pub fn build(self) -> Result<Message, MessageError> {
806 let lifetime_specifier = self
807 .lifetime_specifier
808 .ok_or(MessageError::MissingLifetimeSpecifier)?;
809
810 let message = Message::new(
811 self.header,
812 self.config,
813 lifetime_specifier,
814 self.account_keys,
815 self.instructions,
816 );
817
818 message.validate()?;
819
820 Ok(message)
821 }
822 }
823
824 fn create_test_message() -> Message {
825 MessageBuilder::new()
826 .required_signatures(1)
827 .readonly_unsigned_accounts(1)
828 .lifetime_specifier(Hash::new_unique())
829 .accounts(vec![
830 Address::new_unique(), Address::new_unique(), Address::new_unique(), ])
834 .compute_unit_limit(200_000)
835 .instruction(CompiledInstruction {
836 program_id_index: 1,
837 accounts: vec![0, 2],
838 data: vec![1, 2, 3, 4],
839 })
840 .build()
841 .unwrap()
842 }
843
844 #[test]
845 fn fee_payer_returns_first_account() {
846 let fee_payer = Address::new_unique();
847 let message = MessageBuilder::new()
848 .required_signatures(1)
849 .lifetime_specifier(Hash::new_unique())
850 .accounts(vec![fee_payer, Address::new_unique()])
851 .build()
852 .unwrap();
853
854 assert_eq!(message.fee_payer(), Some(&fee_payer));
855 }
856
857 #[test]
858 fn fee_payer_returns_none_for_empty_accounts() {
859 let message = Message::new(
861 MessageHeader::default(),
862 TransactionConfig::default(),
863 Hash::new_unique(),
864 vec![],
865 vec![],
866 );
867
868 assert_eq!(message.fee_payer(), None);
869 }
870
871 #[test]
872 fn is_signer_checks_signature_requirement() {
873 let message = create_test_message();
874 assert!(message.is_signer(0)); assert!(!message.is_signer(1)); assert!(!message.is_signer(2)); }
878
879 #[test]
880 fn is_signer_writable_identifies_writable_signers() {
881 let message = MessageBuilder::new()
882 .required_signatures(3)
883 .readonly_signed_accounts(1) .lifetime_specifier(Hash::new_unique())
885 .accounts(vec![
886 Address::new_unique(), Address::new_unique(), Address::new_unique(), Address::new_unique(), ])
891 .build()
892 .unwrap();
893
894 assert!(message.is_signer_writable(0));
896 assert!(message.is_signer_writable(1));
897 assert!(!message.is_signer_writable(2));
899 assert!(!message.is_signer_writable(3));
901 assert!(!message.is_signer_writable(100));
902 }
903
904 #[test]
905 fn is_signer_writable_all_writable_when_no_readonly() {
906 let message = MessageBuilder::new()
907 .required_signatures(2)
908 .readonly_signed_accounts(0) .lifetime_specifier(Hash::new_unique())
910 .accounts(vec![
911 Address::new_unique(),
912 Address::new_unique(),
913 Address::new_unique(),
914 ])
915 .build()
916 .unwrap();
917
918 assert!(message.is_signer_writable(0));
919 assert!(message.is_signer_writable(1));
920 assert!(!message.is_signer_writable(2)); }
922
923 #[test]
924 fn is_key_called_as_program_detects_program_indices() {
925 let message = create_test_message();
926 assert!(message.is_key_called_as_program(1));
928 assert!(!message.is_key_called_as_program(0));
929 assert!(!message.is_key_called_as_program(2));
930 assert!(!message.is_key_called_as_program(256));
932 assert!(!message.is_key_called_as_program(10_000));
933 }
934
935 #[test]
936 fn is_upgradeable_loader_present_detects_loader() {
937 let message = create_test_message();
938 assert!(!message.is_upgradeable_loader_present());
939
940 let mut message_with_loader = create_test_message();
941 message_with_loader
942 .account_keys
943 .push(bpf_loader_upgradeable::id());
944 assert!(message_with_loader.is_upgradeable_loader_present());
945 }
946
947 #[test]
948 fn is_writable_index_respects_header_layout() {
949 let message = create_test_message();
950 assert!(message.is_writable_index(0)); assert!(message.is_writable_index(1)); assert!(!message.is_writable_index(2)); }
955
956 #[test]
957 fn is_writable_index_handles_mixed_signer_permissions() {
958 let mut message = create_test_message();
959 message.header.num_required_signatures = 2;
961 message.header.num_readonly_signed_accounts = 1;
962 message.header.num_readonly_unsigned_accounts = 1;
963 message.account_keys = vec![
964 Address::new_unique(), Address::new_unique(), Address::new_unique(), ];
968 message.instructions[0].program_id_index = 2;
969 message.instructions[0].accounts = vec![0, 1];
970
971 assert!(message.sanitize().is_ok());
972 assert!(message.is_writable_index(0)); assert!(!message.is_writable_index(1)); assert!(!message.is_writable_index(2)); assert!(!message.is_writable_index(999)); }
977
978 #[test]
979 fn is_maybe_writable_returns_false_for_readonly_index() {
980 let message = create_test_message();
981 assert!(!message.is_writable_index(2));
983 assert!(!message.is_maybe_writable(2, None));
984 assert!(!message.is_maybe_writable(2, Some(&HashSet::new())));
986 }
987
988 #[test]
989 fn is_maybe_writable_demotes_reserved_accounts() {
990 let message = create_test_message();
991 let reserved = HashSet::from([message.account_keys[0]]);
992 assert!(message.is_writable_index(0));
994 assert!(!message.is_maybe_writable(0, Some(&reserved)));
995 }
996
997 #[test]
998 fn is_maybe_writable_demotes_programs_without_upgradeable_loader() {
999 let message = create_test_message();
1000 assert!(message.is_writable_index(1));
1002 assert!(message.is_key_called_as_program(1));
1003 assert!(!message.is_upgradeable_loader_present());
1004 assert!(!message.is_maybe_writable(1, None));
1005 }
1006
1007 #[test]
1008 fn is_maybe_writable_preserves_programs_with_upgradeable_loader() {
1009 let mut message = create_test_message();
1010 message.account_keys.push(bpf_loader_upgradeable::id());
1012
1013 assert!(message.sanitize().is_ok());
1014 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));
1019 }
1020
1021 #[test]
1022 fn sanitize_accepts_valid_message() {
1023 let message = create_test_message();
1024 assert!(message.sanitize().is_ok());
1025 }
1026
1027 #[test]
1028 fn sanitize_rejects_zero_signers() {
1029 let mut message = create_test_message();
1030 message.header.num_required_signatures = 0;
1031 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1032 }
1033
1034 #[test]
1035 fn sanitize_rejects_over_12_signatures() {
1036 let mut message = create_test_message();
1037 message.header.num_required_signatures = MAX_SIGNATURES + 1;
1038 message.account_keys = (0..MAX_SIGNATURES + 1)
1039 .map(|_| Address::new_unique())
1040 .collect();
1041 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1042 }
1043
1044 #[test]
1045 fn sanitize_rejects_over_64_addresses() {
1046 let mut message = create_test_message();
1047 message.account_keys = (0..65).map(|_| Address::new_unique()).collect();
1048 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1049 }
1050
1051 #[test]
1052 fn sanitize_rejects_over_64_instructions() {
1053 let mut message = create_test_message();
1054 message.instructions = (0..65) .map(|_| CompiledInstruction {
1056 program_id_index: 1,
1057 accounts: vec![0],
1058 data: vec![],
1059 })
1060 .collect();
1061 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1062 }
1063
1064 #[test]
1065 fn sanitize_rejects_insufficient_accounts_for_header() {
1066 let mut message = create_test_message();
1067 message.header.num_readonly_unsigned_accounts = 10;
1070 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1071 }
1072
1073 #[test]
1074 fn sanitize_rejects_all_signers_readonly() {
1075 let mut message = create_test_message();
1076 message.header.num_readonly_signed_accounts = 1; assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1078 }
1079
1080 #[test]
1081 fn sanitize_rejects_duplicate_addresses() {
1082 let mut message = create_test_message();
1083 let dup = message.account_keys[0];
1084 message.account_keys[1] = dup;
1085 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1086 }
1087
1088 #[test]
1089 fn sanitize_rejects_unaligned_heap_size() {
1090 let mut message = create_test_message();
1091 message.config.heap_size = Some(1025); assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1093 }
1094
1095 #[test]
1096 fn sanitize_rejects_heap_size_below_minimum() {
1097 let mut message = create_test_message();
1098 message.config.heap_size = Some(MIN_HEAP_SIZE - 1);
1099 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1100 }
1101
1102 #[test]
1103 fn sanitize_rejects_heap_size_above_maximum() {
1104 let mut message = create_test_message();
1105 message.config.heap_size = Some(MAX_HEAP_SIZE + 1);
1106 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1107 }
1108
1109 #[test]
1110 fn sanitize_accepts_minimum_heap_size() {
1111 let mut message = create_test_message();
1112 message.config.heap_size = Some(MIN_HEAP_SIZE);
1113 assert!(message.sanitize().is_ok());
1114 }
1115
1116 #[test]
1117 fn sanitize_accepts_maximum_heap_size() {
1118 let mut message = create_test_message();
1119 message.config.heap_size = Some(MAX_HEAP_SIZE);
1120 assert!(message.sanitize().is_ok());
1121 }
1122
1123 #[test]
1124 fn sanitize_accepts_aligned_heap_size() {
1125 let mut message = create_test_message();
1126 message.config.heap_size = Some(65536); assert!(message.sanitize().is_ok());
1128 }
1129
1130 #[test]
1131 fn sanitize_rejects_invalid_program_id_index() {
1132 let mut message = create_test_message();
1133 message.instructions[0].program_id_index = 99;
1134 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1135 }
1136
1137 #[test]
1138 fn sanitize_rejects_fee_payer_as_program() {
1139 let mut message = create_test_message();
1140 message.instructions[0].program_id_index = 0;
1141 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1142 }
1143
1144 #[test]
1145 fn sanitize_rejects_instruction_with_too_many_accounts() {
1146 let mut message = create_test_message();
1147 message.instructions[0].accounts = vec![0u8; (u8::MAX as usize) + 1];
1148 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1149 }
1150
1151 #[test]
1152 fn sanitize_rejects_invalid_instruction_account_index() {
1153 let mut message = create_test_message();
1154 message.instructions[0].accounts = vec![0, 99]; assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1156 }
1157
1158 #[test]
1159 fn sanitize_accepts_64_addresses() {
1160 let mut message = create_test_message();
1161 message.account_keys = (0..MAX_ADDRESSES).map(|_| Address::new_unique()).collect();
1162 message.header.num_required_signatures = 1;
1163 message.header.num_readonly_signed_accounts = 0;
1164 message.header.num_readonly_unsigned_accounts = 1;
1165 message.instructions[0].program_id_index = 1;
1166 message.instructions[0].accounts = vec![0, 2];
1167 assert!(message.sanitize().is_ok());
1168 }
1169
1170 #[test]
1171 fn sanitize_accepts_64_instructions() {
1172 let mut message = create_test_message();
1173 message.instructions = (0..MAX_INSTRUCTIONS)
1174 .map(|_| CompiledInstruction {
1175 program_id_index: 1,
1176 accounts: vec![0, 2],
1177 data: vec![1, 2, 3],
1178 })
1179 .collect();
1180 assert!(message.sanitize().is_ok());
1181 }
1182
1183 #[test]
1184 fn size_matches_serialized_length() {
1185 let test_cases = [
1186 MessageBuilder::new()
1188 .required_signatures(1)
1189 .lifetime_specifier(Hash::new_unique())
1190 .accounts(vec![Address::new_unique()])
1191 .build()
1192 .unwrap(),
1193 MessageBuilder::new()
1195 .required_signatures(1)
1196 .lifetime_specifier(Hash::new_unique())
1197 .accounts(vec![Address::new_unique(), Address::new_unique()])
1198 .priority_fee(1000)
1199 .compute_unit_limit(200_000)
1200 .instruction(CompiledInstruction {
1201 program_id_index: 1,
1202 accounts: vec![0],
1203 data: vec![1, 2, 3, 4],
1204 })
1205 .build()
1206 .unwrap(),
1207 MessageBuilder::new()
1209 .required_signatures(2)
1210 .readonly_signed_accounts(1)
1211 .readonly_unsigned_accounts(1)
1212 .lifetime_specifier(Hash::new_unique())
1213 .accounts(vec![
1214 Address::new_unique(),
1215 Address::new_unique(),
1216 Address::new_unique(),
1217 Address::new_unique(),
1218 ])
1219 .heap_size(65536)
1220 .instructions(vec![
1221 CompiledInstruction {
1222 program_id_index: 2,
1223 accounts: vec![0, 1],
1224 data: vec![],
1225 },
1226 CompiledInstruction {
1227 program_id_index: 3,
1228 accounts: vec![0, 1, 2],
1229 data: vec![0xAA; 100],
1230 },
1231 ])
1232 .build()
1233 .unwrap(),
1234 ];
1235
1236 for message in &test_cases {
1237 assert_eq!(message.size(), wincode::serialize(message).unwrap().len());
1238 }
1239 }
1240
1241 #[test]
1242 fn byte_layout_without_config() {
1243 let fee_payer = Address::new_from_array([1u8; 32]);
1244 let program = Address::new_from_array([2u8; 32]);
1245 let blockhash = Hash::new_from_array([0xAB; 32]);
1246
1247 let message = MessageBuilder::new()
1248 .required_signatures(1)
1249 .lifetime_specifier(blockhash)
1250 .accounts(vec![fee_payer, program])
1251 .instruction(CompiledInstruction {
1252 program_id_index: 1,
1253 accounts: vec![0],
1254 data: vec![0xDE, 0xAD],
1255 })
1256 .build()
1257 .unwrap();
1258
1259 let bytes = wincode::serialize(&message).unwrap();
1260
1261 let mut expected = vec![1, 0, 0];
1267 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);
1280 }
1281
1282 #[test]
1283 fn byte_layout_with_config() {
1284 let fee_payer = Address::new_from_array([1u8; 32]);
1285 let program = Address::new_from_array([2u8; 32]);
1286 let blockhash = Hash::new_from_array([0xBB; 32]);
1287
1288 let message = MessageBuilder::new()
1289 .required_signatures(1)
1290 .lifetime_specifier(blockhash)
1291 .accounts(vec![fee_payer, program])
1292 .priority_fee(0x0102030405060708u64)
1293 .compute_unit_limit(0x11223344u32)
1294 .instruction(CompiledInstruction {
1295 program_id_index: 1,
1296 accounts: vec![],
1297 data: vec![],
1298 })
1299 .build()
1300 .unwrap();
1301
1302 let bytes = wincode::serialize(&message).unwrap();
1303
1304 let mut expected = vec![1, 0, 0];
1305 expected.extend_from_slice(&7u32.to_le_bytes());
1307 expected.extend_from_slice(&[0xBB; 32]);
1308 expected.push(1);
1309 expected.push(2);
1310 expected.extend_from_slice(&[1u8; 32]);
1311 expected.extend_from_slice(&[2u8; 32]);
1312 expected.extend_from_slice(&0x0102030405060708u64.to_le_bytes());
1314 expected.extend_from_slice(&0x11223344u32.to_le_bytes());
1316 expected.push(1); expected.push(0); expected.extend_from_slice(&0u16.to_le_bytes()); assert_eq!(bytes, expected);
1321 }
1322
1323 #[test]
1324 fn roundtrip_preserves_all_config_fields() {
1325 let message = MessageBuilder::new()
1326 .required_signatures(1)
1327 .lifetime_specifier(Hash::new_unique())
1328 .accounts(vec![Address::new_unique(), Address::new_unique()])
1329 .priority_fee(1000)
1330 .compute_unit_limit(200_000)
1331 .loaded_accounts_data_size_limit(1_000_000)
1332 .heap_size(65536)
1333 .instruction(CompiledInstruction {
1334 program_id_index: 1,
1335 accounts: vec![0],
1336 data: vec![],
1337 })
1338 .build()
1339 .unwrap();
1340
1341 let serialized = wincode::serialize(&message).unwrap();
1342 let deserialized = deserialize(&serialized).unwrap();
1343 assert_eq!(message.config, deserialized.config);
1344 }
1345}