1#[cfg(test)]
38pub use self::tests::MessageBuilder;
39#[cfg(feature = "serde")]
40use serde_derive::{Deserialize, Serialize};
41#[cfg(feature = "stable-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::{MessageError, TransactionConfig, TransactionConfigMask},
60 AccountKeys, AddressSet, CompileError, MessageHeader,
61 },
62 alloc::vec::Vec,
63 core::mem::size_of,
64 solana_address::Address,
65 solana_hash::Hash,
66 solana_instruction::Instruction,
67 solana_sanitize::{Sanitize, SanitizeError},
68};
69
70#[cfg_attr(feature = "stable-abi", derive(AbiExample, StableAbi, StableAbiSample))]
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_with_config(
212 payer: &Address,
213 instructions: &[Instruction],
214 recent_blockhash: Hash,
215 config: TransactionConfig,
216 ) -> Result<Self, CompileError> {
217 let compiled_keys = CompiledKeys::compile(instructions, Some(*payer));
218 let (header, static_keys) = compiled_keys.try_into_message_components()?;
219
220 let account_keys = AccountKeys::new(&static_keys, None);
221 let instructions = account_keys.try_compile_instructions(instructions)?;
222
223 Ok(Self {
224 header,
225 config,
226 lifetime_specifier: recent_blockhash,
227 account_keys: static_keys,
228 instructions,
229 })
230 }
231
232 pub fn fee_payer(&self) -> Option<&Address> {
234 self.account_keys.first()
235 }
236
237 pub fn is_signer(&self, index: usize) -> bool {
240 index < usize::from(self.header.num_required_signatures)
241 }
242
243 pub fn is_signer_writable(&self, index: usize) -> bool {
245 if !self.is_signer(index) {
246 return false;
247 }
248 let num_writable_signers = usize::from(self.header.num_required_signatures)
251 .saturating_sub(usize::from(self.header.num_readonly_signed_accounts));
252 index < num_writable_signers
253 }
254
255 pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
257 crate::is_key_called_as_program(&self.instructions, key_index)
258 }
259
260 #[inline(always)]
265 #[cfg(feature = "std")]
266 pub(crate) fn is_writable_index(&self, i: usize) -> bool {
267 crate::is_writable_index(i, self.header, &self.account_keys)
268 }
269
270 pub fn is_upgradeable_loader_present(&self) -> bool {
272 crate::is_upgradeable_loader_present(&self.account_keys)
273 }
274
275 pub fn is_maybe_writable_with_reserved_addresses(
290 &self,
291 key_index: usize,
292 reserved_addresses: Option<&impl AddressSet>,
293 ) -> bool {
294 crate::is_maybe_writable(
295 key_index,
296 self.header,
297 &self.account_keys,
298 &self.instructions,
299 reserved_addresses,
300 )
301 }
302
303 pub fn demote_program_id(&self, i: usize) -> bool {
304 crate::is_program_id_write_demoted(i, &self.account_keys, &self.instructions)
305 }
306
307 #[cfg(feature = "wincode")]
309 pub fn serialize(&self) -> Vec<u8> {
310 wincode::serialize(&(crate::v1::V1_PREFIX, self)).unwrap()
311 }
312
313 #[allow(clippy::arithmetic_side_effects)]
315 #[inline(always)]
316 pub fn size(&self) -> usize {
317 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()
325 * (
326 size_of::<u8>()
327 + size_of::<u8>()
328 + size_of::<u16>()
329 ) + self
331 .instructions
332 .iter()
333 .map(|ix| {
334 (ix.accounts.len() * size_of::<u8>())
335 + ix.data.len()
336 })
337 .sum::<usize>() }
339
340 pub fn validate(&self) -> Result<(), MessageError> {
341 if self.instructions.len() > u8::MAX as usize {
343 return Err(MessageError::TooManyInstructions);
344 }
345
346 let num_account_keys = self.account_keys.len();
347
348 if num_account_keys > u8::MAX as usize {
349 return Err(MessageError::TooManyAddresses);
350 }
351
352 let min_accounts = usize::from(self.header.num_required_signatures)
354 .saturating_add(usize::from(self.header.num_readonly_unsigned_accounts));
355
356 if num_account_keys < min_accounts {
357 return Err(MessageError::NotEnoughAddressesForSignatures);
358 }
359
360 if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures {
362 return Err(MessageError::ZeroSigners);
363 }
364
365 let max_account_index = num_account_keys
371 .checked_sub(1)
372 .ok_or(MessageError::NotEnoughAccountKeys)?;
373
374 for instruction in &self.instructions {
375 if usize::from(instruction.program_id_index) > max_account_index {
377 return Err(MessageError::InvalidInstructionAccountIndex);
378 }
379
380 if instruction.program_id_index == 0 {
382 return Err(MessageError::InvalidInstructionAccountIndex);
383 }
384
385 if instruction.accounts.len() > u8::MAX as usize {
387 return Err(MessageError::InstructionAccountsTooLarge);
388 }
389
390 if instruction.data.len() > u16::MAX as usize {
392 return Err(MessageError::InstructionDataTooLarge);
393 }
394
395 for &account_index in &instruction.accounts {
397 if usize::from(account_index) > max_account_index {
398 return Err(MessageError::InvalidInstructionAccountIndex);
399 }
400 }
401 }
402
403 Ok(())
404 }
405}
406
407impl Sanitize for Message {
408 fn sanitize(&self) -> Result<(), SanitizeError> {
409 Ok(self.validate()?)
410 }
411}
412
413#[cfg(feature = "wincode")]
414unsafe impl<C: ConfigCore> SchemaWrite<C> for Message {
415 type Src = Self;
416
417 #[inline(always)]
418 fn size_of(src: &Self::Src) -> WriteResult<usize> {
419 Ok(src.size())
420 }
421
422 fn write(mut writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
423 let mut writer = unsafe { writer.as_trusted_for(src.size()) }?;
425 writer.write(&[
426 src.header.num_required_signatures,
427 src.header.num_readonly_signed_accounts,
428 src.header.num_readonly_unsigned_accounts,
429 ])?;
430 let mask = TransactionConfigMask::from(&src.config).0.to_le_bytes();
431 writer.write(&mask)?;
432 writer.write(src.lifetime_specifier.as_bytes())?;
433 writer.write(&[src.instructions.len() as u8, src.account_keys.len() as u8])?;
434
435 #[expect(clippy::arithmetic_side_effects)]
438 let account_keys = unsafe {
439 from_raw_parts(
440 src.account_keys.as_ptr().cast::<u8>(),
441 src.account_keys.len() * size_of::<Address>(),
442 )
443 };
444 writer.write(account_keys)?;
445
446 if let Some(value) = src.config.priority_fee {
447 writer.write(&value.to_le_bytes())?;
448 }
449 if let Some(value) = src.config.compute_unit_limit {
450 writer.write(&value.to_le_bytes())?;
451 }
452 if let Some(value) = src.config.loaded_accounts_data_size_limit {
453 writer.write(&value.to_le_bytes())?;
454 }
455 if let Some(value) = src.config.heap_size {
456 writer.write(&value.to_le_bytes())?;
457 }
458
459 for ix in &src.instructions {
460 writer.write(&[ix.program_id_index, ix.accounts.len() as u8])?;
461 writer.write(&(ix.data.len() as u16).to_le_bytes())?;
462 }
463
464 for ix in &src.instructions {
465 writer.write(&ix.accounts)?;
466 writer.write(&ix.data)?;
467 }
468
469 writer.finish()?;
470
471 Ok(())
472 }
473}
474
475#[cfg(feature = "wincode")]
476unsafe impl<'de, C: Config> SchemaRead<'de, C> for Message {
477 type Dst = Message;
478
479 #[expect(clippy::arithmetic_side_effects)]
480 fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
481 let (header, lifetime_specifier, config_mask, num_instructions, num_addresses) = {
482 let mut reader = unsafe { reader.as_trusted_for(FIXED_HEADER_SIZE)? };
489 let header = <MessageHeader as SchemaRead<C>>::get(reader.by_ref())?;
490 let config_mask = TransactionConfigMask(u32::from_le_bytes(reader.take_array()?));
491 let lifetime_specifier = <Hash as SchemaRead<C>>::get(reader.by_ref())?;
492 let num_instructions = reader.take_byte()? as usize;
493 let num_addresses = reader.take_byte()? as usize;
494 (
495 header,
496 lifetime_specifier,
497 config_mask,
498 num_instructions,
499 num_addresses,
500 )
501 };
502
503 if config_mask.has_unknown_bits() || config_mask.has_invalid_priority_fee_bits() {
508 return Err(wincode::error::invalid_value(
509 "invalid transaction config mask",
510 ));
511 }
512
513 <C::LengthEncoding as SeqLen<C>>::prealloc_check::<Address>(num_addresses)?;
514 let account_keys = <Vec<Address> as SchemaReadContext<C, context::Len>>::get_with_context(
515 context::Len(num_addresses),
516 reader.by_ref(),
517 )?;
518
519 let mut config = TransactionConfig::empty();
520 if config_mask.has_priority_fee() {
521 config.priority_fee = Some(u64::from_le_bytes(reader.take_array()?));
522 }
523 if config_mask.has_compute_unit_limit() {
524 config.compute_unit_limit = Some(u32::from_le_bytes(reader.take_array()?));
525 }
526 if config_mask.has_loaded_accounts_data_size() {
527 config.loaded_accounts_data_size_limit = Some(u32::from_le_bytes(reader.take_array()?));
528 }
529 if config_mask.has_heap_size() {
530 config.heap_size = Some(u32::from_le_bytes(reader.take_array()?));
531 }
532
533 let instruction_headers = unsafe {
541 from_raw_parts(
542 reader
543 .take_borrowed(num_instructions * size_of::<WireInstructionHeader>())?
544 .as_ptr() as *const WireInstructionHeader,
545 num_instructions,
546 )
547 };
548 let mut instructions = Vec::with_capacity(num_instructions);
549 for header in instruction_headers {
550 let program_id_index = header.program_id_index;
551 let num_accounts = header.num_accounts as usize;
552 let data_len = u16::from_le_bytes(header.data_len) as usize;
553
554 <C::LengthEncoding as SeqLen<C>>::prealloc_check::<u8>(num_accounts)?;
555 let accounts = <Vec<u8> as SchemaReadContext<C, context::Len>>::get_with_context(
556 context::Len(num_accounts),
557 reader.by_ref(),
558 )?;
559 <C::LengthEncoding as SeqLen<C>>::prealloc_check::<u8>(data_len)?;
560 let data = <Vec<u8> as SchemaReadContext<C, context::Len>>::get_with_context(
561 context::Len(data_len),
562 reader.by_ref(),
563 )?;
564
565 instructions.push(CompiledInstruction {
566 program_id_index,
567 accounts,
568 data,
569 });
570 }
571
572 dst.write(Message {
573 header,
574 lifetime_specifier,
575 config,
576 account_keys,
577 instructions,
578 });
579
580 Ok(())
581 }
582}
583
584#[cfg(feature = "wincode")]
587#[inline]
588pub fn deserialize(input: &[u8]) -> wincode::ReadResult<Message> {
589 wincode::deserialize(input)
590}
591
592#[cfg(test)]
593mod tests {
594 use {
595 super::*,
596 crate::v1::{MAX_HEAP_SIZE, MIN_HEAP_SIZE},
597 alloc::vec,
598 solana_sdk_ids::bpf_loader_upgradeable,
599 };
600
601 #[derive(Debug, Clone, Default)]
607 pub struct MessageBuilder {
608 header: MessageHeader,
609 config: TransactionConfig,
610 lifetime_specifier: Option<Hash>,
611 account_keys: Vec<Address>,
612 instructions: Vec<CompiledInstruction>,
613 }
614
615 impl MessageBuilder {
616 pub fn new() -> Self {
617 Self::default()
618 }
619
620 #[must_use]
621 pub fn required_signatures(mut self, count: u8) -> Self {
622 self.header.num_required_signatures = count;
623 self
624 }
625
626 #[must_use]
627 pub fn readonly_signed_accounts(mut self, count: u8) -> Self {
628 self.header.num_readonly_signed_accounts = count;
629 self
630 }
631
632 #[must_use]
633 pub fn readonly_unsigned_accounts(mut self, count: u8) -> Self {
634 self.header.num_readonly_unsigned_accounts = count;
635 self
636 }
637
638 #[must_use]
639 pub fn lifetime_specifier(mut self, hash: Hash) -> Self {
640 self.lifetime_specifier = Some(hash);
641 self
642 }
643
644 #[must_use]
645 pub fn config(mut self, config: TransactionConfig) -> Self {
646 self.config = config;
647 self
648 }
649
650 #[must_use]
651 pub fn priority_fee(mut self, fee: u64) -> Self {
652 self.config.priority_fee = Some(fee);
653 self
654 }
655
656 #[must_use]
657 pub fn compute_unit_limit(mut self, limit: u32) -> Self {
658 self.config.compute_unit_limit = Some(limit);
659 self
660 }
661
662 #[must_use]
663 pub fn loaded_accounts_data_size_limit(mut self, limit: u32) -> Self {
664 self.config.loaded_accounts_data_size_limit = Some(limit);
665 self
666 }
667
668 #[must_use]
669 pub fn heap_size(mut self, size: u32) -> Self {
670 self.config.heap_size = Some(size);
671 self
672 }
673
674 #[must_use]
675 pub fn account(mut self, key: Address) -> Self {
676 self.account_keys.push(key);
677 self
678 }
679
680 #[must_use]
681 pub fn accounts(mut self, keys: Vec<Address>) -> Self {
682 self.account_keys = keys;
683 self
684 }
685
686 #[must_use]
687 pub fn instruction(mut self, instruction: CompiledInstruction) -> Self {
688 self.instructions.push(instruction);
689 self
690 }
691
692 #[must_use]
693 pub fn instructions(mut self, instructions: Vec<CompiledInstruction>) -> Self {
694 self.instructions = instructions;
695 self
696 }
697
698 pub fn build(self) -> Result<Message, MessageError> {
700 let lifetime_specifier = self
701 .lifetime_specifier
702 .ok_or(MessageError::MissingLifetimeSpecifier)?;
703
704 let message = Message::new(
705 self.header,
706 self.config,
707 lifetime_specifier,
708 self.account_keys,
709 self.instructions,
710 );
711
712 message.validate()?;
713
714 Ok(message)
715 }
716 }
717
718 fn create_test_message() -> Message {
719 MessageBuilder::new()
720 .required_signatures(1)
721 .readonly_unsigned_accounts(1)
722 .lifetime_specifier(Hash::new_unique())
723 .accounts(vec![
724 Address::new_unique(), Address::new_unique(), Address::new_unique(), ])
728 .compute_unit_limit(200_000)
729 .instruction(CompiledInstruction {
730 program_id_index: 1,
731 accounts: vec![0, 2],
732 data: vec![1, 2, 3, 4],
733 })
734 .build()
735 .unwrap()
736 }
737
738 #[test]
739 fn fee_payer_returns_first_account() {
740 let fee_payer = Address::new_unique();
741 let message = MessageBuilder::new()
742 .required_signatures(1)
743 .lifetime_specifier(Hash::new_unique())
744 .accounts(vec![fee_payer, Address::new_unique()])
745 .build()
746 .unwrap();
747
748 assert_eq!(message.fee_payer(), Some(&fee_payer));
749 }
750
751 #[test]
752 fn fee_payer_returns_none_for_empty_accounts() {
753 let message = Message::new(
755 MessageHeader::default(),
756 TransactionConfig::default(),
757 Hash::new_unique(),
758 vec![],
759 vec![],
760 );
761
762 assert_eq!(message.fee_payer(), None);
763 }
764
765 #[test]
766 fn is_signer_checks_signature_requirement() {
767 let message = create_test_message();
768 assert!(message.is_signer(0)); assert!(!message.is_signer(1)); assert!(!message.is_signer(2)); }
772
773 #[test]
774 fn is_signer_writable_identifies_writable_signers() {
775 let message = MessageBuilder::new()
776 .required_signatures(3)
777 .readonly_signed_accounts(1) .lifetime_specifier(Hash::new_unique())
779 .accounts(vec![
780 Address::new_unique(), Address::new_unique(), Address::new_unique(), Address::new_unique(), ])
785 .build()
786 .unwrap();
787
788 assert!(message.is_signer_writable(0));
790 assert!(message.is_signer_writable(1));
791 assert!(!message.is_signer_writable(2));
793 assert!(!message.is_signer_writable(3));
795 assert!(!message.is_signer_writable(100));
796 }
797
798 #[test]
799 fn is_signer_writable_all_writable_when_no_readonly() {
800 let message = MessageBuilder::new()
801 .required_signatures(2)
802 .readonly_signed_accounts(0) .lifetime_specifier(Hash::new_unique())
804 .accounts(vec![
805 Address::new_unique(),
806 Address::new_unique(),
807 Address::new_unique(),
808 ])
809 .build()
810 .unwrap();
811
812 assert!(message.is_signer_writable(0));
813 assert!(message.is_signer_writable(1));
814 assert!(!message.is_signer_writable(2)); }
816
817 #[test]
818 fn is_key_called_as_program_detects_program_indices() {
819 let message = create_test_message();
820 assert!(message.is_key_called_as_program(1));
822 assert!(!message.is_key_called_as_program(0));
823 assert!(!message.is_key_called_as_program(2));
824 assert!(!message.is_key_called_as_program(256));
826 assert!(!message.is_key_called_as_program(10_000));
827 }
828
829 #[test]
830 fn is_upgradeable_loader_present_detects_loader() {
831 let message = create_test_message();
832 assert!(!message.is_upgradeable_loader_present());
833
834 let mut message_with_loader = create_test_message();
835 message_with_loader
836 .account_keys
837 .push(bpf_loader_upgradeable::id());
838 assert!(message_with_loader.is_upgradeable_loader_present());
839 }
840
841 #[test]
842 fn is_writable_index_respects_header_layout() {
843 let message = create_test_message();
844 assert!(message.is_writable_index(0)); assert!(message.is_writable_index(1)); assert!(!message.is_writable_index(2)); }
849
850 #[test]
851 fn is_writable_index_handles_mixed_signer_permissions() {
852 let mut message = create_test_message();
853 message.header.num_required_signatures = 2;
855 message.header.num_readonly_signed_accounts = 1;
856 message.header.num_readonly_unsigned_accounts = 1;
857 message.account_keys = vec![
858 Address::new_unique(), Address::new_unique(), Address::new_unique(), ];
862 message.instructions[0].program_id_index = 2;
863 message.instructions[0].accounts = vec![0, 1];
864
865 assert!(message.sanitize().is_ok());
866 assert!(message.is_writable_index(0)); assert!(!message.is_writable_index(1)); assert!(!message.is_writable_index(2)); assert!(!message.is_writable_index(999)); }
871
872 #[test]
873 fn sanitize_accepts_valid_message() {
874 let message = create_test_message();
875 assert!(message.sanitize().is_ok());
876 }
877
878 #[test]
879 fn sanitize_rejects_zero_signers() {
880 let mut message = create_test_message();
881 message.header.num_required_signatures = 0;
882 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
883 }
884
885 #[test]
886 fn sanitize_accepts_over_12_signatures() {
887 let mut message = create_test_message();
888 message.header.num_required_signatures = 13;
889 message.account_keys = (0..14).map(|_| Address::new_unique()).collect();
890 assert!(message.sanitize().is_ok());
891 }
892
893 #[test]
894 fn sanitize_accepts_96_addresses() {
895 let mut message = create_test_message();
896 message.account_keys = (0..96).map(|_| Address::new_unique()).collect();
897 assert!(message.sanitize().is_ok());
898 let serialized = wincode::serialize(&message).unwrap();
899 assert_eq!(
900 wincode::deserialize::<Message>(&serialized).unwrap(),
901 message
902 );
903 }
904
905 #[test]
906 fn sanitize_rejects_addresses_exceeding_wire_count() {
907 let mut message = create_test_message();
908 message.account_keys = (0..=u8::MAX).map(|_| Address::new_unique()).collect();
909 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
910 }
911
912 #[test]
913 fn sanitize_accepts_over_64_instructions() {
914 let mut message = create_test_message();
915 message.instructions = (0..65)
916 .map(|_| CompiledInstruction {
917 program_id_index: 1,
918 accounts: vec![0],
919 data: vec![],
920 })
921 .collect();
922 assert!(message.sanitize().is_ok());
923 }
924
925 #[test]
926 fn sanitize_rejects_instructions_exceeding_wire_count() {
927 let mut message = create_test_message();
928 message.instructions = vec![message.instructions[0].clone(); usize::from(u8::MAX) + 1];
929 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
930 }
931
932 #[test]
933 fn sanitize_rejects_insufficient_accounts_for_header() {
934 let mut message = create_test_message();
935 message.header.num_readonly_unsigned_accounts = 10;
938 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
939 }
940
941 #[test]
942 fn sanitize_rejects_all_signers_readonly() {
943 let mut message = create_test_message();
944 message.header.num_readonly_signed_accounts = 1; assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
946 }
947
948 #[test]
949 fn sanitize_accepts_duplicate_addresses() {
950 let mut message = create_test_message();
951 let dup = message.account_keys[0];
952 message.account_keys[1] = dup;
953 assert!(message.sanitize().is_ok());
954 }
955
956 #[test]
957 fn sanitize_accepts_unaligned_heap_size() {
958 let mut message = create_test_message();
959 message.config.heap_size = Some(1025); assert!(message.sanitize().is_ok());
961 }
962
963 #[test]
964 fn sanitize_accepts_heap_size_below_minimum() {
965 let mut message = create_test_message();
966 message.config.heap_size = Some(MIN_HEAP_SIZE - 1);
967 assert!(message.sanitize().is_ok());
968 }
969
970 #[test]
971 fn sanitize_accepts_heap_size_above_maximum() {
972 let mut message = create_test_message();
973 message.config.heap_size = Some(MAX_HEAP_SIZE + 1);
974 assert!(message.sanitize().is_ok());
975 }
976
977 #[test]
978 fn sanitize_accepts_minimum_heap_size() {
979 let mut message = create_test_message();
980 message.config.heap_size = Some(MIN_HEAP_SIZE);
981 assert!(message.sanitize().is_ok());
982 }
983
984 #[test]
985 fn sanitize_accepts_maximum_heap_size() {
986 let mut message = create_test_message();
987 message.config.heap_size = Some(MAX_HEAP_SIZE);
988 assert!(message.sanitize().is_ok());
989 }
990
991 #[test]
992 fn sanitize_accepts_aligned_heap_size() {
993 let mut message = create_test_message();
994 message.config.heap_size = Some(65536); assert!(message.sanitize().is_ok());
996 }
997
998 #[test]
999 fn sanitize_rejects_invalid_program_id_index() {
1000 let mut message = create_test_message();
1001 message.instructions[0].program_id_index = 99;
1002 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1003 }
1004
1005 #[test]
1006 fn sanitize_rejects_fee_payer_as_program() {
1007 let mut message = create_test_message();
1008 message.instructions[0].program_id_index = 0;
1009 assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1010 }
1011
1012 #[test]
1013 fn sanitize_rejects_instruction_with_too_many_accounts() {
1014 let mut message = create_test_message();
1015 message.instructions[0].accounts = vec![0u8; (u8::MAX as usize) + 1];
1016 assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1017 }
1018
1019 #[test]
1020 fn sanitize_rejects_invalid_instruction_account_index() {
1021 let mut message = create_test_message();
1022 message.instructions[0].accounts = vec![0, 99]; assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1024 }
1025
1026 #[test]
1027 fn sanitize_accepts_64_addresses() {
1028 let mut message = create_test_message();
1029 message.account_keys = (0..64).map(|_| Address::new_unique()).collect();
1030 message.header.num_required_signatures = 1;
1031 message.header.num_readonly_signed_accounts = 0;
1032 message.header.num_readonly_unsigned_accounts = 1;
1033 message.instructions[0].program_id_index = 1;
1034 message.instructions[0].accounts = vec![0, 2];
1035 assert!(message.sanitize().is_ok());
1036 }
1037
1038 #[test]
1039 fn sanitize_accepts_64_instructions() {
1040 let mut message = create_test_message();
1041 message.instructions = (0..64)
1042 .map(|_| CompiledInstruction {
1043 program_id_index: 1,
1044 accounts: vec![0, 2],
1045 data: vec![1, 2, 3],
1046 })
1047 .collect();
1048 assert!(message.sanitize().is_ok());
1049 }
1050
1051 #[test]
1052 fn size_matches_serialized_length() {
1053 let test_cases = [
1054 MessageBuilder::new()
1056 .required_signatures(1)
1057 .lifetime_specifier(Hash::new_unique())
1058 .accounts(vec![Address::new_unique()])
1059 .build()
1060 .unwrap(),
1061 MessageBuilder::new()
1063 .required_signatures(1)
1064 .lifetime_specifier(Hash::new_unique())
1065 .accounts(vec![Address::new_unique(), Address::new_unique()])
1066 .priority_fee(1000)
1067 .compute_unit_limit(200_000)
1068 .instruction(CompiledInstruction {
1069 program_id_index: 1,
1070 accounts: vec![0],
1071 data: vec![1, 2, 3, 4],
1072 })
1073 .build()
1074 .unwrap(),
1075 MessageBuilder::new()
1077 .required_signatures(2)
1078 .readonly_signed_accounts(1)
1079 .readonly_unsigned_accounts(1)
1080 .lifetime_specifier(Hash::new_unique())
1081 .accounts(vec![
1082 Address::new_unique(),
1083 Address::new_unique(),
1084 Address::new_unique(),
1085 Address::new_unique(),
1086 ])
1087 .heap_size(65536)
1088 .instructions(vec![
1089 CompiledInstruction {
1090 program_id_index: 2,
1091 accounts: vec![0, 1],
1092 data: vec![],
1093 },
1094 CompiledInstruction {
1095 program_id_index: 3,
1096 accounts: vec![0, 1, 2],
1097 data: vec![0xAA; 100],
1098 },
1099 ])
1100 .build()
1101 .unwrap(),
1102 ];
1103
1104 for message in &test_cases {
1105 assert_eq!(message.size(), wincode::serialize(message).unwrap().len());
1106 }
1107 }
1108
1109 #[test]
1110 fn byte_layout_without_config() {
1111 let fee_payer = Address::new_from_array([1u8; 32]);
1112 let program = Address::new_from_array([2u8; 32]);
1113 let blockhash = Hash::new_from_array([0xAB; 32]);
1114
1115 let message = MessageBuilder::new()
1116 .required_signatures(1)
1117 .lifetime_specifier(blockhash)
1118 .accounts(vec![fee_payer, program])
1119 .instruction(CompiledInstruction {
1120 program_id_index: 1,
1121 accounts: vec![0],
1122 data: vec![0xDE, 0xAD],
1123 })
1124 .build()
1125 .unwrap();
1126
1127 let bytes = wincode::serialize(&message).unwrap();
1128
1129 let mut expected = vec![1, 0, 0];
1135 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);
1148 }
1149
1150 #[test]
1151 fn byte_layout_with_config() {
1152 let fee_payer = Address::new_from_array([1u8; 32]);
1153 let program = Address::new_from_array([2u8; 32]);
1154 let blockhash = Hash::new_from_array([0xBB; 32]);
1155
1156 let message = MessageBuilder::new()
1157 .required_signatures(1)
1158 .lifetime_specifier(blockhash)
1159 .accounts(vec![fee_payer, program])
1160 .priority_fee(0x0102030405060708u64)
1161 .compute_unit_limit(0x11223344u32)
1162 .instruction(CompiledInstruction {
1163 program_id_index: 1,
1164 accounts: vec![],
1165 data: vec![],
1166 })
1167 .build()
1168 .unwrap();
1169
1170 let bytes = wincode::serialize(&message).unwrap();
1171
1172 let mut expected = vec![1, 0, 0];
1173 expected.extend_from_slice(&7u32.to_le_bytes());
1175 expected.extend_from_slice(&[0xBB; 32]);
1176 expected.push(1);
1177 expected.push(2);
1178 expected.extend_from_slice(&[1u8; 32]);
1179 expected.extend_from_slice(&[2u8; 32]);
1180 expected.extend_from_slice(&0x0102030405060708u64.to_le_bytes());
1182 expected.extend_from_slice(&0x11223344u32.to_le_bytes());
1184 expected.push(1); expected.push(0); expected.extend_from_slice(&0u16.to_le_bytes()); assert_eq!(bytes, expected);
1189 }
1190
1191 #[test]
1192 fn roundtrip_preserves_all_config_fields() {
1193 let message = MessageBuilder::new()
1194 .required_signatures(1)
1195 .lifetime_specifier(Hash::new_unique())
1196 .accounts(vec![Address::new_unique(), Address::new_unique()])
1197 .priority_fee(1000)
1198 .compute_unit_limit(200_000)
1199 .loaded_accounts_data_size_limit(1_000_000)
1200 .heap_size(65536)
1201 .instruction(CompiledInstruction {
1202 program_id_index: 1,
1203 accounts: vec![0],
1204 data: vec![],
1205 })
1206 .build()
1207 .unwrap();
1208
1209 let serialized = wincode::serialize(&message).unwrap();
1210 let deserialized = deserialize(&serialized).unwrap();
1211 assert_eq!(message.config, deserialized.config);
1212 }
1213
1214 #[test]
1215 fn deserialize_rejects_unknown_config_mask_bits() {
1216 let message = MessageBuilder::default()
1217 .required_signatures(1)
1218 .lifetime_specifier(Hash::new_unique())
1219 .accounts(vec![Address::new_unique(), Address::new_unique()])
1220 .instruction(CompiledInstruction {
1221 program_id_index: 1,
1222 accounts: vec![0],
1223 data: vec![],
1224 })
1225 .build()
1226 .unwrap();
1227
1228 let mut serialized = wincode::serialize(&message).unwrap();
1229 assert!(deserialize(&serialized).is_ok());
1231
1232 let unknown_bit = 1u32 << TransactionConfigMask::KNOWN_BITS.trailing_ones();
1236 let mask = u32::from_le_bytes(serialized[3..7].try_into().unwrap()) | unknown_bit;
1237 serialized[3..7].copy_from_slice(&mask.to_le_bytes());
1238 assert!(deserialize(&serialized).is_err());
1239 }
1240
1241 #[test]
1242 fn deserialize_rejects_partial_priority_fee_bits() {
1243 let message = MessageBuilder::default()
1244 .required_signatures(1)
1245 .lifetime_specifier(Hash::new_unique())
1246 .accounts(vec![Address::new_unique(), Address::new_unique()])
1247 .instruction(CompiledInstruction {
1248 program_id_index: 1,
1249 accounts: vec![0],
1250 data: vec![],
1251 })
1252 .build()
1253 .unwrap();
1254
1255 let mut serialized = wincode::serialize(&message).unwrap();
1256 serialized[3] |= 0b1;
1258 assert!(deserialize(&serialized).is_err());
1259 }
1260}