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