1#[cfg(any(feature = "wincode", feature = "serde"))]
2use alloc::vec::Vec;
3#[cfg(feature = "frozen-abi")]
4use solana_frozen_abi_macro::{frozen_abi, AbiEnumVisitor, AbiExample, StableAbi, StableAbiSample};
5#[cfg(feature = "std")]
6use std::collections::HashSet;
7use {
8 crate::{
9 compiled_instruction::CompiledInstruction, legacy::Message as LegacyMessage,
10 v0::MessageAddressTableLookup, AddressSet, MessageHeader,
11 },
12 solana_address::Address,
13 solana_hash::Hash,
14 solana_sanitize::{Sanitize, SanitizeError},
15};
16#[cfg(feature = "serde")]
17use {
18 core::fmt,
19 serde::{
20 de::{self, Deserializer, SeqAccess, Unexpected, Visitor},
21 ser::{SerializeTuple, Serializer},
22 },
23 serde_derive::{Deserialize, Serialize},
24};
25#[cfg(feature = "wincode")]
26use {
27 core::mem::MaybeUninit,
28 wincode::{
29 config::Config,
30 io::{Reader, Writer},
31 ReadResult, SchemaRead, SchemaReadContext, SchemaWrite, WriteResult,
32 },
33};
34
35mod sanitized;
36pub mod v0;
37pub mod v1;
38
39pub use sanitized::*;
40
41pub const MESSAGE_VERSION_PREFIX: u8 = 0x80;
43
44#[cfg_attr(
53 feature = "frozen-abi",
54 derive(AbiEnumVisitor, AbiExample, StableAbi, StableAbiSample),
55 frozen_abi(
56 digest = "9xQQLkQntX2QKgwxbbpeuNrs5V2WopsBa11su46WWCro",
57 abi_digest = "4F9XrnBYkNKecPExdyPDpQSSpegDoSHpaAmgvpWUmT3g",
58 abi_serializer = "wincode",
59 test_roundtrip = "eq_and_wire"
60 )
61)]
62#[derive(Debug, PartialEq, Eq, Clone)]
63pub enum VersionedMessage {
64 Legacy(LegacyMessage),
65 V0(v0::Message),
66 V1(v1::Message),
67}
68
69impl VersionedMessage {
70 pub fn sanitize(&self) -> Result<(), SanitizeError> {
71 match self {
72 Self::Legacy(message) => message.sanitize(),
73 Self::V0(message) => message.sanitize(),
74 Self::V1(message) => message.sanitize(),
75 }
76 }
77
78 pub fn header(&self) -> &MessageHeader {
79 match self {
80 Self::Legacy(message) => &message.header,
81 Self::V0(message) => &message.header,
82 Self::V1(message) => &message.header,
83 }
84 }
85
86 pub fn static_account_keys(&self) -> &[Address] {
87 match self {
88 Self::Legacy(message) => &message.account_keys,
89 Self::V0(message) => &message.account_keys,
90 Self::V1(message) => &message.account_keys,
91 }
92 }
93
94 pub fn address_table_lookups(&self) -> Option<&[MessageAddressTableLookup]> {
95 match self {
96 Self::Legacy(_) => None,
97 Self::V0(message) => Some(&message.address_table_lookups),
98 Self::V1(_) => None,
99 }
100 }
101
102 pub fn is_signer(&self, index: usize) -> bool {
105 index < usize::from(self.header().num_required_signatures)
106 }
107
108 #[cfg(feature = "std")]
116 #[deprecated(
117 since = "4.4.0",
118 note = "Use `is_maybe_writable_with_reserved_addresses` instead"
119 )]
120 pub fn is_maybe_writable(
121 &self,
122 index: usize,
123 reserved_account_keys: Option<&HashSet<Address>>,
124 ) -> bool {
125 self.is_maybe_writable_with_reserved_addresses(index, reserved_account_keys)
126 }
127
128 pub fn is_maybe_writable_with_reserved_addresses(
136 &self,
137 index: usize,
138 reserved_addresses: Option<&impl AddressSet>,
139 ) -> bool {
140 match self {
141 Self::Legacy(message) => {
142 message.is_maybe_writable_with_reserved_addresses(index, reserved_addresses)
143 }
144 Self::V0(message) => {
145 message.is_maybe_writable_with_reserved_addresses(index, reserved_addresses)
146 }
147 Self::V1(message) => {
148 message.is_maybe_writable_with_reserved_addresses(index, reserved_addresses)
149 }
150 }
151 }
152
153 fn is_instruction_account(&self, key_index: usize) -> bool {
156 if let Ok(key_index) = u8::try_from(key_index) {
157 self.instructions()
158 .iter()
159 .any(|ix| ix.accounts.contains(&key_index))
160 } else {
161 false
162 }
163 }
164
165 pub fn is_invoked(&self, key_index: usize) -> bool {
166 match self {
167 Self::Legacy(message) => message.is_key_called_as_program(key_index),
168 Self::V0(message) => message.is_key_called_as_program(key_index),
169 Self::V1(message) => message.is_key_called_as_program(key_index),
170 }
171 }
172
173 pub fn is_non_loader_key(&self, key_index: usize) -> bool {
176 !self.is_invoked(key_index) || self.is_instruction_account(key_index)
177 }
178
179 pub fn recent_blockhash(&self) -> &Hash {
180 match self {
181 Self::Legacy(message) => &message.recent_blockhash,
182 Self::V0(message) => &message.recent_blockhash,
183 Self::V1(message) => &message.lifetime_specifier,
184 }
185 }
186
187 pub fn set_recent_blockhash(&mut self, recent_blockhash: Hash) {
188 match self {
189 Self::Legacy(message) => message.recent_blockhash = recent_blockhash,
190 Self::V0(message) => message.recent_blockhash = recent_blockhash,
191 Self::V1(message) => message.lifetime_specifier = recent_blockhash,
192 }
193 }
194
195 #[inline(always)]
198 pub fn instructions(&self) -> &[CompiledInstruction] {
199 match self {
200 Self::Legacy(message) => &message.instructions,
201 Self::V0(message) => &message.instructions,
202 Self::V1(message) => &message.instructions,
203 }
204 }
205
206 #[cfg(feature = "wincode")]
207 pub fn serialize(&self) -> Vec<u8> {
208 wincode::serialize(self).unwrap()
209 }
210
211 #[cfg(all(feature = "wincode", feature = "blake3"))]
212 pub fn hash(&self) -> Hash {
214 let message_bytes = self.serialize();
215 Self::hash_raw_message(&message_bytes)
216 }
217
218 #[cfg(feature = "blake3")]
219 pub fn hash_raw_message(message_bytes: &[u8]) -> Hash {
221 use blake3::traits::digest::Digest;
222 let mut hasher = blake3::Hasher::new();
223 hasher.update(b"solana-tx-message-v1");
224 hasher.update(message_bytes);
225 let hash_bytes: [u8; solana_hash::HASH_BYTES] = hasher.finalize().into();
226 hash_bytes.into()
227 }
228}
229
230impl Default for VersionedMessage {
231 fn default() -> Self {
232 Self::Legacy(LegacyMessage::default())
233 }
234}
235
236#[cfg(feature = "serde")]
237impl serde::Serialize for VersionedMessage {
238 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
239 where
240 S: Serializer,
241 {
242 match self {
243 Self::Legacy(message) => {
244 let mut seq = serializer.serialize_tuple(1)?;
245 seq.serialize_element(message)?;
246 seq.end()
247 }
248 Self::V0(message) => {
249 let mut seq = serializer.serialize_tuple(2)?;
250 seq.serialize_element(&MESSAGE_VERSION_PREFIX)?;
251 seq.serialize_element(message)?;
252 seq.end()
253 }
254 Self::V1(message) => {
255 let mut seq = serializer.serialize_tuple(2)?;
258 seq.serialize_element(&crate::v1::V1_PREFIX)?;
259 seq.serialize_element(message)?;
260 seq.end()
261 }
262 }
263 }
264}
265
266#[cfg(feature = "serde")]
267enum MessagePrefix {
268 Legacy(u8),
269 Versioned(u8),
270}
271
272#[cfg(feature = "serde")]
273impl<'de> serde::Deserialize<'de> for MessagePrefix {
274 fn deserialize<D>(deserializer: D) -> Result<MessagePrefix, D::Error>
275 where
276 D: Deserializer<'de>,
277 {
278 struct PrefixVisitor;
279
280 impl Visitor<'_> for PrefixVisitor {
281 type Value = MessagePrefix;
282
283 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
284 formatter.write_str("message prefix byte")
285 }
286
287 fn visit_u64<E: de::Error>(self, value: u64) -> Result<MessagePrefix, E> {
292 if value > u8::MAX as u64 {
293 Err(de::Error::invalid_type(Unexpected::Unsigned(value), &self))?;
294 }
295
296 let byte = value as u8;
297 if byte & MESSAGE_VERSION_PREFIX != 0 {
298 Ok(MessagePrefix::Versioned(byte & !MESSAGE_VERSION_PREFIX))
299 } else {
300 Ok(MessagePrefix::Legacy(byte))
301 }
302 }
303 }
304
305 deserializer.deserialize_u8(PrefixVisitor)
306 }
307}
308
309#[cfg(feature = "serde")]
310impl<'de> serde::Deserialize<'de> for VersionedMessage {
311 fn deserialize<D>(deserializer: D) -> Result<VersionedMessage, D::Error>
312 where
313 D: Deserializer<'de>,
314 {
315 struct MessageVisitor;
316
317 impl<'de> Visitor<'de> for MessageVisitor {
318 type Value = VersionedMessage;
319
320 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
321 formatter.write_str("message bytes")
322 }
323
324 fn visit_seq<A>(self, mut seq: A) -> Result<VersionedMessage, A::Error>
325 where
326 A: SeqAccess<'de>,
327 {
328 let prefix: MessagePrefix = seq
329 .next_element()?
330 .ok_or_else(|| de::Error::invalid_length(0, &self))?;
331
332 match prefix {
333 MessagePrefix::Legacy(num_required_signatures) => {
334 #[derive(Serialize, Deserialize)]
336 struct RemainingLegacyMessage {
337 pub num_readonly_signed_accounts: u8,
338 pub num_readonly_unsigned_accounts: u8,
339 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
340 pub account_keys: Vec<Address>,
341 pub recent_blockhash: Hash,
342 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
343 pub instructions: Vec<CompiledInstruction>,
344 }
345
346 let message: RemainingLegacyMessage =
347 seq.next_element()?.ok_or_else(|| {
348 de::Error::invalid_length(1, &self)
350 })?;
351
352 Ok(VersionedMessage::Legacy(LegacyMessage {
353 header: MessageHeader {
354 num_required_signatures,
355 num_readonly_signed_accounts: message.num_readonly_signed_accounts,
356 num_readonly_unsigned_accounts: message
357 .num_readonly_unsigned_accounts,
358 },
359 account_keys: message.account_keys,
360 recent_blockhash: message.recent_blockhash,
361 instructions: message.instructions,
362 }))
363 }
364 MessagePrefix::Versioned(version) => {
365 match version {
366 0 => {
367 Ok(VersionedMessage::V0(seq.next_element()?.ok_or_else(
368 || {
369 de::Error::invalid_length(1, &self)
371 },
372 )?))
373 }
374 1 => {
375 Ok(VersionedMessage::V1(seq.next_element()?.ok_or_else(
376 || {
377 de::Error::invalid_length(1, &self)
379 },
380 )?))
381 }
382 127 => {
383 Err(de::Error::custom("off-chain messages are not accepted"))
388 }
389 _ => Err(de::Error::invalid_value(
390 de::Unexpected::Unsigned(version as u64),
391 &"a valid transaction message version",
392 )),
393 }
394 }
395 }
396 }
397 }
398
399 deserializer.deserialize_tuple(2, MessageVisitor)
400 }
401}
402
403#[cfg(feature = "wincode")]
404unsafe impl<C: Config> SchemaWrite<C> for VersionedMessage {
405 type Src = Self;
406
407 #[allow(clippy::arithmetic_side_effects)]
409 #[inline(always)]
410 fn size_of(src: &Self::Src) -> WriteResult<usize> {
411 match src {
412 VersionedMessage::Legacy(message) => {
413 <LegacyMessage as SchemaWrite<C>>::size_of(message)
414 }
415 VersionedMessage::V0(message) => {
416 Ok(1 + <v0::Message as SchemaWrite<C>>::size_of(message)?)
417 }
418 VersionedMessage::V1(message) => Ok(1 + message.size()),
419 }
420 }
421
422 #[allow(clippy::arithmetic_side_effects)]
424 #[inline(always)]
425 fn write(mut writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
426 match src {
427 VersionedMessage::Legacy(message) => {
428 <LegacyMessage as SchemaWrite<C>>::write(writer, message)
429 }
430 VersionedMessage::V0(message) => {
431 <u8 as SchemaWrite<C>>::write(&mut writer, &MESSAGE_VERSION_PREFIX)?;
432 <v0::Message as SchemaWrite<C>>::write(writer, message)
433 }
434 VersionedMessage::V1(message) => {
435 <u8 as SchemaWrite<C>>::write(writer.by_ref(), &crate::v1::V1_PREFIX)?;
436 <v1::Message as SchemaWrite<C>>::write(writer, message)
437 }
438 }
439 }
440}
441
442#[cfg(feature = "wincode")]
443unsafe impl<'de, C: Config> SchemaReadContext<'de, C, u8> for VersionedMessage {
444 type Dst = Self;
445
446 fn read_with_context(
447 discriminant: u8,
448 reader: impl Reader<'de>,
449 dst: &mut MaybeUninit<Self::Dst>,
450 ) -> ReadResult<()> {
451 if discriminant & MESSAGE_VERSION_PREFIX != 0 {
456 use wincode::error::invalid_tag_encoding;
457
458 let version = discriminant & !MESSAGE_VERSION_PREFIX;
459 return match version {
460 0 => {
461 let msg = <v0::Message as SchemaRead<C>>::get(reader)?;
462 dst.write(VersionedMessage::V0(msg));
463 Ok(())
464 }
465 1 => {
466 let message = <v1::Message as SchemaRead<C>>::get(reader)?;
467 dst.write(VersionedMessage::V1(message));
468
469 Ok(())
470 }
471 _ => Err(invalid_tag_encoding(version as usize)),
472 };
473 };
474 let legacy =
475 <LegacyMessage as SchemaReadContext<C, _>>::get_with_context(discriminant, reader)?;
476 dst.write(VersionedMessage::Legacy(legacy));
477
478 Ok(())
479 }
480}
481#[cfg(feature = "wincode")]
482unsafe impl<'de, C: Config> SchemaRead<'de, C> for VersionedMessage {
483 type Dst = Self;
484
485 #[inline]
486 fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
487 let discriminant = reader.take_byte()?;
488 <VersionedMessage as SchemaReadContext<C, _>>::read_with_context(discriminant, reader, dst)
489 }
490}
491
492#[cfg(test)]
493mod tests {
494 use {
495 super::*,
496 crate::{
497 v0::MessageAddressTableLookup,
498 v1::{MAX_HEAP_SIZE, MIN_HEAP_SIZE, V1_PREFIX},
499 },
500 alloc::vec,
501 proptest::{
502 collection::vec,
503 option::of,
504 prelude::{any, Just},
505 prop_compose, proptest,
506 },
507 solana_instruction::{AccountMeta, Instruction},
508 };
509
510 #[derive(Clone, Debug)]
511 struct TestMessageData {
512 required_signatures: u8,
513 lifetime: [u8; 32],
514 accounts: Vec<[u8; 32]>,
515 priority_fee: Option<u64>,
516 compute_unit_limit: Option<u32>,
517 loaded_accounts_data_size_limit: Option<u32>,
518 heap_size: Option<u32>,
519 program_id_index: u8,
520 instr_accounts: Vec<u8>,
521 data: Vec<u8>,
522 }
523
524 #[test]
525 fn test_legacy_message_serialization() {
526 let program_id0 = Address::new_unique();
527 let program_id1 = Address::new_unique();
528 let id0 = Address::new_unique();
529 let id1 = Address::new_unique();
530 let id2 = Address::new_unique();
531 let id3 = Address::new_unique();
532 let instructions = vec![
533 Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id0, false)]),
534 Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id1, true)]),
535 Instruction::new_with_bincode(
536 program_id1,
537 &0,
538 vec![AccountMeta::new_readonly(id2, false)],
539 ),
540 Instruction::new_with_bincode(
541 program_id1,
542 &0,
543 vec![AccountMeta::new_readonly(id3, true)],
544 ),
545 ];
546
547 let mut message = LegacyMessage::new(&instructions, Some(&id1));
548 message.recent_blockhash = Hash::new_unique();
549 let wrapped_message = VersionedMessage::Legacy(message.clone());
550
551 {
553 let bytes = bincode::serialize(&message).unwrap();
554 assert_eq!(bytes, bincode::serialize(&wrapped_message).unwrap());
555
556 let message_from_bytes: LegacyMessage = bincode::deserialize(&bytes).unwrap();
557 let wrapped_message_from_bytes: VersionedMessage =
558 bincode::deserialize(&bytes).unwrap();
559
560 assert_eq!(message, message_from_bytes);
561 assert_eq!(wrapped_message, wrapped_message_from_bytes);
562 }
563
564 {
566 let string = serde_json::to_string(&message).unwrap();
567 let message_from_string: LegacyMessage = serde_json::from_str(&string).unwrap();
568 assert_eq!(message, message_from_string);
569 }
570 }
571
572 #[test]
573 fn test_versioned_message_serialization() {
574 let message = VersionedMessage::V0(v0::Message {
575 header: MessageHeader {
576 num_required_signatures: 1,
577 num_readonly_signed_accounts: 0,
578 num_readonly_unsigned_accounts: 0,
579 },
580 recent_blockhash: Hash::new_unique(),
581 account_keys: vec![Address::new_unique()],
582 address_table_lookups: vec![
583 MessageAddressTableLookup {
584 account_key: Address::new_unique(),
585 writable_indexes: vec![1],
586 readonly_indexes: vec![0],
587 },
588 MessageAddressTableLookup {
589 account_key: Address::new_unique(),
590 writable_indexes: vec![0],
591 readonly_indexes: vec![1],
592 },
593 ],
594 instructions: vec![CompiledInstruction {
595 program_id_index: 1,
596 accounts: vec![0, 2, 3, 4],
597 data: vec![],
598 }],
599 });
600
601 let bytes = bincode::serialize(&message).unwrap();
602 let message_from_bytes: VersionedMessage = bincode::deserialize(&bytes).unwrap();
603 assert_eq!(message, message_from_bytes);
604
605 let string = serde_json::to_string(&message).unwrap();
606 let message_from_string: VersionedMessage = serde_json::from_str(&string).unwrap();
607 assert_eq!(message, message_from_string);
608 }
609
610 prop_compose! {
611 fn generate_message_data()
612 (
613 accounts in vec(any::<[u8; 32]>(), 12..=64),
616 lifetime in any::<[u8; 32]>(),
617 priority_fee in of(any::<u64>()),
618 compute_unit_limit in of(0..=1_400_000u32),
619 loaded_accounts_data_size_limit in of(0..=20_480u32),
620 heap_size in of(MIN_HEAP_SIZE.saturating_div(1024)..=MAX_HEAP_SIZE.saturating_div(1024)),
623 required_signatures in 1..=12u8,
624 )
625 (
626 program_id_index in 1u8..accounts.len() as u8,
628 instr_accounts in vec(
630 0u8..accounts.len() as u8,
631 (required_signatures as usize)..=accounts.len(),
632 ),
633 data in vec(any::<u8>(), 0..=2048),
636 accounts in Just(accounts),
637 lifetime in Just(lifetime),
638 priority_fee in Just(priority_fee),
639 compute_unit_limit in Just(compute_unit_limit),
640 loaded_accounts_data_size_limit in Just(loaded_accounts_data_size_limit),
641 heap_size in Just(heap_size.map(|size| size.saturating_mul(1024))),
642 required_signatures in Just(required_signatures),
643 ) -> TestMessageData
644 {
645 TestMessageData {
646 required_signatures,
647 lifetime,
648 accounts,
649 priority_fee,
650 compute_unit_limit,
651 loaded_accounts_data_size_limit,
652 heap_size,
653 program_id_index,
654 instr_accounts,
655 data,
656 }
657 }
658 }
659
660 proptest! {
661 #[test]
662 fn test_v1_message_raw_bytes_roundtrip(test_data in generate_message_data()) {
663 let accounts: Vec<Address> = test_data.accounts.into_iter()
664 .map(Address::new_from_array).collect();
665 let lifetime = Hash::new_from_array(test_data.lifetime);
666
667 let mut builder = v1::MessageBuilder::new()
668 .required_signatures(test_data.required_signatures)
669 .lifetime_specifier(lifetime)
670 .accounts(accounts)
671 .instruction(CompiledInstruction {
672 program_id_index: test_data.program_id_index,
673 accounts: test_data.instr_accounts,
674 data: test_data.data,
675 });
676
677 if let Some(priority_fee) = test_data.priority_fee {
679 builder = builder.priority_fee(priority_fee);
680 }
681 if let Some(compute_unit_limit) = test_data.compute_unit_limit {
682 builder = builder.compute_unit_limit(compute_unit_limit);
683 }
684 if let Some(loaded_accounts_data_size_limit) = test_data.loaded_accounts_data_size_limit {
685 builder = builder.loaded_accounts_data_size_limit(loaded_accounts_data_size_limit);
686 }
687 if let Some(heap_size) = test_data.heap_size {
688 builder = builder.heap_size(heap_size);
689 }
690
691 let message = builder.build().unwrap();
692
693 let bytes = wincode::serialize(&message).unwrap();
695 let parsed = v1::deserialize(&bytes).unwrap();
697
698 assert_eq!(message, parsed);
700 assert_eq!(message, wincode::deserialize(&bytes).unwrap());
701
702 let versioned = VersionedMessage::V1(message);
704 let serialized = versioned.serialize();
705
706 assert!(!serialized.is_empty());
711 assert_eq!(serialized[0], V1_PREFIX);
712 assert_eq!(&serialized[1..], bytes.as_slice());
713 }
714 }
715
716 #[test]
717 fn test_v1_versioned_message_json_roundtrip() {
718 let msg = v1::MessageBuilder::new()
719 .required_signatures(1)
720 .lifetime_specifier(Hash::new_unique())
721 .accounts(vec![Address::new_unique(), Address::new_unique()])
722 .priority_fee(1000)
723 .compute_unit_limit(200_000)
724 .instruction(CompiledInstruction {
725 program_id_index: 1,
726 accounts: vec![0],
727 data: vec![1, 2, 3, 4],
728 })
729 .build()
730 .unwrap();
731
732 let vm = VersionedMessage::V1(msg);
733 let s = serde_json::to_string(&vm).unwrap();
734 let back: VersionedMessage = serde_json::from_str(&s).unwrap();
735 assert_eq!(vm, back);
736 }
737
738 #[cfg(feature = "wincode")]
739 #[test]
740 fn test_v1_wincode_roundtrip() {
741 let test_messages = [
742 v1::MessageBuilder::new()
744 .required_signatures(1)
745 .lifetime_specifier(Hash::new_unique())
746 .accounts(vec![Address::new_unique(), Address::new_unique()])
747 .instruction(CompiledInstruction {
748 program_id_index: 1,
749 accounts: vec![0],
750 data: vec![],
751 })
752 .build()
753 .unwrap(),
754 v1::MessageBuilder::new()
756 .required_signatures(1)
757 .lifetime_specifier(Hash::new_unique())
758 .accounts(vec![Address::new_unique(), Address::new_unique()])
759 .priority_fee(1000)
760 .compute_unit_limit(200_000)
761 .instruction(CompiledInstruction {
762 program_id_index: 1,
763 accounts: vec![0],
764 data: vec![1, 2, 3, 4],
765 })
766 .build()
767 .unwrap(),
768 v1::MessageBuilder::new()
770 .required_signatures(2)
771 .lifetime_specifier(Hash::new_unique())
772 .accounts(vec![
773 Address::new_unique(),
774 Address::new_unique(),
775 Address::new_unique(),
776 ])
777 .heap_size(65536)
778 .instructions(vec![
779 CompiledInstruction {
780 program_id_index: 2,
781 accounts: vec![0, 1],
782 data: vec![0xAA, 0xBB],
783 },
784 CompiledInstruction {
785 program_id_index: 2,
786 accounts: vec![1],
787 data: vec![0xCC],
788 },
789 ])
790 .build()
791 .unwrap(),
792 ];
793
794 for message in test_messages {
795 let versioned = VersionedMessage::V1(message.clone());
796
797 let bytes = wincode::serialize(&versioned).expect("Wincode serialize failed");
799 let deserialized: VersionedMessage =
800 wincode::deserialize(&bytes).expect("Wincode deserialize failed");
801
802 match deserialized {
803 VersionedMessage::V1(parsed) => assert_eq!(parsed, message),
804 _ => panic!("Expected V1 message"),
805 }
806 }
807 }
808}