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