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,
60 MAX_INSTRUCTIONS, MAX_SIGNATURES,
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 #[allow(clippy::arithmetic_side_effects)]
396 #[inline(always)]
397 pub fn size(&self) -> usize {
398 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()
406 * (
407 size_of::<u8>()
408 + size_of::<u8>()
409 + size_of::<u16>()
410 ) + self
412 .instructions
413 .iter()
414 .map(|ix| {
415 (ix.accounts.len() * size_of::<u8>())
416 + ix.data.len()
417 })
418 .sum::<usize>() }
420
421 pub fn validate(&self) -> Result<(), MessageError> {
422 if self.header.num_required_signatures > MAX_SIGNATURES {
424 return Err(MessageError::TooManySignatures);
425 }
426
427 if self.instructions.len() > MAX_INSTRUCTIONS as usize {
429 return Err(MessageError::TooManyInstructions);
430 }
431
432 let num_account_keys = self.account_keys.len();
433
434 if num_account_keys > MAX_ADDRESSES as usize {
436 return Err(MessageError::TooManyAddresses);
437 }
438
439 let min_accounts = usize::from(self.header.num_required_signatures)
441 .saturating_add(usize::from(self.header.num_readonly_unsigned_accounts));
442
443 if num_account_keys < min_accounts {
444 return Err(MessageError::NotEnoughAddressesForSignatures);
445 }
446
447 if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures {
449 return Err(MessageError::ZeroSigners);
450 }
451
452 let unique_keys: HashSet<_> = self.account_keys.iter().collect();
454 if unique_keys.len() != num_account_keys {
455 return Err(MessageError::DuplicateAddresses);
456 }
457
458 let mask: TransactionConfigMask = self.config.into();
460
461 if mask.has_invalid_priority_fee_bits() {
462 return Err(MessageError::InvalidConfigMask);
463 }
464
465 if let Some(heap_size) = self.config.heap_size {
467 if heap_size % 1024 != 0 {
468 return Err(MessageError::InvalidHeapSize);
469 }
470 }
471
472 let max_account_index = num_account_keys
474 .checked_sub(1)
475 .ok_or(MessageError::NotEnoughAccountKeys)?;
476
477 for instruction in &self.instructions {
478 if usize::from(instruction.program_id_index) > max_account_index {
480 return Err(MessageError::InvalidInstructionAccountIndex);
481 }
482
483 if instruction.program_id_index == 0 {
485 return Err(MessageError::InvalidInstructionAccountIndex);
486 }
487
488 if instruction.accounts.len() > u8::MAX as usize {
490 return Err(MessageError::InstructionAccountsTooLarge);
491 }
492
493 if instruction.data.len() > u16::MAX as usize {
495 return Err(MessageError::InstructionDataTooLarge);
496 }
497
498 for &account_index in &instruction.accounts {
500 if usize::from(account_index) > max_account_index {
501 return Err(MessageError::InvalidInstructionAccountIndex);
502 }
503 }
504 }
505
506 Ok(())
507 }
508}
509
510impl Sanitize for Message {
511 fn sanitize(&self) -> Result<(), SanitizeError> {
512 Ok(self.validate()?)
513 }
514}
515
516#[cfg(feature = "wincode")]
517unsafe impl<C: ConfigCore> SchemaWrite<C> for Message {
518 type Src = Self;
519
520 #[inline(always)]
521 fn size_of(src: &Self::Src) -> WriteResult<usize> {
522 Ok(src.size())
523 }
524
525 fn write(mut writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
526 let mut writer = unsafe { writer.as_trusted_for(src.size()) }?;
528 writer.write(&[
529 src.header.num_required_signatures,
530 src.header.num_readonly_signed_accounts,
531 src.header.num_readonly_unsigned_accounts,
532 ])?;
533 let mask = TransactionConfigMask::from(&src.config).0.to_le_bytes();
534 writer.write(&mask)?;
535 writer.write(src.lifetime_specifier.as_bytes())?;
536 writer.write(&[src.instructions.len() as u8, src.account_keys.len() as u8])?;
537
538 #[expect(clippy::arithmetic_side_effects)]
541 let account_keys = unsafe {
542 from_raw_parts(
543 src.account_keys.as_ptr().cast::<u8>(),
544 src.account_keys.len() * size_of::<Address>(),
545 )
546 };
547 writer.write(account_keys)?;
548
549 if let Some(value) = src.config.priority_fee {
550 writer.write(&value.to_le_bytes())?;
551 }
552 if let Some(value) = src.config.compute_unit_limit {
553 writer.write(&value.to_le_bytes())?;
554 }
555 if let Some(value) = src.config.loaded_accounts_data_size_limit {
556 writer.write(&value.to_le_bytes())?;
557 }
558 if let Some(value) = src.config.heap_size {
559 writer.write(&value.to_le_bytes())?;
560 }
561
562 for ix in &src.instructions {
563 writer.write(&[ix.program_id_index, ix.accounts.len() as u8])?;
564 writer.write(&(ix.data.len() as u16).to_le_bytes())?;
565 }
566
567 for ix in &src.instructions {
568 writer.write(&ix.accounts)?;
569 writer.write(&ix.data)?;
570 }
571
572 writer.finish()?;
573
574 Ok(())
575 }
576}
577
578#[cfg(feature = "wincode")]
580#[inline]
581pub fn serialize(message: &Message) -> Vec<u8> {
582 wincode::serialize(message).unwrap()
583}
584
585#[cfg(feature = "wincode")]
586unsafe impl<'de, C: Config> SchemaRead<'de, C> for Message {
587 type Dst = Message;
588
589 #[expect(clippy::arithmetic_side_effects)]
590 fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
591 let (header, lifetime_specifier, config_mask, num_instructions, num_addresses) = {
592 let mut reader = unsafe { reader.as_trusted_for(FIXED_HEADER_SIZE)? };
599 let header = <MessageHeader as SchemaRead<C>>::get(reader.by_ref())?;
600 let config_mask = TransactionConfigMask(u32::from_le_bytes(reader.take_array()?));
601 let lifetime_specifier = <Hash as SchemaRead<C>>::get(reader.by_ref())?;
602 let num_instructions = reader.take_byte()? as usize;
603 let num_addresses = reader.take_byte()? as usize;
604 (
605 header,
606 lifetime_specifier,
607 config_mask,
608 num_instructions,
609 num_addresses,
610 )
611 };
612
613 <C::LengthEncoding as SeqLen<C>>::prealloc_check::<Address>(num_addresses)?;
614 let account_keys = <Vec<Address> as SchemaReadContext<C, context::Len>>::get_with_context(
615 context::Len(num_addresses),
616 reader.by_ref(),
617 )?;
618
619 let mut config = TransactionConfig::empty();
620 if config_mask.has_priority_fee() {
621 config.priority_fee = Some(u64::from_le_bytes(reader.take_array()?));
622 }
623 if config_mask.has_compute_unit_limit() {
624 config.compute_unit_limit = Some(u32::from_le_bytes(reader.take_array()?));
625 }
626 if config_mask.has_loaded_accounts_data_size() {
627 config.loaded_accounts_data_size_limit = Some(u32::from_le_bytes(reader.take_array()?));
628 }
629 if config_mask.has_heap_size() {
630 config.heap_size = Some(u32::from_le_bytes(reader.take_array()?));
631 }
632
633 let instruction_headers = unsafe {
641 from_raw_parts(
642 reader
643 .take_borrowed(num_instructions * size_of::<InstructionHeader>())?
644 .as_ptr() as *const InstructionHeader,
645 num_instructions,
646 )
647 };
648 let mut instructions = Vec::with_capacity(num_instructions);
649 for header in instruction_headers {
650 let program_id_index = header.0;
651 let num_accounts = header.1 as usize;
652 let data_len = u16::from_le_bytes(header.2) as usize;
653
654 <C::LengthEncoding as SeqLen<C>>::prealloc_check::<u8>(num_accounts)?;
655 let accounts = <Vec<u8> as SchemaReadContext<C, context::Len>>::get_with_context(
656 context::Len(num_accounts),
657 reader.by_ref(),
658 )?;
659 <C::LengthEncoding as SeqLen<C>>::prealloc_check::<u8>(data_len)?;
660 let data = <Vec<u8> as SchemaReadContext<C, context::Len>>::get_with_context(
661 context::Len(data_len),
662 reader.by_ref(),
663 )?;
664
665 instructions.push(CompiledInstruction {
666 program_id_index,
667 accounts,
668 data,
669 });
670 }
671
672 dst.write(Message {
673 header,
674 lifetime_specifier,
675 config,
676 account_keys,
677 instructions,
678 });
679
680 Ok(())
681 }
682}
683
684#[cfg(feature = "wincode")]
687#[inline]
688pub fn deserialize(input: &[u8]) -> wincode::ReadResult<Message> {
689 wincode::deserialize(input)
690}
691
692#[cfg(test)]
693mod tests {
694 use {super::*, solana_sdk_ids::bpf_loader_upgradeable};
695
696 #[derive(Debug, Clone, Default)]
702 pub struct MessageBuilder {
703 header: MessageHeader,
704 config: TransactionConfig,
705 lifetime_specifier: Option<Hash>,
706 account_keys: Vec<Address>,
707 instructions: Vec<CompiledInstruction>,
708 }
709
710 impl MessageBuilder {
711 pub fn new() -> Self {
712 Self::default()
713 }
714
715 #[must_use]
716 pub fn required_signatures(mut self, count: u8) -> Self {
717 self.header.num_required_signatures = count;
718 self
719 }
720
721 #[must_use]
722 pub fn readonly_signed_accounts(mut self, count: u8) -> Self {
723 self.header.num_readonly_signed_accounts = count;
724 self
725 }
726
727 #[must_use]
728 pub fn readonly_unsigned_accounts(mut self, count: u8) -> Self {
729 self.header.num_readonly_unsigned_accounts = count;
730 self
731 }
732
733 #[must_use]
734 pub fn lifetime_specifier(mut self, hash: Hash) -> Self {
735 self.lifetime_specifier = Some(hash);
736 self
737 }
738
739 #[must_use]
740 pub fn config(mut self, config: TransactionConfig) -> Self {
741 self.config = config;
742 self
743 }
744
745 #[must_use]
746 pub fn priority_fee(mut self, fee: u64) -> Self {
747 self.config.priority_fee = Some(fee);
748 self
749 }
750
751 #[must_use]
752 pub fn compute_unit_limit(mut self, limit: u32) -> Self {
753 self.config.compute_unit_limit = Some(limit);
754 self
755 }
756
757 #[must_use]
758 pub fn loaded_accounts_data_size_limit(mut self, limit: u32) -> Self {
759 self.config.loaded_accounts_data_size_limit = Some(limit);
760 self
761 }
762
763 #[must_use]
764 pub fn heap_size(mut self, size: u32) -> Self {
765 self.config.heap_size = Some(size);
766 self
767 }
768
769 #[must_use]
770 pub fn account(mut self, key: Address) -> Self {
771 self.account_keys.push(key);
772 self
773 }
774
775 #[must_use]
776 pub fn accounts(mut self, keys: Vec<Address>) -> Self {
777 self.account_keys = keys;
778 self
779 }
780
781 #[must_use]
782 pub fn instruction(mut self, instruction: CompiledInstruction) -> Self {
783 self.instructions.push(instruction);
784 self
785 }
786
787 #[must_use]
788 pub fn instructions(mut self, instructions: Vec<CompiledInstruction>) -> Self {
789 self.instructions = instructions;
790 self
791 }
792
793 pub fn build(self) -> Result<Message, MessageError> {
795 let lifetime_specifier = self
796 .lifetime_specifier
797 .ok_or(MessageError::MissingLifetimeSpecifier)?;
798
799 let message = Message::new(
800 self.header,
801 self.config,
802 lifetime_specifier,
803 self.account_keys,
804 self.instructions,
805 );
806
807 message.validate()?;
808
809 Ok(message)
810 }
811 }
812
813 fn create_test_message() -> Message {
814 MessageBuilder::new()
815 .required_signatures(1)
816 .readonly_unsigned_accounts(1)
817 .lifetime_specifier(Hash::new_unique())
818 .accounts(vec![
819 Address::new_unique(), Address::new_unique(), Address::new_unique(), ])
823 .compute_unit_limit(200_000)
824 .instruction(CompiledInstruction {
825 program_id_index: 1,
826 accounts: vec![0, 2],
827 data: vec![1, 2, 3, 4],
828 })
829 .build()
830 .unwrap()
831 }
832
833 #[test]
834 fn fee_payer_returns_first_account() {
835 let fee_payer = Address::new_unique();
836 let message = MessageBuilder::new()
837 .required_signatures(1)
838 .lifetime_specifier(Hash::new_unique())
839 .accounts(vec![fee_payer, Address::new_unique()])
840 .build()
841 .unwrap();
842
843 assert_eq!(message.fee_payer(), Some(&fee_payer));
844 }
845
846 #[test]
847 fn fee_payer_returns_none_for_empty_accounts() {
848 let message = Message::new(
850 MessageHeader::default(),
851 TransactionConfig::default(),
852 Hash::new_unique(),
853 vec![],
854 vec![],
855 );
856
857 assert_eq!(message.fee_payer(), None);
858 }
859
860 #[test]
861 fn is_signer_checks_signature_requirement() {
862 let message = create_test_message();
863 assert!(message.is_signer(0)); assert!(!message.is_signer(1)); assert!(!message.is_signer(2)); }
867
868 #[test]
869 fn is_signer_writable_identifies_writable_signers() {
870 let message = MessageBuilder::new()
871 .required_signatures(3)
872 .readonly_signed_accounts(1) .lifetime_specifier(Hash::new_unique())
874 .accounts(vec![
875 Address::new_unique(), Address::new_unique(), Address::new_unique(), Address::new_unique(), ])
880 .build()
881 .unwrap();
882
883 assert!(message.is_signer_writable(0));
885 assert!(message.is_signer_writable(1));
886 assert!(!message.is_signer_writable(2));
888 assert!(!message.is_signer_writable(3));
890 assert!(!message.is_signer_writable(100));
891 }
892
893 #[test]
894 fn is_signer_writable_all_writable_when_no_readonly() {
895 let message = MessageBuilder::new()
896 .required_signatures(2)
897 .readonly_signed_accounts(0) .lifetime_specifier(Hash::new_unique())
899 .accounts(vec![
900 Address::new_unique(),
901 Address::new_unique(),
902 Address::new_unique(),
903 ])
904 .build()
905 .unwrap();
906
907 assert!(message.is_signer_writable(0));
908 assert!(message.is_signer_writable(1));
909 assert!(!message.is_signer_writable(2)); }
911
912 #[test]
913 fn is_key_called_as_program_detects_program_indices() {
914 let message = create_test_message();
915 assert!(message.is_key_called_as_program(1));
917 assert!(!message.is_key_called_as_program(0));
918 assert!(!message.is_key_called_as_program(2));
919 assert!(!message.is_key_called_as_program(256));
921 assert!(!message.is_key_called_as_program(10_000));
922 }
923
924 #[test]
925 fn is_upgradeable_loader_present_detects_loader() {
926 let message = create_test_message();
927 assert!(!message.is_upgradeable_loader_present());
928
929 let mut message_with_loader = create_test_message();
930 message_with_loader
931 .account_keys
932 .push(bpf_loader_upgradeable::id());
933 assert!(message_with_loader.is_upgradeable_loader_present());
934 }
935
936 #[test]
937 fn is_writable_index_respects_header_layout() {
938 let message = create_test_message();
939 assert!(message.is_writable_index(0)); assert!(message.is_writable_index(1)); assert!(!message.is_writable_index(2)); }
944
945 #[test]
946 fn is_writable_index_handles_mixed_signer_permissions() {
947 let mut message = create_test_message();
948 message.header.num_required_signatures = 2;
950 message.header.num_readonly_signed_accounts = 1;
951 message.header.num_readonly_unsigned_accounts = 1;
952 message.account_keys = vec![
953 Address::new_unique(), Address::new_unique(), Address::new_unique(), ];
957 message.instructions[0].program_id_index = 2;
958 message.instructions[0].accounts = vec![0, 1];
959
960 assert!(message.sanitize().is_ok());
961 assert!(message.is_writable_index(0)); assert!(!message.is_writable_index(1)); assert!(!message.is_writable_index(2)); assert!(!message.is_writable_index(999)); }
966
967 #[test]
968 fn is_maybe_writable_returns_false_for_readonly_index() {
969 let message = create_test_message();
970 assert!(!message.is_writable_index(2));
972 assert!(!message.is_maybe_writable(2, None));
973 assert!(!message.is_maybe_writable(2, Some(&HashSet::new())));
975 }
976
977 #[test]
978 fn is_maybe_writable_demotes_reserved_accounts() {
979 let message = create_test_message();
980 let reserved = HashSet::from([message.account_keys[0]]);
981 assert!(message.is_writable_index(0));
983 assert!(!message.is_maybe_writable(0, Some(&reserved)));
984 }
985
986 #[test]
987 fn is_maybe_writable_demotes_programs_without_upgradeable_loader() {
988 let message = create_test_message();
989 assert!(message.is_writable_index(1));
991 assert!(message.is_key_called_as_program(1));
992 assert!(!message.is_upgradeable_loader_present());
993 assert!(!message.is_maybe_writable(1, None));
994 }
995
996 #[test]
997 fn is_maybe_writable_preserves_programs_with_upgradeable_loader() {
998 let mut message = create_test_message();
999 message.account_keys.push(bpf_loader_upgradeable::id());
1001
1002 assert!(message.sanitize().is_ok());
1003 assert!(message.is_writable_index(1));
1004 assert!(message.is_key_called_as_program(1));
1005 assert!(message.is_upgradeable_loader_present());
1006 assert!(message.is_maybe_writable(1, None));
1008 }
1009
1010 #[test]
1011 fn sanitize_accepts_valid_message() {
1012 let message = create_test_message();
1013 assert!(message.sanitize().is_ok());
1014 }
1015
1016 #[test]
1017 fn sanitize_rejects_zero_signers() {
1018 let mut message = create_test_message();
1019 message.header.num_required_signatures = 0;
1020 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1021 }
1022
1023 #[test]
1024 fn sanitize_rejects_over_12_signatures() {
1025 let mut message = create_test_message();
1026 message.header.num_required_signatures = MAX_SIGNATURES + 1;
1027 message.account_keys = (0..MAX_SIGNATURES + 1)
1028 .map(|_| Address::new_unique())
1029 .collect();
1030 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1031 }
1032
1033 #[test]
1034 fn sanitize_rejects_over_64_addresses() {
1035 let mut message = create_test_message();
1036 message.account_keys = (0..65).map(|_| Address::new_unique()).collect();
1037 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1038 }
1039
1040 #[test]
1041 fn sanitize_rejects_over_64_instructions() {
1042 let mut message = create_test_message();
1043 message.instructions = (0..65) .map(|_| CompiledInstruction {
1045 program_id_index: 1,
1046 accounts: vec![0],
1047 data: vec![],
1048 })
1049 .collect();
1050 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1051 }
1052
1053 #[test]
1054 fn sanitize_rejects_insufficient_accounts_for_header() {
1055 let mut message = create_test_message();
1056 message.header.num_readonly_unsigned_accounts = 10;
1059 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1060 }
1061
1062 #[test]
1063 fn sanitize_rejects_all_signers_readonly() {
1064 let mut message = create_test_message();
1065 message.header.num_readonly_signed_accounts = 1; assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1067 }
1068
1069 #[test]
1070 fn sanitize_rejects_duplicate_addresses() {
1071 let mut message = create_test_message();
1072 let dup = message.account_keys[0];
1073 message.account_keys[1] = dup;
1074 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1075 }
1076
1077 #[test]
1078 fn sanitize_rejects_unaligned_heap_size() {
1079 let mut message = create_test_message();
1080 message.config.heap_size = Some(1025); assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1082 }
1083
1084 #[test]
1085 fn sanitize_accepts_aligned_heap_size() {
1086 let mut message = create_test_message();
1087 message.config.heap_size = Some(65536); assert!(message.sanitize().is_ok());
1089 }
1090
1091 #[test]
1092 fn sanitize_rejects_invalid_program_id_index() {
1093 let mut message = create_test_message();
1094 message.instructions[0].program_id_index = 99;
1095 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1096 }
1097
1098 #[test]
1099 fn sanitize_rejects_fee_payer_as_program() {
1100 let mut message = create_test_message();
1101 message.instructions[0].program_id_index = 0;
1102 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1103 }
1104
1105 #[test]
1106 fn sanitize_rejects_instruction_with_too_many_accounts() {
1107 let mut message = create_test_message();
1108 message.instructions[0].accounts = vec![0u8; (u8::MAX as usize) + 1];
1109 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1110 }
1111
1112 #[test]
1113 fn sanitize_rejects_invalid_instruction_account_index() {
1114 let mut message = create_test_message();
1115 message.instructions[0].accounts = vec![0, 99]; assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1117 }
1118
1119 #[test]
1120 fn sanitize_accepts_64_addresses() {
1121 let mut message = create_test_message();
1122 message.account_keys = (0..MAX_ADDRESSES).map(|_| Address::new_unique()).collect();
1123 message.header.num_required_signatures = 1;
1124 message.header.num_readonly_signed_accounts = 0;
1125 message.header.num_readonly_unsigned_accounts = 1;
1126 message.instructions[0].program_id_index = 1;
1127 message.instructions[0].accounts = vec![0, 2];
1128 assert!(message.sanitize().is_ok());
1129 }
1130
1131 #[test]
1132 fn sanitize_accepts_64_instructions() {
1133 let mut message = create_test_message();
1134 message.instructions = (0..MAX_INSTRUCTIONS)
1135 .map(|_| CompiledInstruction {
1136 program_id_index: 1,
1137 accounts: vec![0, 2],
1138 data: vec![1, 2, 3],
1139 })
1140 .collect();
1141 assert!(message.sanitize().is_ok());
1142 }
1143
1144 #[test]
1145 fn size_matches_serialized_length() {
1146 let test_cases = [
1147 MessageBuilder::new()
1149 .required_signatures(1)
1150 .lifetime_specifier(Hash::new_unique())
1151 .accounts(vec![Address::new_unique()])
1152 .build()
1153 .unwrap(),
1154 MessageBuilder::new()
1156 .required_signatures(1)
1157 .lifetime_specifier(Hash::new_unique())
1158 .accounts(vec![Address::new_unique(), Address::new_unique()])
1159 .priority_fee(1000)
1160 .compute_unit_limit(200_000)
1161 .instruction(CompiledInstruction {
1162 program_id_index: 1,
1163 accounts: vec![0],
1164 data: vec![1, 2, 3, 4],
1165 })
1166 .build()
1167 .unwrap(),
1168 MessageBuilder::new()
1170 .required_signatures(2)
1171 .readonly_signed_accounts(1)
1172 .readonly_unsigned_accounts(1)
1173 .lifetime_specifier(Hash::new_unique())
1174 .accounts(vec![
1175 Address::new_unique(),
1176 Address::new_unique(),
1177 Address::new_unique(),
1178 Address::new_unique(),
1179 ])
1180 .heap_size(65536)
1181 .instructions(vec![
1182 CompiledInstruction {
1183 program_id_index: 2,
1184 accounts: vec![0, 1],
1185 data: vec![],
1186 },
1187 CompiledInstruction {
1188 program_id_index: 3,
1189 accounts: vec![0, 1, 2],
1190 data: vec![0xAA; 100],
1191 },
1192 ])
1193 .build()
1194 .unwrap(),
1195 ];
1196
1197 for message in &test_cases {
1198 assert_eq!(message.size(), serialize(message).len());
1199 }
1200 }
1201
1202 #[test]
1203 fn byte_layout_without_config() {
1204 let fee_payer = Address::new_from_array([1u8; 32]);
1205 let program = Address::new_from_array([2u8; 32]);
1206 let blockhash = Hash::new_from_array([0xAB; 32]);
1207
1208 let message = MessageBuilder::new()
1209 .required_signatures(1)
1210 .lifetime_specifier(blockhash)
1211 .accounts(vec![fee_payer, program])
1212 .instruction(CompiledInstruction {
1213 program_id_index: 1,
1214 accounts: vec![0],
1215 data: vec![0xDE, 0xAD],
1216 })
1217 .build()
1218 .unwrap();
1219
1220 let bytes = serialize(&message);
1221
1222 let mut expected = vec![1, 0, 0];
1228 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);
1241 }
1242
1243 #[test]
1244 fn byte_layout_with_config() {
1245 let fee_payer = Address::new_from_array([1u8; 32]);
1246 let program = Address::new_from_array([2u8; 32]);
1247 let blockhash = Hash::new_from_array([0xBB; 32]);
1248
1249 let message = MessageBuilder::new()
1250 .required_signatures(1)
1251 .lifetime_specifier(blockhash)
1252 .accounts(vec![fee_payer, program])
1253 .priority_fee(0x0102030405060708u64)
1254 .compute_unit_limit(0x11223344u32)
1255 .instruction(CompiledInstruction {
1256 program_id_index: 1,
1257 accounts: vec![],
1258 data: vec![],
1259 })
1260 .build()
1261 .unwrap();
1262
1263 let bytes = serialize(&message);
1264
1265 let mut expected = vec![1, 0, 0];
1266 expected.extend_from_slice(&7u32.to_le_bytes());
1268 expected.extend_from_slice(&[0xBB; 32]);
1269 expected.push(1);
1270 expected.push(2);
1271 expected.extend_from_slice(&[1u8; 32]);
1272 expected.extend_from_slice(&[2u8; 32]);
1273 expected.extend_from_slice(&0x0102030405060708u64.to_le_bytes());
1275 expected.extend_from_slice(&0x11223344u32.to_le_bytes());
1277 expected.push(1); expected.push(0); expected.extend_from_slice(&0u16.to_le_bytes()); assert_eq!(bytes, expected);
1282 }
1283
1284 #[test]
1285 fn roundtrip_preserves_all_config_fields() {
1286 let message = MessageBuilder::new()
1287 .required_signatures(1)
1288 .lifetime_specifier(Hash::new_unique())
1289 .accounts(vec![Address::new_unique(), Address::new_unique()])
1290 .priority_fee(1000)
1291 .compute_unit_limit(200_000)
1292 .loaded_accounts_data_size_limit(1_000_000)
1293 .heap_size(65536)
1294 .instruction(CompiledInstruction {
1295 program_id_index: 1,
1296 accounts: vec![0],
1297 data: vec![],
1298 })
1299 .build()
1300 .unwrap();
1301
1302 let serialized = serialize(&message);
1303 let deserialized = deserialize(&serialized).unwrap();
1304 assert_eq!(message.config, deserialized.config);
1305 }
1306}