1#[cfg(test)]
38pub use self::tests::MessageBuilder;
39#[cfg(feature = "serde")]
40use serde_derive::{Deserialize, Serialize};
41#[cfg(feature = "frozen-abi")]
42use solana_frozen_abi_macro::{AbiExample, StableAbi, StableAbiSample};
43#[cfg(feature = "wincode")]
44use {
45 crate::v1::{WireInstructionHeader, FIXED_HEADER_SIZE},
46 core::{mem::MaybeUninit, slice::from_raw_parts},
47 wincode::{
48 config::{Config, ConfigCore},
49 context,
50 io::{Reader, Writer},
51 len::SeqLen,
52 ReadResult, SchemaRead, SchemaReadContext, SchemaWrite, WriteResult,
53 },
54};
55use {
56 crate::{
57 compiled_instruction::CompiledInstruction,
58 compiled_keys::CompiledKeys,
59 v1::{
60 MessageError, TransactionConfig, TransactionConfigMask, MAX_ADDRESSES, MAX_HEAP_SIZE,
61 MAX_INSTRUCTIONS, MAX_SIGNATURES, MIN_HEAP_SIZE,
62 },
63 AccountKeys, AddressSet, CompileError, MessageHeader,
64 },
65 alloc::{collections::BTreeSet, vec::Vec},
66 core::mem::size_of,
67 solana_address::Address,
68 solana_hash::Hash,
69 solana_instruction::Instruction,
70 solana_sanitize::{Sanitize, SanitizeError},
71};
72
73#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
80#[cfg_attr(
81 feature = "serde",
82 derive(Serialize, Deserialize),
83 serde(rename_all = "camelCase")
84)]
85#[derive(Debug, Clone, PartialEq, Eq, Default)]
86pub struct Message {
87 pub header: MessageHeader,
89
90 pub config: TransactionConfig,
92
93 pub lifetime_specifier: Hash,
95
96 pub account_keys: Vec<Address>,
115
116 pub instructions: Vec<CompiledInstruction>,
118}
119
120impl Message {
121 pub fn new(
123 header: MessageHeader,
124 config: TransactionConfig,
125 lifetime_specifier: Hash,
126 account_keys: Vec<Address>,
127 instructions: Vec<CompiledInstruction>,
128 ) -> Self {
129 Self {
130 header,
131 config,
132 lifetime_specifier,
133 account_keys,
134 instructions,
135 }
136 }
137
138 pub fn try_compile_with_config(
215 payer: &Address,
216 instructions: &[Instruction],
217 recent_blockhash: Hash,
218 config: TransactionConfig,
219 ) -> Result<Self, CompileError> {
220 let compiled_keys = CompiledKeys::compile(instructions, Some(*payer));
221 let (header, static_keys) = compiled_keys.try_into_message_components()?;
222
223 let account_keys = AccountKeys::new(&static_keys, None);
224 let instructions = account_keys.try_compile_instructions(instructions)?;
225
226 Ok(Self {
227 header,
228 config,
229 lifetime_specifier: recent_blockhash,
230 account_keys: static_keys,
231 instructions,
232 })
233 }
234
235 pub fn fee_payer(&self) -> Option<&Address> {
237 self.account_keys.first()
238 }
239
240 pub fn is_signer(&self, index: usize) -> bool {
243 index < usize::from(self.header.num_required_signatures)
244 }
245
246 pub fn is_signer_writable(&self, index: usize) -> bool {
248 if !self.is_signer(index) {
249 return false;
250 }
251 let num_writable_signers = usize::from(self.header.num_required_signatures)
254 .saturating_sub(usize::from(self.header.num_readonly_signed_accounts));
255 index < num_writable_signers
256 }
257
258 pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
260 crate::is_key_called_as_program(&self.instructions, key_index)
261 }
262
263 #[inline(always)]
268 #[cfg(feature = "std")]
269 pub(crate) fn is_writable_index(&self, i: usize) -> bool {
270 crate::is_writable_index(i, self.header, &self.account_keys)
271 }
272
273 pub fn is_upgradeable_loader_present(&self) -> bool {
275 crate::is_upgradeable_loader_present(&self.account_keys)
276 }
277
278 pub fn is_maybe_writable_with_reserved_addresses(
293 &self,
294 key_index: usize,
295 reserved_addresses: Option<&impl AddressSet>,
296 ) -> bool {
297 crate::is_maybe_writable(
298 key_index,
299 self.header,
300 &self.account_keys,
301 &self.instructions,
302 reserved_addresses,
303 )
304 }
305
306 pub fn demote_program_id(&self, i: usize) -> bool {
307 crate::is_program_id_write_demoted(i, &self.account_keys, &self.instructions)
308 }
309
310 #[cfg(feature = "wincode")]
312 pub fn serialize(&self) -> Vec<u8> {
313 wincode::serialize(&(crate::v1::V1_PREFIX, self)).unwrap()
314 }
315
316 #[allow(clippy::arithmetic_side_effects)]
318 #[inline(always)]
319 pub fn size(&self) -> usize {
320 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()
328 * (
329 size_of::<u8>()
330 + size_of::<u8>()
331 + size_of::<u16>()
332 ) + self
334 .instructions
335 .iter()
336 .map(|ix| {
337 (ix.accounts.len() * size_of::<u8>())
338 + ix.data.len()
339 })
340 .sum::<usize>() }
342
343 pub fn validate(&self) -> Result<(), MessageError> {
344 if self.header.num_required_signatures > MAX_SIGNATURES {
346 return Err(MessageError::TooManySignatures);
347 }
348
349 if self.instructions.len() > MAX_INSTRUCTIONS as usize {
351 return Err(MessageError::TooManyInstructions);
352 }
353
354 let num_account_keys = self.account_keys.len();
355
356 if num_account_keys > MAX_ADDRESSES as usize {
358 return Err(MessageError::TooManyAddresses);
359 }
360
361 let min_accounts = usize::from(self.header.num_required_signatures)
363 .saturating_add(usize::from(self.header.num_readonly_unsigned_accounts));
364
365 if num_account_keys < min_accounts {
366 return Err(MessageError::NotEnoughAddressesForSignatures);
367 }
368
369 if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures {
371 return Err(MessageError::ZeroSigners);
372 }
373
374 let unique_keys: BTreeSet<_> = self.account_keys.iter().collect();
376 if unique_keys.len() != num_account_keys {
377 return Err(MessageError::DuplicateAddresses);
378 }
379
380 if let Some(heap_size) = self.config.heap_size {
386 if !heap_size.is_multiple_of(1024) {
387 return Err(MessageError::InvalidHeapSize);
388 }
389
390 if !(MIN_HEAP_SIZE..=MAX_HEAP_SIZE).contains(&heap_size) {
391 return Err(MessageError::InvalidHeapSize);
392 }
393 }
394
395 let max_account_index = num_account_keys
397 .checked_sub(1)
398 .ok_or(MessageError::NotEnoughAccountKeys)?;
399
400 for instruction in &self.instructions {
401 if usize::from(instruction.program_id_index) > max_account_index {
403 return Err(MessageError::InvalidInstructionAccountIndex);
404 }
405
406 if instruction.program_id_index == 0 {
408 return Err(MessageError::InvalidInstructionAccountIndex);
409 }
410
411 if instruction.accounts.len() > u8::MAX as usize {
413 return Err(MessageError::InstructionAccountsTooLarge);
414 }
415
416 if instruction.data.len() > u16::MAX as usize {
418 return Err(MessageError::InstructionDataTooLarge);
419 }
420
421 for &account_index in &instruction.accounts {
423 if usize::from(account_index) > max_account_index {
424 return Err(MessageError::InvalidInstructionAccountIndex);
425 }
426 }
427 }
428
429 Ok(())
430 }
431}
432
433impl Sanitize for Message {
434 fn sanitize(&self) -> Result<(), SanitizeError> {
435 Ok(self.validate()?)
436 }
437}
438
439#[cfg(feature = "wincode")]
440unsafe impl<C: ConfigCore> SchemaWrite<C> for Message {
441 type Src = Self;
442
443 #[inline(always)]
444 fn size_of(src: &Self::Src) -> WriteResult<usize> {
445 Ok(src.size())
446 }
447
448 fn write(mut writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
449 let mut writer = unsafe { writer.as_trusted_for(src.size()) }?;
451 writer.write(&[
452 src.header.num_required_signatures,
453 src.header.num_readonly_signed_accounts,
454 src.header.num_readonly_unsigned_accounts,
455 ])?;
456 let mask = TransactionConfigMask::from(&src.config).0.to_le_bytes();
457 writer.write(&mask)?;
458 writer.write(src.lifetime_specifier.as_bytes())?;
459 writer.write(&[src.instructions.len() as u8, src.account_keys.len() as u8])?;
460
461 #[expect(clippy::arithmetic_side_effects)]
464 let account_keys = unsafe {
465 from_raw_parts(
466 src.account_keys.as_ptr().cast::<u8>(),
467 src.account_keys.len() * size_of::<Address>(),
468 )
469 };
470 writer.write(account_keys)?;
471
472 if let Some(value) = src.config.priority_fee {
473 writer.write(&value.to_le_bytes())?;
474 }
475 if let Some(value) = src.config.compute_unit_limit {
476 writer.write(&value.to_le_bytes())?;
477 }
478 if let Some(value) = src.config.loaded_accounts_data_size_limit {
479 writer.write(&value.to_le_bytes())?;
480 }
481 if let Some(value) = src.config.heap_size {
482 writer.write(&value.to_le_bytes())?;
483 }
484
485 for ix in &src.instructions {
486 writer.write(&[ix.program_id_index, ix.accounts.len() as u8])?;
487 writer.write(&(ix.data.len() as u16).to_le_bytes())?;
488 }
489
490 for ix in &src.instructions {
491 writer.write(&ix.accounts)?;
492 writer.write(&ix.data)?;
493 }
494
495 writer.finish()?;
496
497 Ok(())
498 }
499}
500
501#[cfg(feature = "wincode")]
502unsafe impl<'de, C: Config> SchemaRead<'de, C> for Message {
503 type Dst = Message;
504
505 #[expect(clippy::arithmetic_side_effects)]
506 fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
507 let (header, lifetime_specifier, config_mask, num_instructions, num_addresses) = {
508 let mut reader = unsafe { reader.as_trusted_for(FIXED_HEADER_SIZE)? };
515 let header = <MessageHeader as SchemaRead<C>>::get(reader.by_ref())?;
516 let config_mask = TransactionConfigMask(u32::from_le_bytes(reader.take_array()?));
517 let lifetime_specifier = <Hash as SchemaRead<C>>::get(reader.by_ref())?;
518 let num_instructions = reader.take_byte()? as usize;
519 let num_addresses = reader.take_byte()? as usize;
520 (
521 header,
522 lifetime_specifier,
523 config_mask,
524 num_instructions,
525 num_addresses,
526 )
527 };
528
529 if config_mask.has_unknown_bits() || config_mask.has_invalid_priority_fee_bits() {
534 return Err(wincode::error::invalid_value(
535 "invalid transaction config mask",
536 ));
537 }
538
539 <C::LengthEncoding as SeqLen<C>>::prealloc_check::<Address>(num_addresses)?;
540 let account_keys = <Vec<Address> as SchemaReadContext<C, context::Len>>::get_with_context(
541 context::Len(num_addresses),
542 reader.by_ref(),
543 )?;
544
545 let mut config = TransactionConfig::empty();
546 if config_mask.has_priority_fee() {
547 config.priority_fee = Some(u64::from_le_bytes(reader.take_array()?));
548 }
549 if config_mask.has_compute_unit_limit() {
550 config.compute_unit_limit = Some(u32::from_le_bytes(reader.take_array()?));
551 }
552 if config_mask.has_loaded_accounts_data_size() {
553 config.loaded_accounts_data_size_limit = Some(u32::from_le_bytes(reader.take_array()?));
554 }
555 if config_mask.has_heap_size() {
556 config.heap_size = Some(u32::from_le_bytes(reader.take_array()?));
557 }
558
559 let instruction_headers = unsafe {
567 from_raw_parts(
568 reader
569 .take_borrowed(num_instructions * size_of::<WireInstructionHeader>())?
570 .as_ptr() as *const WireInstructionHeader,
571 num_instructions,
572 )
573 };
574 let mut instructions = Vec::with_capacity(num_instructions);
575 for header in instruction_headers {
576 let program_id_index = header.program_id_index;
577 let num_accounts = header.num_accounts as usize;
578 let data_len = u16::from_le_bytes(header.data_len) as usize;
579
580 <C::LengthEncoding as SeqLen<C>>::prealloc_check::<u8>(num_accounts)?;
581 let accounts = <Vec<u8> as SchemaReadContext<C, context::Len>>::get_with_context(
582 context::Len(num_accounts),
583 reader.by_ref(),
584 )?;
585 <C::LengthEncoding as SeqLen<C>>::prealloc_check::<u8>(data_len)?;
586 let data = <Vec<u8> as SchemaReadContext<C, context::Len>>::get_with_context(
587 context::Len(data_len),
588 reader.by_ref(),
589 )?;
590
591 instructions.push(CompiledInstruction {
592 program_id_index,
593 accounts,
594 data,
595 });
596 }
597
598 dst.write(Message {
599 header,
600 lifetime_specifier,
601 config,
602 account_keys,
603 instructions,
604 });
605
606 Ok(())
607 }
608}
609
610#[cfg(feature = "wincode")]
613#[inline]
614pub fn deserialize(input: &[u8]) -> wincode::ReadResult<Message> {
615 wincode::deserialize(input)
616}
617
618#[cfg(test)]
619mod tests {
620 use {super::*, alloc::vec, solana_sdk_ids::bpf_loader_upgradeable};
621
622 #[derive(Debug, Clone, Default)]
628 pub struct MessageBuilder {
629 header: MessageHeader,
630 config: TransactionConfig,
631 lifetime_specifier: Option<Hash>,
632 account_keys: Vec<Address>,
633 instructions: Vec<CompiledInstruction>,
634 }
635
636 impl MessageBuilder {
637 pub fn new() -> Self {
638 Self::default()
639 }
640
641 #[must_use]
642 pub fn required_signatures(mut self, count: u8) -> Self {
643 self.header.num_required_signatures = count;
644 self
645 }
646
647 #[must_use]
648 pub fn readonly_signed_accounts(mut self, count: u8) -> Self {
649 self.header.num_readonly_signed_accounts = count;
650 self
651 }
652
653 #[must_use]
654 pub fn readonly_unsigned_accounts(mut self, count: u8) -> Self {
655 self.header.num_readonly_unsigned_accounts = count;
656 self
657 }
658
659 #[must_use]
660 pub fn lifetime_specifier(mut self, hash: Hash) -> Self {
661 self.lifetime_specifier = Some(hash);
662 self
663 }
664
665 #[must_use]
666 pub fn config(mut self, config: TransactionConfig) -> Self {
667 self.config = config;
668 self
669 }
670
671 #[must_use]
672 pub fn priority_fee(mut self, fee: u64) -> Self {
673 self.config.priority_fee = Some(fee);
674 self
675 }
676
677 #[must_use]
678 pub fn compute_unit_limit(mut self, limit: u32) -> Self {
679 self.config.compute_unit_limit = Some(limit);
680 self
681 }
682
683 #[must_use]
684 pub fn loaded_accounts_data_size_limit(mut self, limit: u32) -> Self {
685 self.config.loaded_accounts_data_size_limit = Some(limit);
686 self
687 }
688
689 #[must_use]
690 pub fn heap_size(mut self, size: u32) -> Self {
691 self.config.heap_size = Some(size);
692 self
693 }
694
695 #[must_use]
696 pub fn account(mut self, key: Address) -> Self {
697 self.account_keys.push(key);
698 self
699 }
700
701 #[must_use]
702 pub fn accounts(mut self, keys: Vec<Address>) -> Self {
703 self.account_keys = keys;
704 self
705 }
706
707 #[must_use]
708 pub fn instruction(mut self, instruction: CompiledInstruction) -> Self {
709 self.instructions.push(instruction);
710 self
711 }
712
713 #[must_use]
714 pub fn instructions(mut self, instructions: Vec<CompiledInstruction>) -> Self {
715 self.instructions = instructions;
716 self
717 }
718
719 pub fn build(self) -> Result<Message, MessageError> {
721 let lifetime_specifier = self
722 .lifetime_specifier
723 .ok_or(MessageError::MissingLifetimeSpecifier)?;
724
725 let message = Message::new(
726 self.header,
727 self.config,
728 lifetime_specifier,
729 self.account_keys,
730 self.instructions,
731 );
732
733 message.validate()?;
734
735 Ok(message)
736 }
737 }
738
739 fn create_test_message() -> Message {
740 MessageBuilder::new()
741 .required_signatures(1)
742 .readonly_unsigned_accounts(1)
743 .lifetime_specifier(Hash::new_unique())
744 .accounts(vec![
745 Address::new_unique(), Address::new_unique(), Address::new_unique(), ])
749 .compute_unit_limit(200_000)
750 .instruction(CompiledInstruction {
751 program_id_index: 1,
752 accounts: vec![0, 2],
753 data: vec![1, 2, 3, 4],
754 })
755 .build()
756 .unwrap()
757 }
758
759 #[test]
760 fn fee_payer_returns_first_account() {
761 let fee_payer = Address::new_unique();
762 let message = MessageBuilder::new()
763 .required_signatures(1)
764 .lifetime_specifier(Hash::new_unique())
765 .accounts(vec![fee_payer, Address::new_unique()])
766 .build()
767 .unwrap();
768
769 assert_eq!(message.fee_payer(), Some(&fee_payer));
770 }
771
772 #[test]
773 fn fee_payer_returns_none_for_empty_accounts() {
774 let message = Message::new(
776 MessageHeader::default(),
777 TransactionConfig::default(),
778 Hash::new_unique(),
779 vec![],
780 vec![],
781 );
782
783 assert_eq!(message.fee_payer(), None);
784 }
785
786 #[test]
787 fn is_signer_checks_signature_requirement() {
788 let message = create_test_message();
789 assert!(message.is_signer(0)); assert!(!message.is_signer(1)); assert!(!message.is_signer(2)); }
793
794 #[test]
795 fn is_signer_writable_identifies_writable_signers() {
796 let message = MessageBuilder::new()
797 .required_signatures(3)
798 .readonly_signed_accounts(1) .lifetime_specifier(Hash::new_unique())
800 .accounts(vec![
801 Address::new_unique(), Address::new_unique(), Address::new_unique(), Address::new_unique(), ])
806 .build()
807 .unwrap();
808
809 assert!(message.is_signer_writable(0));
811 assert!(message.is_signer_writable(1));
812 assert!(!message.is_signer_writable(2));
814 assert!(!message.is_signer_writable(3));
816 assert!(!message.is_signer_writable(100));
817 }
818
819 #[test]
820 fn is_signer_writable_all_writable_when_no_readonly() {
821 let message = MessageBuilder::new()
822 .required_signatures(2)
823 .readonly_signed_accounts(0) .lifetime_specifier(Hash::new_unique())
825 .accounts(vec![
826 Address::new_unique(),
827 Address::new_unique(),
828 Address::new_unique(),
829 ])
830 .build()
831 .unwrap();
832
833 assert!(message.is_signer_writable(0));
834 assert!(message.is_signer_writable(1));
835 assert!(!message.is_signer_writable(2)); }
837
838 #[test]
839 fn is_key_called_as_program_detects_program_indices() {
840 let message = create_test_message();
841 assert!(message.is_key_called_as_program(1));
843 assert!(!message.is_key_called_as_program(0));
844 assert!(!message.is_key_called_as_program(2));
845 assert!(!message.is_key_called_as_program(256));
847 assert!(!message.is_key_called_as_program(10_000));
848 }
849
850 #[test]
851 fn is_upgradeable_loader_present_detects_loader() {
852 let message = create_test_message();
853 assert!(!message.is_upgradeable_loader_present());
854
855 let mut message_with_loader = create_test_message();
856 message_with_loader
857 .account_keys
858 .push(bpf_loader_upgradeable::id());
859 assert!(message_with_loader.is_upgradeable_loader_present());
860 }
861
862 #[test]
863 fn is_writable_index_respects_header_layout() {
864 let message = create_test_message();
865 assert!(message.is_writable_index(0)); assert!(message.is_writable_index(1)); assert!(!message.is_writable_index(2)); }
870
871 #[test]
872 fn is_writable_index_handles_mixed_signer_permissions() {
873 let mut message = create_test_message();
874 message.header.num_required_signatures = 2;
876 message.header.num_readonly_signed_accounts = 1;
877 message.header.num_readonly_unsigned_accounts = 1;
878 message.account_keys = vec![
879 Address::new_unique(), Address::new_unique(), Address::new_unique(), ];
883 message.instructions[0].program_id_index = 2;
884 message.instructions[0].accounts = vec![0, 1];
885
886 assert!(message.sanitize().is_ok());
887 assert!(message.is_writable_index(0)); assert!(!message.is_writable_index(1)); assert!(!message.is_writable_index(2)); assert!(!message.is_writable_index(999)); }
892
893 #[test]
894 fn sanitize_accepts_valid_message() {
895 let message = create_test_message();
896 assert!(message.sanitize().is_ok());
897 }
898
899 #[test]
900 fn sanitize_rejects_zero_signers() {
901 let mut message = create_test_message();
902 message.header.num_required_signatures = 0;
903 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
904 }
905
906 #[test]
907 fn sanitize_rejects_over_12_signatures() {
908 let mut message = create_test_message();
909 message.header.num_required_signatures = MAX_SIGNATURES + 1;
910 message.account_keys = (0..MAX_SIGNATURES + 1)
911 .map(|_| Address::new_unique())
912 .collect();
913 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
914 }
915
916 #[test]
917 fn sanitize_rejects_over_64_addresses() {
918 let mut message = create_test_message();
919 message.account_keys = (0..65).map(|_| Address::new_unique()).collect();
920 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
921 }
922
923 #[test]
924 fn sanitize_rejects_over_64_instructions() {
925 let mut message = create_test_message();
926 message.instructions = (0..65) .map(|_| CompiledInstruction {
928 program_id_index: 1,
929 accounts: vec![0],
930 data: vec![],
931 })
932 .collect();
933 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
934 }
935
936 #[test]
937 fn sanitize_rejects_insufficient_accounts_for_header() {
938 let mut message = create_test_message();
939 message.header.num_readonly_unsigned_accounts = 10;
942 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
943 }
944
945 #[test]
946 fn sanitize_rejects_all_signers_readonly() {
947 let mut message = create_test_message();
948 message.header.num_readonly_signed_accounts = 1; assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
950 }
951
952 #[test]
953 fn sanitize_rejects_duplicate_addresses() {
954 let mut message = create_test_message();
955 let dup = message.account_keys[0];
956 message.account_keys[1] = dup;
957 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
958 }
959
960 #[test]
961 fn sanitize_rejects_unaligned_heap_size() {
962 let mut message = create_test_message();
963 message.config.heap_size = Some(1025); assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
965 }
966
967 #[test]
968 fn sanitize_rejects_heap_size_below_minimum() {
969 let mut message = create_test_message();
970 message.config.heap_size = Some(MIN_HEAP_SIZE - 1);
971 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
972 }
973
974 #[test]
975 fn sanitize_rejects_heap_size_above_maximum() {
976 let mut message = create_test_message();
977 message.config.heap_size = Some(MAX_HEAP_SIZE + 1);
978 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
979 }
980
981 #[test]
982 fn sanitize_accepts_minimum_heap_size() {
983 let mut message = create_test_message();
984 message.config.heap_size = Some(MIN_HEAP_SIZE);
985 assert!(message.sanitize().is_ok());
986 }
987
988 #[test]
989 fn sanitize_accepts_maximum_heap_size() {
990 let mut message = create_test_message();
991 message.config.heap_size = Some(MAX_HEAP_SIZE);
992 assert!(message.sanitize().is_ok());
993 }
994
995 #[test]
996 fn sanitize_accepts_aligned_heap_size() {
997 let mut message = create_test_message();
998 message.config.heap_size = Some(65536); assert!(message.sanitize().is_ok());
1000 }
1001
1002 #[test]
1003 fn sanitize_rejects_invalid_program_id_index() {
1004 let mut message = create_test_message();
1005 message.instructions[0].program_id_index = 99;
1006 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1007 }
1008
1009 #[test]
1010 fn sanitize_rejects_fee_payer_as_program() {
1011 let mut message = create_test_message();
1012 message.instructions[0].program_id_index = 0;
1013 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1014 }
1015
1016 #[test]
1017 fn sanitize_rejects_instruction_with_too_many_accounts() {
1018 let mut message = create_test_message();
1019 message.instructions[0].accounts = vec![0u8; (u8::MAX as usize) + 1];
1020 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1021 }
1022
1023 #[test]
1024 fn sanitize_rejects_invalid_instruction_account_index() {
1025 let mut message = create_test_message();
1026 message.instructions[0].accounts = vec![0, 99]; assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1028 }
1029
1030 #[test]
1031 fn sanitize_accepts_64_addresses() {
1032 let mut message = create_test_message();
1033 message.account_keys = (0..MAX_ADDRESSES).map(|_| Address::new_unique()).collect();
1034 message.header.num_required_signatures = 1;
1035 message.header.num_readonly_signed_accounts = 0;
1036 message.header.num_readonly_unsigned_accounts = 1;
1037 message.instructions[0].program_id_index = 1;
1038 message.instructions[0].accounts = vec![0, 2];
1039 assert!(message.sanitize().is_ok());
1040 }
1041
1042 #[test]
1043 fn sanitize_accepts_64_instructions() {
1044 let mut message = create_test_message();
1045 message.instructions = (0..MAX_INSTRUCTIONS)
1046 .map(|_| CompiledInstruction {
1047 program_id_index: 1,
1048 accounts: vec![0, 2],
1049 data: vec![1, 2, 3],
1050 })
1051 .collect();
1052 assert!(message.sanitize().is_ok());
1053 }
1054
1055 #[test]
1056 fn size_matches_serialized_length() {
1057 let test_cases = [
1058 MessageBuilder::new()
1060 .required_signatures(1)
1061 .lifetime_specifier(Hash::new_unique())
1062 .accounts(vec![Address::new_unique()])
1063 .build()
1064 .unwrap(),
1065 MessageBuilder::new()
1067 .required_signatures(1)
1068 .lifetime_specifier(Hash::new_unique())
1069 .accounts(vec![Address::new_unique(), Address::new_unique()])
1070 .priority_fee(1000)
1071 .compute_unit_limit(200_000)
1072 .instruction(CompiledInstruction {
1073 program_id_index: 1,
1074 accounts: vec![0],
1075 data: vec![1, 2, 3, 4],
1076 })
1077 .build()
1078 .unwrap(),
1079 MessageBuilder::new()
1081 .required_signatures(2)
1082 .readonly_signed_accounts(1)
1083 .readonly_unsigned_accounts(1)
1084 .lifetime_specifier(Hash::new_unique())
1085 .accounts(vec![
1086 Address::new_unique(),
1087 Address::new_unique(),
1088 Address::new_unique(),
1089 Address::new_unique(),
1090 ])
1091 .heap_size(65536)
1092 .instructions(vec![
1093 CompiledInstruction {
1094 program_id_index: 2,
1095 accounts: vec![0, 1],
1096 data: vec![],
1097 },
1098 CompiledInstruction {
1099 program_id_index: 3,
1100 accounts: vec![0, 1, 2],
1101 data: vec![0xAA; 100],
1102 },
1103 ])
1104 .build()
1105 .unwrap(),
1106 ];
1107
1108 for message in &test_cases {
1109 assert_eq!(message.size(), wincode::serialize(message).unwrap().len());
1110 }
1111 }
1112
1113 #[test]
1114 fn byte_layout_without_config() {
1115 let fee_payer = Address::new_from_array([1u8; 32]);
1116 let program = Address::new_from_array([2u8; 32]);
1117 let blockhash = Hash::new_from_array([0xAB; 32]);
1118
1119 let message = MessageBuilder::new()
1120 .required_signatures(1)
1121 .lifetime_specifier(blockhash)
1122 .accounts(vec![fee_payer, program])
1123 .instruction(CompiledInstruction {
1124 program_id_index: 1,
1125 accounts: vec![0],
1126 data: vec![0xDE, 0xAD],
1127 })
1128 .build()
1129 .unwrap();
1130
1131 let bytes = wincode::serialize(&message).unwrap();
1132
1133 let mut expected = vec![1, 0, 0];
1139 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);
1152 }
1153
1154 #[test]
1155 fn byte_layout_with_config() {
1156 let fee_payer = Address::new_from_array([1u8; 32]);
1157 let program = Address::new_from_array([2u8; 32]);
1158 let blockhash = Hash::new_from_array([0xBB; 32]);
1159
1160 let message = MessageBuilder::new()
1161 .required_signatures(1)
1162 .lifetime_specifier(blockhash)
1163 .accounts(vec![fee_payer, program])
1164 .priority_fee(0x0102030405060708u64)
1165 .compute_unit_limit(0x11223344u32)
1166 .instruction(CompiledInstruction {
1167 program_id_index: 1,
1168 accounts: vec![],
1169 data: vec![],
1170 })
1171 .build()
1172 .unwrap();
1173
1174 let bytes = wincode::serialize(&message).unwrap();
1175
1176 let mut expected = vec![1, 0, 0];
1177 expected.extend_from_slice(&7u32.to_le_bytes());
1179 expected.extend_from_slice(&[0xBB; 32]);
1180 expected.push(1);
1181 expected.push(2);
1182 expected.extend_from_slice(&[1u8; 32]);
1183 expected.extend_from_slice(&[2u8; 32]);
1184 expected.extend_from_slice(&0x0102030405060708u64.to_le_bytes());
1186 expected.extend_from_slice(&0x11223344u32.to_le_bytes());
1188 expected.push(1); expected.push(0); expected.extend_from_slice(&0u16.to_le_bytes()); assert_eq!(bytes, expected);
1193 }
1194
1195 #[test]
1196 fn roundtrip_preserves_all_config_fields() {
1197 let message = MessageBuilder::new()
1198 .required_signatures(1)
1199 .lifetime_specifier(Hash::new_unique())
1200 .accounts(vec![Address::new_unique(), Address::new_unique()])
1201 .priority_fee(1000)
1202 .compute_unit_limit(200_000)
1203 .loaded_accounts_data_size_limit(1_000_000)
1204 .heap_size(65536)
1205 .instruction(CompiledInstruction {
1206 program_id_index: 1,
1207 accounts: vec![0],
1208 data: vec![],
1209 })
1210 .build()
1211 .unwrap();
1212
1213 let serialized = wincode::serialize(&message).unwrap();
1214 let deserialized = deserialize(&serialized).unwrap();
1215 assert_eq!(message.config, deserialized.config);
1216 }
1217
1218 #[test]
1219 fn deserialize_rejects_unknown_config_mask_bits() {
1220 let message = MessageBuilder::default()
1221 .required_signatures(1)
1222 .lifetime_specifier(Hash::new_unique())
1223 .accounts(vec![Address::new_unique(), Address::new_unique()])
1224 .instruction(CompiledInstruction {
1225 program_id_index: 1,
1226 accounts: vec![0],
1227 data: vec![],
1228 })
1229 .build()
1230 .unwrap();
1231
1232 let mut serialized = wincode::serialize(&message).unwrap();
1233 assert!(deserialize(&serialized).is_ok());
1235
1236 let unknown_bit = 1u32 << TransactionConfigMask::KNOWN_BITS.trailing_ones();
1240 let mask = u32::from_le_bytes(serialized[3..7].try_into().unwrap()) | unknown_bit;
1241 serialized[3..7].copy_from_slice(&mask.to_le_bytes());
1242 assert!(deserialize(&serialized).is_err());
1243 }
1244
1245 #[test]
1246 fn deserialize_rejects_partial_priority_fee_bits() {
1247 let message = MessageBuilder::default()
1248 .required_signatures(1)
1249 .lifetime_specifier(Hash::new_unique())
1250 .accounts(vec![Address::new_unique(), Address::new_unique()])
1251 .instruction(CompiledInstruction {
1252 program_id_index: 1,
1253 accounts: vec![0],
1254 data: vec![],
1255 })
1256 .build()
1257 .unwrap();
1258
1259 let mut serialized = wincode::serialize(&message).unwrap();
1260 serialized[3] |= 0b1;
1262 assert!(deserialize(&serialized).is_err());
1263 }
1264}