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 let mask: TransactionConfigMask = self.config.into();
472
473 if mask.has_invalid_priority_fee_bits() {
474 return Err(MessageError::InvalidConfigMask);
475 }
476
477 if let Some(heap_size) = self.config.heap_size {
479 if !heap_size.is_multiple_of(1024) {
480 return Err(MessageError::InvalidHeapSize);
481 }
482
483 if !(MIN_HEAP_SIZE..=MAX_HEAP_SIZE).contains(&heap_size) {
484 return Err(MessageError::InvalidHeapSize);
485 }
486 }
487
488 let max_account_index = num_account_keys
490 .checked_sub(1)
491 .ok_or(MessageError::NotEnoughAccountKeys)?;
492
493 for instruction in &self.instructions {
494 if usize::from(instruction.program_id_index) > max_account_index {
496 return Err(MessageError::InvalidInstructionAccountIndex);
497 }
498
499 if instruction.program_id_index == 0 {
501 return Err(MessageError::InvalidInstructionAccountIndex);
502 }
503
504 if instruction.accounts.len() > u8::MAX as usize {
506 return Err(MessageError::InstructionAccountsTooLarge);
507 }
508
509 if instruction.data.len() > u16::MAX as usize {
511 return Err(MessageError::InstructionDataTooLarge);
512 }
513
514 for &account_index in &instruction.accounts {
516 if usize::from(account_index) > max_account_index {
517 return Err(MessageError::InvalidInstructionAccountIndex);
518 }
519 }
520 }
521
522 Ok(())
523 }
524}
525
526impl Sanitize for Message {
527 fn sanitize(&self) -> Result<(), SanitizeError> {
528 Ok(self.validate()?)
529 }
530}
531
532#[cfg(feature = "wincode")]
533unsafe impl<C: ConfigCore> SchemaWrite<C> for Message {
534 type Src = Self;
535
536 #[inline(always)]
537 fn size_of(src: &Self::Src) -> WriteResult<usize> {
538 Ok(src.size())
539 }
540
541 fn write(mut writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
542 let mut writer = unsafe { writer.as_trusted_for(src.size()) }?;
544 writer.write(&[
545 src.header.num_required_signatures,
546 src.header.num_readonly_signed_accounts,
547 src.header.num_readonly_unsigned_accounts,
548 ])?;
549 let mask = TransactionConfigMask::from(&src.config).0.to_le_bytes();
550 writer.write(&mask)?;
551 writer.write(src.lifetime_specifier.as_bytes())?;
552 writer.write(&[src.instructions.len() as u8, src.account_keys.len() as u8])?;
553
554 #[expect(clippy::arithmetic_side_effects)]
557 let account_keys = unsafe {
558 from_raw_parts(
559 src.account_keys.as_ptr().cast::<u8>(),
560 src.account_keys.len() * size_of::<Address>(),
561 )
562 };
563 writer.write(account_keys)?;
564
565 if let Some(value) = src.config.priority_fee {
566 writer.write(&value.to_le_bytes())?;
567 }
568 if let Some(value) = src.config.compute_unit_limit {
569 writer.write(&value.to_le_bytes())?;
570 }
571 if let Some(value) = src.config.loaded_accounts_data_size_limit {
572 writer.write(&value.to_le_bytes())?;
573 }
574 if let Some(value) = src.config.heap_size {
575 writer.write(&value.to_le_bytes())?;
576 }
577
578 for ix in &src.instructions {
579 writer.write(&[ix.program_id_index, ix.accounts.len() as u8])?;
580 writer.write(&(ix.data.len() as u16).to_le_bytes())?;
581 }
582
583 for ix in &src.instructions {
584 writer.write(&ix.accounts)?;
585 writer.write(&ix.data)?;
586 }
587
588 writer.finish()?;
589
590 Ok(())
591 }
592}
593
594#[cfg(feature = "wincode")]
596#[inline]
597#[deprecated(since = "4.1.2", note = "use `Message::serialize` instead")]
598pub fn serialize(message: &Message) -> Vec<u8> {
599 wincode::serialize(message).unwrap()
600}
601
602#[cfg(feature = "wincode")]
603unsafe impl<'de, C: Config> SchemaRead<'de, C> for Message {
604 type Dst = Message;
605
606 #[expect(clippy::arithmetic_side_effects)]
607 fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
608 let (header, lifetime_specifier, config_mask, num_instructions, num_addresses) = {
609 let mut reader = unsafe { reader.as_trusted_for(FIXED_HEADER_SIZE)? };
616 let header = <MessageHeader as SchemaRead<C>>::get(reader.by_ref())?;
617 let config_mask = TransactionConfigMask(u32::from_le_bytes(reader.take_array()?));
618 let lifetime_specifier = <Hash as SchemaRead<C>>::get(reader.by_ref())?;
619 let num_instructions = reader.take_byte()? as usize;
620 let num_addresses = reader.take_byte()? as usize;
621 (
622 header,
623 lifetime_specifier,
624 config_mask,
625 num_instructions,
626 num_addresses,
627 )
628 };
629
630 <C::LengthEncoding as SeqLen<C>>::prealloc_check::<Address>(num_addresses)?;
631 let account_keys = <Vec<Address> as SchemaReadContext<C, context::Len>>::get_with_context(
632 context::Len(num_addresses),
633 reader.by_ref(),
634 )?;
635
636 let mut config = TransactionConfig::empty();
637 if config_mask.has_priority_fee() {
638 config.priority_fee = Some(u64::from_le_bytes(reader.take_array()?));
639 }
640 if config_mask.has_compute_unit_limit() {
641 config.compute_unit_limit = Some(u32::from_le_bytes(reader.take_array()?));
642 }
643 if config_mask.has_loaded_accounts_data_size() {
644 config.loaded_accounts_data_size_limit = Some(u32::from_le_bytes(reader.take_array()?));
645 }
646 if config_mask.has_heap_size() {
647 config.heap_size = Some(u32::from_le_bytes(reader.take_array()?));
648 }
649
650 let instruction_headers = unsafe {
658 from_raw_parts(
659 reader
660 .take_borrowed(num_instructions * size_of::<InstructionHeader>())?
661 .as_ptr() as *const InstructionHeader,
662 num_instructions,
663 )
664 };
665 let mut instructions = Vec::with_capacity(num_instructions);
666 for header in instruction_headers {
667 let program_id_index = header.0;
668 let num_accounts = header.1 as usize;
669 let data_len = u16::from_le_bytes(header.2) as usize;
670
671 <C::LengthEncoding as SeqLen<C>>::prealloc_check::<u8>(num_accounts)?;
672 let accounts = <Vec<u8> as SchemaReadContext<C, context::Len>>::get_with_context(
673 context::Len(num_accounts),
674 reader.by_ref(),
675 )?;
676 <C::LengthEncoding as SeqLen<C>>::prealloc_check::<u8>(data_len)?;
677 let data = <Vec<u8> as SchemaReadContext<C, context::Len>>::get_with_context(
678 context::Len(data_len),
679 reader.by_ref(),
680 )?;
681
682 instructions.push(CompiledInstruction {
683 program_id_index,
684 accounts,
685 data,
686 });
687 }
688
689 dst.write(Message {
690 header,
691 lifetime_specifier,
692 config,
693 account_keys,
694 instructions,
695 });
696
697 Ok(())
698 }
699}
700
701#[cfg(feature = "wincode")]
704#[inline]
705pub fn deserialize(input: &[u8]) -> wincode::ReadResult<Message> {
706 wincode::deserialize(input)
707}
708
709#[cfg(test)]
710mod tests {
711 use {super::*, alloc::vec, solana_sdk_ids::bpf_loader_upgradeable};
712
713 #[derive(Debug, Clone, Default)]
719 pub struct MessageBuilder {
720 header: MessageHeader,
721 config: TransactionConfig,
722 lifetime_specifier: Option<Hash>,
723 account_keys: Vec<Address>,
724 instructions: Vec<CompiledInstruction>,
725 }
726
727 impl MessageBuilder {
728 pub fn new() -> Self {
729 Self::default()
730 }
731
732 #[must_use]
733 pub fn required_signatures(mut self, count: u8) -> Self {
734 self.header.num_required_signatures = count;
735 self
736 }
737
738 #[must_use]
739 pub fn readonly_signed_accounts(mut self, count: u8) -> Self {
740 self.header.num_readonly_signed_accounts = count;
741 self
742 }
743
744 #[must_use]
745 pub fn readonly_unsigned_accounts(mut self, count: u8) -> Self {
746 self.header.num_readonly_unsigned_accounts = count;
747 self
748 }
749
750 #[must_use]
751 pub fn lifetime_specifier(mut self, hash: Hash) -> Self {
752 self.lifetime_specifier = Some(hash);
753 self
754 }
755
756 #[must_use]
757 pub fn config(mut self, config: TransactionConfig) -> Self {
758 self.config = config;
759 self
760 }
761
762 #[must_use]
763 pub fn priority_fee(mut self, fee: u64) -> Self {
764 self.config.priority_fee = Some(fee);
765 self
766 }
767
768 #[must_use]
769 pub fn compute_unit_limit(mut self, limit: u32) -> Self {
770 self.config.compute_unit_limit = Some(limit);
771 self
772 }
773
774 #[must_use]
775 pub fn loaded_accounts_data_size_limit(mut self, limit: u32) -> Self {
776 self.config.loaded_accounts_data_size_limit = Some(limit);
777 self
778 }
779
780 #[must_use]
781 pub fn heap_size(mut self, size: u32) -> Self {
782 self.config.heap_size = Some(size);
783 self
784 }
785
786 #[must_use]
787 pub fn account(mut self, key: Address) -> Self {
788 self.account_keys.push(key);
789 self
790 }
791
792 #[must_use]
793 pub fn accounts(mut self, keys: Vec<Address>) -> Self {
794 self.account_keys = keys;
795 self
796 }
797
798 #[must_use]
799 pub fn instruction(mut self, instruction: CompiledInstruction) -> Self {
800 self.instructions.push(instruction);
801 self
802 }
803
804 #[must_use]
805 pub fn instructions(mut self, instructions: Vec<CompiledInstruction>) -> Self {
806 self.instructions = instructions;
807 self
808 }
809
810 pub fn build(self) -> Result<Message, MessageError> {
812 let lifetime_specifier = self
813 .lifetime_specifier
814 .ok_or(MessageError::MissingLifetimeSpecifier)?;
815
816 let message = Message::new(
817 self.header,
818 self.config,
819 lifetime_specifier,
820 self.account_keys,
821 self.instructions,
822 );
823
824 message.validate()?;
825
826 Ok(message)
827 }
828 }
829
830 fn create_test_message() -> Message {
831 MessageBuilder::new()
832 .required_signatures(1)
833 .readonly_unsigned_accounts(1)
834 .lifetime_specifier(Hash::new_unique())
835 .accounts(vec![
836 Address::new_unique(), Address::new_unique(), Address::new_unique(), ])
840 .compute_unit_limit(200_000)
841 .instruction(CompiledInstruction {
842 program_id_index: 1,
843 accounts: vec![0, 2],
844 data: vec![1, 2, 3, 4],
845 })
846 .build()
847 .unwrap()
848 }
849
850 #[test]
851 fn fee_payer_returns_first_account() {
852 let fee_payer = Address::new_unique();
853 let message = MessageBuilder::new()
854 .required_signatures(1)
855 .lifetime_specifier(Hash::new_unique())
856 .accounts(vec![fee_payer, Address::new_unique()])
857 .build()
858 .unwrap();
859
860 assert_eq!(message.fee_payer(), Some(&fee_payer));
861 }
862
863 #[test]
864 fn fee_payer_returns_none_for_empty_accounts() {
865 let message = Message::new(
867 MessageHeader::default(),
868 TransactionConfig::default(),
869 Hash::new_unique(),
870 vec![],
871 vec![],
872 );
873
874 assert_eq!(message.fee_payer(), None);
875 }
876
877 #[test]
878 fn is_signer_checks_signature_requirement() {
879 let message = create_test_message();
880 assert!(message.is_signer(0)); assert!(!message.is_signer(1)); assert!(!message.is_signer(2)); }
884
885 #[test]
886 fn is_signer_writable_identifies_writable_signers() {
887 let message = MessageBuilder::new()
888 .required_signatures(3)
889 .readonly_signed_accounts(1) .lifetime_specifier(Hash::new_unique())
891 .accounts(vec![
892 Address::new_unique(), Address::new_unique(), Address::new_unique(), Address::new_unique(), ])
897 .build()
898 .unwrap();
899
900 assert!(message.is_signer_writable(0));
902 assert!(message.is_signer_writable(1));
903 assert!(!message.is_signer_writable(2));
905 assert!(!message.is_signer_writable(3));
907 assert!(!message.is_signer_writable(100));
908 }
909
910 #[test]
911 fn is_signer_writable_all_writable_when_no_readonly() {
912 let message = MessageBuilder::new()
913 .required_signatures(2)
914 .readonly_signed_accounts(0) .lifetime_specifier(Hash::new_unique())
916 .accounts(vec![
917 Address::new_unique(),
918 Address::new_unique(),
919 Address::new_unique(),
920 ])
921 .build()
922 .unwrap();
923
924 assert!(message.is_signer_writable(0));
925 assert!(message.is_signer_writable(1));
926 assert!(!message.is_signer_writable(2)); }
928
929 #[test]
930 fn is_key_called_as_program_detects_program_indices() {
931 let message = create_test_message();
932 assert!(message.is_key_called_as_program(1));
934 assert!(!message.is_key_called_as_program(0));
935 assert!(!message.is_key_called_as_program(2));
936 assert!(!message.is_key_called_as_program(256));
938 assert!(!message.is_key_called_as_program(10_000));
939 }
940
941 #[test]
942 fn is_upgradeable_loader_present_detects_loader() {
943 let message = create_test_message();
944 assert!(!message.is_upgradeable_loader_present());
945
946 let mut message_with_loader = create_test_message();
947 message_with_loader
948 .account_keys
949 .push(bpf_loader_upgradeable::id());
950 assert!(message_with_loader.is_upgradeable_loader_present());
951 }
952
953 #[test]
954 fn is_writable_index_respects_header_layout() {
955 let message = create_test_message();
956 assert!(message.is_writable_index(0)); assert!(message.is_writable_index(1)); assert!(!message.is_writable_index(2)); }
961
962 #[test]
963 fn is_writable_index_handles_mixed_signer_permissions() {
964 let mut message = create_test_message();
965 message.header.num_required_signatures = 2;
967 message.header.num_readonly_signed_accounts = 1;
968 message.header.num_readonly_unsigned_accounts = 1;
969 message.account_keys = vec![
970 Address::new_unique(), Address::new_unique(), Address::new_unique(), ];
974 message.instructions[0].program_id_index = 2;
975 message.instructions[0].accounts = vec![0, 1];
976
977 assert!(message.sanitize().is_ok());
978 assert!(message.is_writable_index(0)); assert!(!message.is_writable_index(1)); assert!(!message.is_writable_index(2)); assert!(!message.is_writable_index(999)); }
983
984 #[test]
985 fn is_maybe_writable_returns_false_for_readonly_index() {
986 let message = create_test_message();
987 assert!(!message.is_writable_index(2));
989 assert!(!message.is_maybe_writable(2, None));
990 assert!(!message.is_maybe_writable(2, Some(&HashSet::new())));
992 }
993
994 #[test]
995 fn is_maybe_writable_demotes_reserved_accounts() {
996 let message = create_test_message();
997 let reserved = HashSet::from([message.account_keys[0]]);
998 assert!(message.is_writable_index(0));
1000 assert!(!message.is_maybe_writable(0, Some(&reserved)));
1001 }
1002
1003 #[test]
1004 fn is_maybe_writable_demotes_programs_without_upgradeable_loader() {
1005 let message = create_test_message();
1006 assert!(message.is_writable_index(1));
1008 assert!(message.is_key_called_as_program(1));
1009 assert!(!message.is_upgradeable_loader_present());
1010 assert!(!message.is_maybe_writable(1, None));
1011 }
1012
1013 #[test]
1014 fn is_maybe_writable_preserves_programs_with_upgradeable_loader() {
1015 let mut message = create_test_message();
1016 message.account_keys.push(bpf_loader_upgradeable::id());
1018
1019 assert!(message.sanitize().is_ok());
1020 assert!(message.is_writable_index(1));
1021 assert!(message.is_key_called_as_program(1));
1022 assert!(message.is_upgradeable_loader_present());
1023 assert!(message.is_maybe_writable(1, None));
1025 }
1026
1027 #[test]
1028 fn sanitize_accepts_valid_message() {
1029 let message = create_test_message();
1030 assert!(message.sanitize().is_ok());
1031 }
1032
1033 #[test]
1034 fn sanitize_rejects_zero_signers() {
1035 let mut message = create_test_message();
1036 message.header.num_required_signatures = 0;
1037 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1038 }
1039
1040 #[test]
1041 fn sanitize_rejects_over_12_signatures() {
1042 let mut message = create_test_message();
1043 message.header.num_required_signatures = MAX_SIGNATURES + 1;
1044 message.account_keys = (0..MAX_SIGNATURES + 1)
1045 .map(|_| Address::new_unique())
1046 .collect();
1047 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1048 }
1049
1050 #[test]
1051 fn sanitize_rejects_over_64_addresses() {
1052 let mut message = create_test_message();
1053 message.account_keys = (0..65).map(|_| Address::new_unique()).collect();
1054 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1055 }
1056
1057 #[test]
1058 fn sanitize_rejects_over_64_instructions() {
1059 let mut message = create_test_message();
1060 message.instructions = (0..65) .map(|_| CompiledInstruction {
1062 program_id_index: 1,
1063 accounts: vec![0],
1064 data: vec![],
1065 })
1066 .collect();
1067 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1068 }
1069
1070 #[test]
1071 fn sanitize_rejects_insufficient_accounts_for_header() {
1072 let mut message = create_test_message();
1073 message.header.num_readonly_unsigned_accounts = 10;
1076 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1077 }
1078
1079 #[test]
1080 fn sanitize_rejects_all_signers_readonly() {
1081 let mut message = create_test_message();
1082 message.header.num_readonly_signed_accounts = 1; assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1084 }
1085
1086 #[test]
1087 fn sanitize_rejects_duplicate_addresses() {
1088 let mut message = create_test_message();
1089 let dup = message.account_keys[0];
1090 message.account_keys[1] = dup;
1091 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1092 }
1093
1094 #[test]
1095 fn sanitize_rejects_unaligned_heap_size() {
1096 let mut message = create_test_message();
1097 message.config.heap_size = Some(1025); assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1099 }
1100
1101 #[test]
1102 fn sanitize_rejects_heap_size_below_minimum() {
1103 let mut message = create_test_message();
1104 message.config.heap_size = Some(MIN_HEAP_SIZE - 1);
1105 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1106 }
1107
1108 #[test]
1109 fn sanitize_rejects_heap_size_above_maximum() {
1110 let mut message = create_test_message();
1111 message.config.heap_size = Some(MAX_HEAP_SIZE + 1);
1112 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1113 }
1114
1115 #[test]
1116 fn sanitize_accepts_minimum_heap_size() {
1117 let mut message = create_test_message();
1118 message.config.heap_size = Some(MIN_HEAP_SIZE);
1119 assert!(message.sanitize().is_ok());
1120 }
1121
1122 #[test]
1123 fn sanitize_accepts_maximum_heap_size() {
1124 let mut message = create_test_message();
1125 message.config.heap_size = Some(MAX_HEAP_SIZE);
1126 assert!(message.sanitize().is_ok());
1127 }
1128
1129 #[test]
1130 fn sanitize_accepts_aligned_heap_size() {
1131 let mut message = create_test_message();
1132 message.config.heap_size = Some(65536); assert!(message.sanitize().is_ok());
1134 }
1135
1136 #[test]
1137 fn sanitize_rejects_invalid_program_id_index() {
1138 let mut message = create_test_message();
1139 message.instructions[0].program_id_index = 99;
1140 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1141 }
1142
1143 #[test]
1144 fn sanitize_rejects_fee_payer_as_program() {
1145 let mut message = create_test_message();
1146 message.instructions[0].program_id_index = 0;
1147 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1148 }
1149
1150 #[test]
1151 fn sanitize_rejects_instruction_with_too_many_accounts() {
1152 let mut message = create_test_message();
1153 message.instructions[0].accounts = vec![0u8; (u8::MAX as usize) + 1];
1154 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1155 }
1156
1157 #[test]
1158 fn sanitize_rejects_invalid_instruction_account_index() {
1159 let mut message = create_test_message();
1160 message.instructions[0].accounts = vec![0, 99]; assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1162 }
1163
1164 #[test]
1165 fn sanitize_accepts_64_addresses() {
1166 let mut message = create_test_message();
1167 message.account_keys = (0..MAX_ADDRESSES).map(|_| Address::new_unique()).collect();
1168 message.header.num_required_signatures = 1;
1169 message.header.num_readonly_signed_accounts = 0;
1170 message.header.num_readonly_unsigned_accounts = 1;
1171 message.instructions[0].program_id_index = 1;
1172 message.instructions[0].accounts = vec![0, 2];
1173 assert!(message.sanitize().is_ok());
1174 }
1175
1176 #[test]
1177 fn sanitize_accepts_64_instructions() {
1178 let mut message = create_test_message();
1179 message.instructions = (0..MAX_INSTRUCTIONS)
1180 .map(|_| CompiledInstruction {
1181 program_id_index: 1,
1182 accounts: vec![0, 2],
1183 data: vec![1, 2, 3],
1184 })
1185 .collect();
1186 assert!(message.sanitize().is_ok());
1187 }
1188
1189 #[test]
1190 fn size_matches_serialized_length() {
1191 let test_cases = [
1192 MessageBuilder::new()
1194 .required_signatures(1)
1195 .lifetime_specifier(Hash::new_unique())
1196 .accounts(vec![Address::new_unique()])
1197 .build()
1198 .unwrap(),
1199 MessageBuilder::new()
1201 .required_signatures(1)
1202 .lifetime_specifier(Hash::new_unique())
1203 .accounts(vec![Address::new_unique(), Address::new_unique()])
1204 .priority_fee(1000)
1205 .compute_unit_limit(200_000)
1206 .instruction(CompiledInstruction {
1207 program_id_index: 1,
1208 accounts: vec![0],
1209 data: vec![1, 2, 3, 4],
1210 })
1211 .build()
1212 .unwrap(),
1213 MessageBuilder::new()
1215 .required_signatures(2)
1216 .readonly_signed_accounts(1)
1217 .readonly_unsigned_accounts(1)
1218 .lifetime_specifier(Hash::new_unique())
1219 .accounts(vec![
1220 Address::new_unique(),
1221 Address::new_unique(),
1222 Address::new_unique(),
1223 Address::new_unique(),
1224 ])
1225 .heap_size(65536)
1226 .instructions(vec![
1227 CompiledInstruction {
1228 program_id_index: 2,
1229 accounts: vec![0, 1],
1230 data: vec![],
1231 },
1232 CompiledInstruction {
1233 program_id_index: 3,
1234 accounts: vec![0, 1, 2],
1235 data: vec![0xAA; 100],
1236 },
1237 ])
1238 .build()
1239 .unwrap(),
1240 ];
1241
1242 for message in &test_cases {
1243 assert_eq!(message.size(), wincode::serialize(message).unwrap().len());
1244 }
1245 }
1246
1247 #[test]
1248 fn byte_layout_without_config() {
1249 let fee_payer = Address::new_from_array([1u8; 32]);
1250 let program = Address::new_from_array([2u8; 32]);
1251 let blockhash = Hash::new_from_array([0xAB; 32]);
1252
1253 let message = MessageBuilder::new()
1254 .required_signatures(1)
1255 .lifetime_specifier(blockhash)
1256 .accounts(vec![fee_payer, program])
1257 .instruction(CompiledInstruction {
1258 program_id_index: 1,
1259 accounts: vec![0],
1260 data: vec![0xDE, 0xAD],
1261 })
1262 .build()
1263 .unwrap();
1264
1265 let bytes = wincode::serialize(&message).unwrap();
1266
1267 let mut expected = vec![1, 0, 0];
1273 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);
1286 }
1287
1288 #[test]
1289 fn byte_layout_with_config() {
1290 let fee_payer = Address::new_from_array([1u8; 32]);
1291 let program = Address::new_from_array([2u8; 32]);
1292 let blockhash = Hash::new_from_array([0xBB; 32]);
1293
1294 let message = MessageBuilder::new()
1295 .required_signatures(1)
1296 .lifetime_specifier(blockhash)
1297 .accounts(vec![fee_payer, program])
1298 .priority_fee(0x0102030405060708u64)
1299 .compute_unit_limit(0x11223344u32)
1300 .instruction(CompiledInstruction {
1301 program_id_index: 1,
1302 accounts: vec![],
1303 data: vec![],
1304 })
1305 .build()
1306 .unwrap();
1307
1308 let bytes = wincode::serialize(&message).unwrap();
1309
1310 let mut expected = vec![1, 0, 0];
1311 expected.extend_from_slice(&7u32.to_le_bytes());
1313 expected.extend_from_slice(&[0xBB; 32]);
1314 expected.push(1);
1315 expected.push(2);
1316 expected.extend_from_slice(&[1u8; 32]);
1317 expected.extend_from_slice(&[2u8; 32]);
1318 expected.extend_from_slice(&0x0102030405060708u64.to_le_bytes());
1320 expected.extend_from_slice(&0x11223344u32.to_le_bytes());
1322 expected.push(1); expected.push(0); expected.extend_from_slice(&0u16.to_le_bytes()); assert_eq!(bytes, expected);
1327 }
1328
1329 #[test]
1330 fn roundtrip_preserves_all_config_fields() {
1331 let message = MessageBuilder::new()
1332 .required_signatures(1)
1333 .lifetime_specifier(Hash::new_unique())
1334 .accounts(vec![Address::new_unique(), Address::new_unique()])
1335 .priority_fee(1000)
1336 .compute_unit_limit(200_000)
1337 .loaded_accounts_data_size_limit(1_000_000)
1338 .heap_size(65536)
1339 .instruction(CompiledInstruction {
1340 program_id_index: 1,
1341 accounts: vec![0],
1342 data: vec![],
1343 })
1344 .build()
1345 .unwrap();
1346
1347 let serialized = wincode::serialize(&message).unwrap();
1348 let deserialized = deserialize(&serialized).unwrap();
1349 assert_eq!(message.config, deserialized.config);
1350 }
1351}