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::MAX_TRANSACTION_SIZE,
45 wincode::{
46 config::ConfigCore,
47 error::invalid_tag_encoding,
48 io::{Reader, Writer},
49 ReadResult, SchemaRead, SchemaWrite, WriteResult,
50 },
51};
52use {
53 crate::{
54 compiled_instruction::CompiledInstruction,
55 compiled_keys::CompiledKeys,
56 v1::{
57 InstructionHeader, MessageError, TransactionConfig, TransactionConfigMask,
58 FIXED_HEADER_SIZE, MAX_ADDRESSES, MAX_INSTRUCTIONS, MAX_SIGNATURES,
59 },
60 AccountKeys, CompileError, MessageHeader,
61 },
62 core::{mem::size_of, ptr::copy_nonoverlapping},
63 solana_address::Address,
64 solana_hash::Hash,
65 solana_instruction::Instruction,
66 solana_sanitize::{Sanitize, SanitizeError},
67 std::{collections::HashSet, mem::MaybeUninit},
68};
69
70#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
77#[cfg_attr(
78 feature = "serde",
79 derive(Serialize, Deserialize),
80 serde(rename_all = "camelCase")
81)]
82#[derive(Debug, Clone, PartialEq, Eq, Default)]
83pub struct Message {
84 pub header: MessageHeader,
86
87 pub config: TransactionConfig,
89
90 pub lifetime_specifier: Hash,
92
93 pub account_keys: Vec<Address>,
112
113 pub instructions: Vec<CompiledInstruction>,
115}
116
117impl Message {
118 pub fn new(
120 header: MessageHeader,
121 config: TransactionConfig,
122 lifetime_specifier: Hash,
123 account_keys: Vec<Address>,
124 instructions: Vec<CompiledInstruction>,
125 ) -> Self {
126 Self {
127 header,
128 config,
129 lifetime_specifier,
130 account_keys,
131 instructions,
132 }
133 }
134
135 pub fn try_compile(
210 payer: &Address,
211 instructions: &[Instruction],
212 recent_blockhash: Hash,
213 ) -> Result<Self, CompileError> {
214 Self::try_compile_with_config(
215 payer,
216 instructions,
217 recent_blockhash,
218 TransactionConfig::empty(),
219 )
220 }
221
222 pub fn try_compile_with_config(
298 payer: &Address,
299 instructions: &[Instruction],
300 recent_blockhash: Hash,
301 config: TransactionConfig,
302 ) -> Result<Self, CompileError> {
303 let compiled_keys = CompiledKeys::compile(instructions, Some(*payer));
304 let (header, static_keys) = compiled_keys.try_into_message_components()?;
305
306 let account_keys = AccountKeys::new(&static_keys, None);
307 let instructions = account_keys.try_compile_instructions(instructions)?;
308
309 Ok(Self {
310 header,
311 config,
312 lifetime_specifier: recent_blockhash,
313 account_keys: static_keys,
314 instructions,
315 })
316 }
317
318 pub fn fee_payer(&self) -> Option<&Address> {
320 self.account_keys.first()
321 }
322
323 pub fn is_signer(&self, index: usize) -> bool {
326 index < usize::from(self.header.num_required_signatures)
327 }
328
329 pub fn is_signer_writable(&self, index: usize) -> bool {
331 if !self.is_signer(index) {
332 return false;
333 }
334 let num_writable_signers = usize::from(self.header.num_required_signatures)
337 .saturating_sub(usize::from(self.header.num_readonly_signed_accounts));
338 index < num_writable_signers
339 }
340
341 pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
343 crate::is_key_called_as_program(&self.instructions, key_index)
344 }
345
346 #[inline(always)]
351 pub(crate) fn is_writable_index(&self, i: usize) -> bool {
352 crate::is_writable_index(i, self.header, &self.account_keys)
353 }
354
355 pub fn is_upgradeable_loader_present(&self) -> bool {
357 crate::is_upgradeable_loader_present(&self.account_keys)
358 }
359
360 pub fn is_maybe_writable(
375 &self,
376 key_index: usize,
377 reserved_account_keys: Option<&HashSet<Address>>,
378 ) -> bool {
379 crate::is_maybe_writable(
380 key_index,
381 self.header,
382 &self.account_keys,
383 &self.instructions,
384 reserved_account_keys,
385 )
386 }
387
388 pub fn demote_program_id(&self, i: usize) -> bool {
389 crate::is_program_id_write_demoted(i, &self.account_keys, &self.instructions)
390 }
391
392 #[allow(clippy::arithmetic_side_effects)]
394 #[inline(always)]
395 pub fn size(&self) -> usize {
396 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()
404 * (
405 size_of::<u8>()
406 + size_of::<u8>()
407 + size_of::<u16>()
408 ) + self
410 .instructions
411 .iter()
412 .map(|ix| {
413 (ix.accounts.len() * size_of::<u8>())
414 + ix.data.len()
415 })
416 .sum::<usize>() }
418
419 pub fn validate(&self) -> Result<(), MessageError> {
420 if self.header.num_required_signatures > MAX_SIGNATURES {
422 return Err(MessageError::TooManySignatures);
423 }
424
425 if self.instructions.len() > MAX_INSTRUCTIONS as usize {
427 return Err(MessageError::TooManyInstructions);
428 }
429
430 let num_account_keys = self.account_keys.len();
431
432 if num_account_keys > MAX_ADDRESSES as usize {
434 return Err(MessageError::TooManyAddresses);
435 }
436
437 let min_accounts = usize::from(self.header.num_required_signatures)
439 .saturating_add(usize::from(self.header.num_readonly_unsigned_accounts));
440
441 if num_account_keys < min_accounts {
442 return Err(MessageError::NotEnoughAddressesForSignatures);
443 }
444
445 if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures {
447 return Err(MessageError::ZeroSigners);
448 }
449
450 let unique_keys: HashSet<_> = self.account_keys.iter().collect();
452 if unique_keys.len() != num_account_keys {
453 return Err(MessageError::DuplicateAddresses);
454 }
455
456 let mask: TransactionConfigMask = self.config.into();
458
459 if mask.has_invalid_priority_fee_bits() {
460 return Err(MessageError::InvalidConfigMask);
461 }
462
463 if let Some(heap_size) = self.config.heap_size {
465 if heap_size % 1024 != 0 {
466 return Err(MessageError::InvalidHeapSize);
467 }
468 }
469
470 let max_account_index = num_account_keys
472 .checked_sub(1)
473 .ok_or(MessageError::NotEnoughAccountKeys)?;
474
475 for instruction in &self.instructions {
476 if usize::from(instruction.program_id_index) > max_account_index {
478 return Err(MessageError::InvalidInstructionAccountIndex);
479 }
480
481 if instruction.program_id_index == 0 {
483 return Err(MessageError::InvalidInstructionAccountIndex);
484 }
485
486 if instruction.accounts.len() > u8::MAX as usize {
488 return Err(MessageError::InstructionAccountsTooLarge);
489 }
490
491 if instruction.data.len() > u16::MAX as usize {
493 return Err(MessageError::InstructionDataTooLarge);
494 }
495
496 for &account_index in &instruction.accounts {
498 if usize::from(account_index) > max_account_index {
499 return Err(MessageError::InvalidInstructionAccountIndex);
500 }
501 }
502 }
503
504 Ok(())
505 }
506}
507
508impl Sanitize for Message {
509 fn sanitize(&self) -> Result<(), SanitizeError> {
510 Ok(self.validate()?)
511 }
512}
513
514#[cfg(feature = "wincode")]
515unsafe impl<C: ConfigCore> SchemaWrite<C> for Message {
516 type Src = Self;
517
518 #[allow(clippy::arithmetic_side_effects)]
519 #[inline(always)]
520 fn size_of(src: &Self::Src) -> WriteResult<usize> {
521 Ok(src.size())
522 }
523
524 #[inline(always)]
526 fn write(mut writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
527 unsafe {
529 writer
530 .write_slice_t(&serialize(src))
531 .map_err(wincode::WriteError::Io)
532 }
533 }
534}
535
536#[cfg(feature = "wincode")]
537unsafe impl<'de, C: ConfigCore> SchemaRead<'de, C> for Message {
538 type Dst = Self;
539
540 fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
541 let bytes = reader.fill_buf(MAX_TRANSACTION_SIZE)?;
542 let (message, consumed) = deserialize(bytes).map_err(|_| invalid_tag_encoding(1))?;
543
544 unsafe { reader.consume_unchecked(consumed) };
546
547 dst.write(message);
548
549 Ok(())
550 }
551}
552
553pub fn serialize(message: &Message) -> Vec<u8> {
555 let total = message.size();
556 let mut buffer = Vec::<u8>::with_capacity(total);
557
558 unsafe {
560 serialize_into(message, buffer.as_mut_ptr());
561 buffer.set_len(total);
562 }
563
564 buffer
565}
566
567pub(crate) unsafe fn serialize_into(message: &Message, mut dst: *mut u8) {
574 dst.write(message.header.num_required_signatures);
576 dst = dst.add(1);
577 dst.write(message.header.num_readonly_signed_accounts);
578 dst = dst.add(1);
579 dst.write(message.header.num_readonly_unsigned_accounts);
580 dst = dst.add(1);
581
582 let mask = TransactionConfigMask::from(&message.config).0.to_le_bytes();
584 copy_nonoverlapping(mask.as_ptr(), dst, mask.len());
585 dst = dst.add(mask.len());
586
587 let lifetime = message.lifetime_specifier.as_ref();
589 copy_nonoverlapping(lifetime.as_ptr(), dst, lifetime.len());
590 dst = dst.add(lifetime.len());
591
592 dst.write(message.instructions.len() as u8);
594 dst = dst.add(1);
595 dst.write(message.account_keys.len() as u8);
596 dst = dst.add(1);
597
598 for addr in &message.account_keys {
600 let a = addr.as_ref();
601 copy_nonoverlapping(a.as_ptr(), dst, a.len());
602 dst = dst.add(a.len());
603 }
604
605 if let Some(value) = message.config.priority_fee {
607 let bytes = value.to_le_bytes();
608 copy_nonoverlapping(bytes.as_ptr(), dst, bytes.len());
609 dst = dst.add(bytes.len());
610 }
611 if let Some(value) = message.config.compute_unit_limit {
612 let bytes = value.to_le_bytes();
613 copy_nonoverlapping(bytes.as_ptr(), dst, bytes.len());
614 dst = dst.add(bytes.len());
615 }
616 if let Some(value) = message.config.loaded_accounts_data_size_limit {
617 let bytes = value.to_le_bytes();
618 copy_nonoverlapping(bytes.as_ptr(), dst, bytes.len());
619 dst = dst.add(bytes.len());
620 }
621 if let Some(value) = message.config.heap_size {
622 let bytes = value.to_le_bytes();
623 copy_nonoverlapping(bytes.as_ptr(), dst, bytes.len());
624 dst = dst.add(bytes.len());
625 }
626
627 for ix in &message.instructions {
629 dst.write(ix.program_id_index);
630 dst = dst.add(1);
631 dst.write(ix.accounts.len() as u8);
632 dst = dst.add(1);
633
634 let len = (ix.data.len() as u16).to_le_bytes();
635 copy_nonoverlapping(len.as_ptr(), dst, 2);
636 dst = dst.add(2);
637 }
638
639 for ix in &message.instructions {
641 copy_nonoverlapping(ix.accounts.as_ptr(), dst, ix.accounts.len());
642 dst = dst.add(ix.accounts.len());
643
644 copy_nonoverlapping(ix.data.as_ptr(), dst, ix.data.len());
645 dst = dst.add(ix.data.len());
646 }
647}
648
649#[allow(clippy::arithmetic_side_effects)]
652pub fn deserialize(input: &[u8]) -> Result<(Message, usize), MessageError> {
653 if input.len() < FIXED_HEADER_SIZE {
654 return Err(MessageError::BufferTooSmall);
655 }
656
657 let mut input_ptr = input.as_ptr();
658
659 let header = unsafe {
661 let mut header = MaybeUninit::<MessageHeader>::uninit();
662 let dst = header.as_mut_ptr() as *mut u8;
663
664 dst.write(input_ptr.read());
666 dst.add(1).write(input_ptr.add(1).read());
668 dst.add(2).write(input_ptr.add(2).read());
670
671 input_ptr = input_ptr.add(3);
673
674 header.assume_init()
675 };
676
677 let config_mask = unsafe {
681 let mask = TransactionConfigMask(u32::from_le_bytes(*(input_ptr as *const [u8; 4])));
682 input_ptr = input_ptr.add(4);
683
684 mask
685 };
686
687 let lifetime_specifier = unsafe {
691 let specifier = Hash::new_from_array(*(input_ptr as *const [u8; 32]));
692 input_ptr = input_ptr.add(32);
693
694 specifier
695 };
696
697 let num_instructions = unsafe {
701 let num_instructions = input_ptr.read() as usize;
702 input_ptr = input_ptr.add(1);
703
704 num_instructions
705 };
706 let num_addresses = unsafe {
708 let num_addresses = input_ptr.read() as usize;
709 input_ptr = input_ptr.add(1);
710
711 num_addresses
712 };
713
714 let mut offset = FIXED_HEADER_SIZE + num_addresses * size_of::<Address>();
717
718 if input.len() < offset {
721 return Err(MessageError::BufferTooSmall);
722 }
723
724 let mut account_keys = Vec::with_capacity(num_addresses);
725 unsafe {
728 let dst = account_keys.as_mut_ptr();
729 copy_nonoverlapping(input_ptr as *const Address, dst, num_addresses);
730 account_keys.set_len(num_addresses);
731 input_ptr = input_ptr.add(num_addresses * size_of::<Address>());
732 }
733
734 offset += config_mask.size_of_config();
736
737 if input.len() < offset {
738 return Err(MessageError::BufferTooSmall);
739 }
740
741 let mut config = TransactionConfig::empty();
742
743 if config_mask.has_priority_fee() {
744 let value = unsafe { u64::from_le_bytes(*(input_ptr as *const [u8; 8])) };
747 config = config.with_priority_fee(value);
748 input_ptr = unsafe { input_ptr.add(size_of::<u64>()) };
749 }
750
751 if config_mask.has_compute_unit_limit() {
752 let value = unsafe { u32::from_le_bytes(*(input_ptr as *const [u8; 4])) };
755 config = config.with_compute_unit_limit(value);
756 input_ptr = unsafe { input_ptr.add(size_of::<u32>()) };
757 }
758
759 if config_mask.has_loaded_accounts_data_size() {
760 let value = unsafe { u32::from_le_bytes(*(input_ptr as *const [u8; 4])) };
763 config = config.with_loaded_accounts_data_size_limit(value);
764 input_ptr = unsafe { input_ptr.add(size_of::<u32>()) };
765 }
766
767 if config_mask.has_heap_size() {
768 let value = unsafe { u32::from_le_bytes(*(input_ptr as *const [u8; 4])) };
771 config = config.with_heap_size(value);
772 input_ptr = unsafe { input_ptr.add(size_of::<u32>()) };
773 }
774
775 offset += num_instructions * size_of::<InstructionHeader>();
778
779 if input.len() < offset {
780 return Err(MessageError::BufferTooSmall);
781 }
782
783 let instruction_headers: &[InstructionHeader] = unsafe {
786 core::slice::from_raw_parts(input_ptr as *const InstructionHeader, num_instructions)
787 };
788
789 input_ptr = unsafe { input_ptr.add(num_instructions * size_of::<InstructionHeader>()) };
790
791 let mut instructions = Vec::with_capacity(num_instructions);
794
795 for header in instruction_headers {
796 let program_id_index = header.0;
797 let num_accounts = header.1 as usize;
798 let data_len = u16::from_le_bytes(header.2) as usize;
799
800 offset += num_accounts + data_len;
801
802 if input.len() < offset {
803 return Err(MessageError::BufferTooSmall);
804 }
805
806 let accounts = unsafe { core::slice::from_raw_parts(input_ptr, num_accounts).to_vec() };
809
810 let data = unsafe {
811 input_ptr = input_ptr.add(num_accounts);
812 core::slice::from_raw_parts(input_ptr, data_len).to_vec()
813 };
814
815 input_ptr = unsafe { input_ptr.add(data_len) };
816
817 instructions.push(CompiledInstruction {
818 program_id_index,
819 accounts,
820 data,
821 });
822 }
823
824 Ok((
825 Message {
826 header,
827 config,
828 lifetime_specifier,
829 account_keys,
830 instructions,
831 },
832 offset,
833 ))
834}
835
836#[cfg(test)]
837mod tests {
838 use {super::*, solana_sdk_ids::bpf_loader_upgradeable};
839
840 #[derive(Debug, Clone, Default)]
846 pub struct MessageBuilder {
847 header: MessageHeader,
848 config: TransactionConfig,
849 lifetime_specifier: Option<Hash>,
850 account_keys: Vec<Address>,
851 instructions: Vec<CompiledInstruction>,
852 }
853
854 impl MessageBuilder {
855 pub fn new() -> Self {
856 Self::default()
857 }
858
859 #[must_use]
860 pub fn required_signatures(mut self, count: u8) -> Self {
861 self.header.num_required_signatures = count;
862 self
863 }
864
865 #[must_use]
866 pub fn readonly_signed_accounts(mut self, count: u8) -> Self {
867 self.header.num_readonly_signed_accounts = count;
868 self
869 }
870
871 #[must_use]
872 pub fn readonly_unsigned_accounts(mut self, count: u8) -> Self {
873 self.header.num_readonly_unsigned_accounts = count;
874 self
875 }
876
877 #[must_use]
878 pub fn lifetime_specifier(mut self, hash: Hash) -> Self {
879 self.lifetime_specifier = Some(hash);
880 self
881 }
882
883 #[must_use]
884 pub fn config(mut self, config: TransactionConfig) -> Self {
885 self.config = config;
886 self
887 }
888
889 #[must_use]
890 pub fn priority_fee(mut self, fee: u64) -> Self {
891 self.config.priority_fee = Some(fee);
892 self
893 }
894
895 #[must_use]
896 pub fn compute_unit_limit(mut self, limit: u32) -> Self {
897 self.config.compute_unit_limit = Some(limit);
898 self
899 }
900
901 #[must_use]
902 pub fn loaded_accounts_data_size_limit(mut self, limit: u32) -> Self {
903 self.config.loaded_accounts_data_size_limit = Some(limit);
904 self
905 }
906
907 #[must_use]
908 pub fn heap_size(mut self, size: u32) -> Self {
909 self.config.heap_size = Some(size);
910 self
911 }
912
913 #[must_use]
914 pub fn account(mut self, key: Address) -> Self {
915 self.account_keys.push(key);
916 self
917 }
918
919 #[must_use]
920 pub fn accounts(mut self, keys: Vec<Address>) -> Self {
921 self.account_keys = keys;
922 self
923 }
924
925 #[must_use]
926 pub fn instruction(mut self, instruction: CompiledInstruction) -> Self {
927 self.instructions.push(instruction);
928 self
929 }
930
931 #[must_use]
932 pub fn instructions(mut self, instructions: Vec<CompiledInstruction>) -> Self {
933 self.instructions = instructions;
934 self
935 }
936
937 pub fn build(self) -> Result<Message, MessageError> {
939 let lifetime_specifier = self
940 .lifetime_specifier
941 .ok_or(MessageError::MissingLifetimeSpecifier)?;
942
943 let message = Message::new(
944 self.header,
945 self.config,
946 lifetime_specifier,
947 self.account_keys,
948 self.instructions,
949 );
950
951 message.validate()?;
952
953 Ok(message)
954 }
955 }
956
957 fn create_test_message() -> Message {
958 MessageBuilder::new()
959 .required_signatures(1)
960 .readonly_unsigned_accounts(1)
961 .lifetime_specifier(Hash::new_unique())
962 .accounts(vec![
963 Address::new_unique(), Address::new_unique(), Address::new_unique(), ])
967 .compute_unit_limit(200_000)
968 .instruction(CompiledInstruction {
969 program_id_index: 1,
970 accounts: vec![0, 2],
971 data: vec![1, 2, 3, 4],
972 })
973 .build()
974 .unwrap()
975 }
976
977 #[test]
978 fn fee_payer_returns_first_account() {
979 let fee_payer = Address::new_unique();
980 let message = MessageBuilder::new()
981 .required_signatures(1)
982 .lifetime_specifier(Hash::new_unique())
983 .accounts(vec![fee_payer, Address::new_unique()])
984 .build()
985 .unwrap();
986
987 assert_eq!(message.fee_payer(), Some(&fee_payer));
988 }
989
990 #[test]
991 fn fee_payer_returns_none_for_empty_accounts() {
992 let message = Message::new(
994 MessageHeader::default(),
995 TransactionConfig::default(),
996 Hash::new_unique(),
997 vec![],
998 vec![],
999 );
1000
1001 assert_eq!(message.fee_payer(), None);
1002 }
1003
1004 #[test]
1005 fn is_signer_checks_signature_requirement() {
1006 let message = create_test_message();
1007 assert!(message.is_signer(0)); assert!(!message.is_signer(1)); assert!(!message.is_signer(2)); }
1011
1012 #[test]
1013 fn is_signer_writable_identifies_writable_signers() {
1014 let message = MessageBuilder::new()
1015 .required_signatures(3)
1016 .readonly_signed_accounts(1) .lifetime_specifier(Hash::new_unique())
1018 .accounts(vec![
1019 Address::new_unique(), Address::new_unique(), Address::new_unique(), Address::new_unique(), ])
1024 .build()
1025 .unwrap();
1026
1027 assert!(message.is_signer_writable(0));
1029 assert!(message.is_signer_writable(1));
1030 assert!(!message.is_signer_writable(2));
1032 assert!(!message.is_signer_writable(3));
1034 assert!(!message.is_signer_writable(100));
1035 }
1036
1037 #[test]
1038 fn is_signer_writable_all_writable_when_no_readonly() {
1039 let message = MessageBuilder::new()
1040 .required_signatures(2)
1041 .readonly_signed_accounts(0) .lifetime_specifier(Hash::new_unique())
1043 .accounts(vec![
1044 Address::new_unique(),
1045 Address::new_unique(),
1046 Address::new_unique(),
1047 ])
1048 .build()
1049 .unwrap();
1050
1051 assert!(message.is_signer_writable(0));
1052 assert!(message.is_signer_writable(1));
1053 assert!(!message.is_signer_writable(2)); }
1055
1056 #[test]
1057 fn is_key_called_as_program_detects_program_indices() {
1058 let message = create_test_message();
1059 assert!(message.is_key_called_as_program(1));
1061 assert!(!message.is_key_called_as_program(0));
1062 assert!(!message.is_key_called_as_program(2));
1063 assert!(!message.is_key_called_as_program(256));
1065 assert!(!message.is_key_called_as_program(10_000));
1066 }
1067
1068 #[test]
1069 fn is_upgradeable_loader_present_detects_loader() {
1070 let message = create_test_message();
1071 assert!(!message.is_upgradeable_loader_present());
1072
1073 let mut message_with_loader = create_test_message();
1074 message_with_loader
1075 .account_keys
1076 .push(bpf_loader_upgradeable::id());
1077 assert!(message_with_loader.is_upgradeable_loader_present());
1078 }
1079
1080 #[test]
1081 fn is_writable_index_respects_header_layout() {
1082 let message = create_test_message();
1083 assert!(message.is_writable_index(0)); assert!(message.is_writable_index(1)); assert!(!message.is_writable_index(2)); }
1088
1089 #[test]
1090 fn is_writable_index_handles_mixed_signer_permissions() {
1091 let mut message = create_test_message();
1092 message.header.num_required_signatures = 2;
1094 message.header.num_readonly_signed_accounts = 1;
1095 message.header.num_readonly_unsigned_accounts = 1;
1096 message.account_keys = vec![
1097 Address::new_unique(), Address::new_unique(), Address::new_unique(), ];
1101 message.instructions[0].program_id_index = 2;
1102 message.instructions[0].accounts = vec![0, 1];
1103
1104 assert!(message.sanitize().is_ok());
1105 assert!(message.is_writable_index(0)); assert!(!message.is_writable_index(1)); assert!(!message.is_writable_index(2)); assert!(!message.is_writable_index(999)); }
1110
1111 #[test]
1112 fn is_maybe_writable_returns_false_for_readonly_index() {
1113 let message = create_test_message();
1114 assert!(!message.is_writable_index(2));
1116 assert!(!message.is_maybe_writable(2, None));
1117 assert!(!message.is_maybe_writable(2, Some(&HashSet::new())));
1119 }
1120
1121 #[test]
1122 fn is_maybe_writable_demotes_reserved_accounts() {
1123 let message = create_test_message();
1124 let reserved = HashSet::from([message.account_keys[0]]);
1125 assert!(message.is_writable_index(0));
1127 assert!(!message.is_maybe_writable(0, Some(&reserved)));
1128 }
1129
1130 #[test]
1131 fn is_maybe_writable_demotes_programs_without_upgradeable_loader() {
1132 let message = create_test_message();
1133 assert!(message.is_writable_index(1));
1135 assert!(message.is_key_called_as_program(1));
1136 assert!(!message.is_upgradeable_loader_present());
1137 assert!(!message.is_maybe_writable(1, None));
1138 }
1139
1140 #[test]
1141 fn is_maybe_writable_preserves_programs_with_upgradeable_loader() {
1142 let mut message = create_test_message();
1143 message.account_keys.push(bpf_loader_upgradeable::id());
1145
1146 assert!(message.sanitize().is_ok());
1147 assert!(message.is_writable_index(1));
1148 assert!(message.is_key_called_as_program(1));
1149 assert!(message.is_upgradeable_loader_present());
1150 assert!(message.is_maybe_writable(1, None));
1152 }
1153
1154 #[test]
1155 fn sanitize_accepts_valid_message() {
1156 let message = create_test_message();
1157 assert!(message.sanitize().is_ok());
1158 }
1159
1160 #[test]
1161 fn sanitize_rejects_zero_signers() {
1162 let mut message = create_test_message();
1163 message.header.num_required_signatures = 0;
1164 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1165 }
1166
1167 #[test]
1168 fn sanitize_rejects_over_12_signatures() {
1169 let mut message = create_test_message();
1170 message.header.num_required_signatures = MAX_SIGNATURES + 1;
1171 message.account_keys = (0..MAX_SIGNATURES + 1)
1172 .map(|_| Address::new_unique())
1173 .collect();
1174 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1175 }
1176
1177 #[test]
1178 fn sanitize_rejects_over_64_addresses() {
1179 let mut message = create_test_message();
1180 message.account_keys = (0..65).map(|_| Address::new_unique()).collect();
1181 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1182 }
1183
1184 #[test]
1185 fn sanitize_rejects_over_64_instructions() {
1186 let mut message = create_test_message();
1187 message.instructions = (0..65) .map(|_| CompiledInstruction {
1189 program_id_index: 1,
1190 accounts: vec![0],
1191 data: vec![],
1192 })
1193 .collect();
1194 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1195 }
1196
1197 #[test]
1198 fn sanitize_rejects_insufficient_accounts_for_header() {
1199 let mut message = create_test_message();
1200 message.header.num_readonly_unsigned_accounts = 10;
1203 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1204 }
1205
1206 #[test]
1207 fn sanitize_rejects_all_signers_readonly() {
1208 let mut message = create_test_message();
1209 message.header.num_readonly_signed_accounts = 1; assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1211 }
1212
1213 #[test]
1214 fn sanitize_rejects_duplicate_addresses() {
1215 let mut message = create_test_message();
1216 let dup = message.account_keys[0];
1217 message.account_keys[1] = dup;
1218 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1219 }
1220
1221 #[test]
1222 fn sanitize_rejects_unaligned_heap_size() {
1223 let mut message = create_test_message();
1224 message.config.heap_size = Some(1025); assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1226 }
1227
1228 #[test]
1229 fn sanitize_accepts_aligned_heap_size() {
1230 let mut message = create_test_message();
1231 message.config.heap_size = Some(65536); assert!(message.sanitize().is_ok());
1233 }
1234
1235 #[test]
1236 fn sanitize_rejects_invalid_program_id_index() {
1237 let mut message = create_test_message();
1238 message.instructions[0].program_id_index = 99;
1239 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1240 }
1241
1242 #[test]
1243 fn sanitize_rejects_fee_payer_as_program() {
1244 let mut message = create_test_message();
1245 message.instructions[0].program_id_index = 0;
1246 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1247 }
1248
1249 #[test]
1250 fn sanitize_rejects_instruction_with_too_many_accounts() {
1251 let mut message = create_test_message();
1252 message.instructions[0].accounts = vec![0u8; (u8::MAX as usize) + 1];
1253 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1254 }
1255
1256 #[test]
1257 fn sanitize_rejects_invalid_instruction_account_index() {
1258 let mut message = create_test_message();
1259 message.instructions[0].accounts = vec![0, 99]; assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1261 }
1262
1263 #[test]
1264 fn sanitize_accepts_64_addresses() {
1265 let mut message = create_test_message();
1266 message.account_keys = (0..MAX_ADDRESSES).map(|_| Address::new_unique()).collect();
1267 message.header.num_required_signatures = 1;
1268 message.header.num_readonly_signed_accounts = 0;
1269 message.header.num_readonly_unsigned_accounts = 1;
1270 message.instructions[0].program_id_index = 1;
1271 message.instructions[0].accounts = vec![0, 2];
1272 assert!(message.sanitize().is_ok());
1273 }
1274
1275 #[test]
1276 fn sanitize_accepts_64_instructions() {
1277 let mut message = create_test_message();
1278 message.instructions = (0..MAX_INSTRUCTIONS)
1279 .map(|_| CompiledInstruction {
1280 program_id_index: 1,
1281 accounts: vec![0, 2],
1282 data: vec![1, 2, 3],
1283 })
1284 .collect();
1285 assert!(message.sanitize().is_ok());
1286 }
1287
1288 #[test]
1289 fn size_matches_serialized_length() {
1290 let test_cases = [
1291 MessageBuilder::new()
1293 .required_signatures(1)
1294 .lifetime_specifier(Hash::new_unique())
1295 .accounts(vec![Address::new_unique()])
1296 .build()
1297 .unwrap(),
1298 MessageBuilder::new()
1300 .required_signatures(1)
1301 .lifetime_specifier(Hash::new_unique())
1302 .accounts(vec![Address::new_unique(), Address::new_unique()])
1303 .priority_fee(1000)
1304 .compute_unit_limit(200_000)
1305 .instruction(CompiledInstruction {
1306 program_id_index: 1,
1307 accounts: vec![0],
1308 data: vec![1, 2, 3, 4],
1309 })
1310 .build()
1311 .unwrap(),
1312 MessageBuilder::new()
1314 .required_signatures(2)
1315 .readonly_signed_accounts(1)
1316 .readonly_unsigned_accounts(1)
1317 .lifetime_specifier(Hash::new_unique())
1318 .accounts(vec![
1319 Address::new_unique(),
1320 Address::new_unique(),
1321 Address::new_unique(),
1322 Address::new_unique(),
1323 ])
1324 .heap_size(65536)
1325 .instructions(vec![
1326 CompiledInstruction {
1327 program_id_index: 2,
1328 accounts: vec![0, 1],
1329 data: vec![],
1330 },
1331 CompiledInstruction {
1332 program_id_index: 3,
1333 accounts: vec![0, 1, 2],
1334 data: vec![0xAA; 100],
1335 },
1336 ])
1337 .build()
1338 .unwrap(),
1339 ];
1340
1341 for message in &test_cases {
1342 assert_eq!(message.size(), serialize(message).len());
1343 }
1344 }
1345
1346 #[test]
1347 fn byte_layout_without_config() {
1348 let fee_payer = Address::new_from_array([1u8; 32]);
1349 let program = Address::new_from_array([2u8; 32]);
1350 let blockhash = Hash::new_from_array([0xAB; 32]);
1351
1352 let message = MessageBuilder::new()
1353 .required_signatures(1)
1354 .lifetime_specifier(blockhash)
1355 .accounts(vec![fee_payer, program])
1356 .instruction(CompiledInstruction {
1357 program_id_index: 1,
1358 accounts: vec![0],
1359 data: vec![0xDE, 0xAD],
1360 })
1361 .build()
1362 .unwrap();
1363
1364 let bytes = serialize(&message);
1365
1366 let mut expected = vec![1, 0, 0];
1372 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);
1385 }
1386
1387 #[test]
1388 fn byte_layout_with_config() {
1389 let fee_payer = Address::new_from_array([1u8; 32]);
1390 let program = Address::new_from_array([2u8; 32]);
1391 let blockhash = Hash::new_from_array([0xBB; 32]);
1392
1393 let message = MessageBuilder::new()
1394 .required_signatures(1)
1395 .lifetime_specifier(blockhash)
1396 .accounts(vec![fee_payer, program])
1397 .priority_fee(0x0102030405060708u64)
1398 .compute_unit_limit(0x11223344u32)
1399 .instruction(CompiledInstruction {
1400 program_id_index: 1,
1401 accounts: vec![],
1402 data: vec![],
1403 })
1404 .build()
1405 .unwrap();
1406
1407 let bytes = serialize(&message);
1408
1409 let mut expected = vec![1, 0, 0];
1410 expected.extend_from_slice(&7u32.to_le_bytes());
1412 expected.extend_from_slice(&[0xBB; 32]);
1413 expected.push(1);
1414 expected.push(2);
1415 expected.extend_from_slice(&[1u8; 32]);
1416 expected.extend_from_slice(&[2u8; 32]);
1417 expected.extend_from_slice(&0x0102030405060708u64.to_le_bytes());
1419 expected.extend_from_slice(&0x11223344u32.to_le_bytes());
1421 expected.push(1); expected.push(0); expected.extend_from_slice(&0u16.to_le_bytes()); assert_eq!(bytes, expected);
1426 }
1427
1428 #[test]
1429 fn roundtrip_preserves_all_config_fields() {
1430 let message = MessageBuilder::new()
1431 .required_signatures(1)
1432 .lifetime_specifier(Hash::new_unique())
1433 .accounts(vec![Address::new_unique(), Address::new_unique()])
1434 .priority_fee(1000)
1435 .compute_unit_limit(200_000)
1436 .loaded_accounts_data_size_limit(1_000_000)
1437 .heap_size(65536)
1438 .instruction(CompiledInstruction {
1439 program_id_index: 1,
1440 accounts: vec![0],
1441 data: vec![],
1442 })
1443 .build()
1444 .unwrap();
1445
1446 let serialized = serialize(&message);
1447 let (deserialized, _) = deserialize(&serialized).unwrap();
1448 assert_eq!(message.config, deserialized.config);
1449 }
1450}