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, 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")]
113 pub fn is_maybe_writable(
114 &self,
115 index: usize,
116 reserved_account_keys: Option<&HashSet<Address>>,
117 ) -> bool {
118 match self {
119 Self::Legacy(message) => message.is_maybe_writable(index, reserved_account_keys),
120 Self::V0(message) => message.is_maybe_writable(index, reserved_account_keys),
121 Self::V1(message) => message.is_maybe_writable(index, reserved_account_keys),
122 }
123 }
124
125 fn is_instruction_account(&self, key_index: usize) -> bool {
128 if let Ok(key_index) = u8::try_from(key_index) {
129 self.instructions()
130 .iter()
131 .any(|ix| ix.accounts.contains(&key_index))
132 } else {
133 false
134 }
135 }
136
137 pub fn is_invoked(&self, key_index: usize) -> bool {
138 match self {
139 Self::Legacy(message) => message.is_key_called_as_program(key_index),
140 Self::V0(message) => message.is_key_called_as_program(key_index),
141 Self::V1(message) => message.is_key_called_as_program(key_index),
142 }
143 }
144
145 pub fn is_non_loader_key(&self, key_index: usize) -> bool {
148 !self.is_invoked(key_index) || self.is_instruction_account(key_index)
149 }
150
151 pub fn recent_blockhash(&self) -> &Hash {
152 match self {
153 Self::Legacy(message) => &message.recent_blockhash,
154 Self::V0(message) => &message.recent_blockhash,
155 Self::V1(message) => &message.lifetime_specifier,
156 }
157 }
158
159 pub fn set_recent_blockhash(&mut self, recent_blockhash: Hash) {
160 match self {
161 Self::Legacy(message) => message.recent_blockhash = recent_blockhash,
162 Self::V0(message) => message.recent_blockhash = recent_blockhash,
163 Self::V1(message) => message.lifetime_specifier = recent_blockhash,
164 }
165 }
166
167 #[inline(always)]
170 pub fn instructions(&self) -> &[CompiledInstruction] {
171 match self {
172 Self::Legacy(message) => &message.instructions,
173 Self::V0(message) => &message.instructions,
174 Self::V1(message) => &message.instructions,
175 }
176 }
177
178 #[cfg(feature = "wincode")]
179 pub fn serialize(&self) -> Vec<u8> {
180 wincode::serialize(self).unwrap()
181 }
182
183 #[cfg(all(feature = "wincode", feature = "blake3"))]
184 pub fn hash(&self) -> Hash {
186 let message_bytes = self.serialize();
187 Self::hash_raw_message(&message_bytes)
188 }
189
190 #[cfg(feature = "blake3")]
191 pub fn hash_raw_message(message_bytes: &[u8]) -> Hash {
193 use blake3::traits::digest::Digest;
194 let mut hasher = blake3::Hasher::new();
195 hasher.update(b"solana-tx-message-v1");
196 hasher.update(message_bytes);
197 let hash_bytes: [u8; solana_hash::HASH_BYTES] = hasher.finalize().into();
198 hash_bytes.into()
199 }
200}
201
202impl Default for VersionedMessage {
203 fn default() -> Self {
204 Self::Legacy(LegacyMessage::default())
205 }
206}
207
208#[cfg(feature = "serde")]
209impl serde::Serialize for VersionedMessage {
210 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
211 where
212 S: Serializer,
213 {
214 match self {
215 Self::Legacy(message) => {
216 let mut seq = serializer.serialize_tuple(1)?;
217 seq.serialize_element(message)?;
218 seq.end()
219 }
220 Self::V0(message) => {
221 let mut seq = serializer.serialize_tuple(2)?;
222 seq.serialize_element(&MESSAGE_VERSION_PREFIX)?;
223 seq.serialize_element(message)?;
224 seq.end()
225 }
226 Self::V1(message) => {
227 let mut seq = serializer.serialize_tuple(2)?;
230 seq.serialize_element(&crate::v1::V1_PREFIX)?;
231 seq.serialize_element(message)?;
232 seq.end()
233 }
234 }
235 }
236}
237
238#[cfg(feature = "serde")]
239enum MessagePrefix {
240 Legacy(u8),
241 Versioned(u8),
242}
243
244#[cfg(feature = "serde")]
245impl<'de> serde::Deserialize<'de> for MessagePrefix {
246 fn deserialize<D>(deserializer: D) -> Result<MessagePrefix, D::Error>
247 where
248 D: Deserializer<'de>,
249 {
250 struct PrefixVisitor;
251
252 impl Visitor<'_> for PrefixVisitor {
253 type Value = MessagePrefix;
254
255 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
256 formatter.write_str("message prefix byte")
257 }
258
259 fn visit_u64<E: de::Error>(self, value: u64) -> Result<MessagePrefix, E> {
264 if value > u8::MAX as u64 {
265 Err(de::Error::invalid_type(Unexpected::Unsigned(value), &self))?;
266 }
267
268 let byte = value as u8;
269 if byte & MESSAGE_VERSION_PREFIX != 0 {
270 Ok(MessagePrefix::Versioned(byte & !MESSAGE_VERSION_PREFIX))
271 } else {
272 Ok(MessagePrefix::Legacy(byte))
273 }
274 }
275 }
276
277 deserializer.deserialize_u8(PrefixVisitor)
278 }
279}
280
281#[cfg(feature = "serde")]
282impl<'de> serde::Deserialize<'de> for VersionedMessage {
283 fn deserialize<D>(deserializer: D) -> Result<VersionedMessage, D::Error>
284 where
285 D: Deserializer<'de>,
286 {
287 struct MessageVisitor;
288
289 impl<'de> Visitor<'de> for MessageVisitor {
290 type Value = VersionedMessage;
291
292 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
293 formatter.write_str("message bytes")
294 }
295
296 fn visit_seq<A>(self, mut seq: A) -> Result<VersionedMessage, A::Error>
297 where
298 A: SeqAccess<'de>,
299 {
300 let prefix: MessagePrefix = seq
301 .next_element()?
302 .ok_or_else(|| de::Error::invalid_length(0, &self))?;
303
304 match prefix {
305 MessagePrefix::Legacy(num_required_signatures) => {
306 #[derive(Serialize, Deserialize)]
308 struct RemainingLegacyMessage {
309 pub num_readonly_signed_accounts: u8,
310 pub num_readonly_unsigned_accounts: u8,
311 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
312 pub account_keys: Vec<Address>,
313 pub recent_blockhash: Hash,
314 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
315 pub instructions: Vec<CompiledInstruction>,
316 }
317
318 let message: RemainingLegacyMessage =
319 seq.next_element()?.ok_or_else(|| {
320 de::Error::invalid_length(1, &self)
322 })?;
323
324 Ok(VersionedMessage::Legacy(LegacyMessage {
325 header: MessageHeader {
326 num_required_signatures,
327 num_readonly_signed_accounts: message.num_readonly_signed_accounts,
328 num_readonly_unsigned_accounts: message
329 .num_readonly_unsigned_accounts,
330 },
331 account_keys: message.account_keys,
332 recent_blockhash: message.recent_blockhash,
333 instructions: message.instructions,
334 }))
335 }
336 MessagePrefix::Versioned(version) => {
337 match version {
338 0 => {
339 Ok(VersionedMessage::V0(seq.next_element()?.ok_or_else(
340 || {
341 de::Error::invalid_length(1, &self)
343 },
344 )?))
345 }
346 1 => {
347 Ok(VersionedMessage::V1(seq.next_element()?.ok_or_else(
348 || {
349 de::Error::invalid_length(1, &self)
351 },
352 )?))
353 }
354 127 => {
355 Err(de::Error::custom("off-chain messages are not accepted"))
360 }
361 _ => Err(de::Error::invalid_value(
362 de::Unexpected::Unsigned(version as u64),
363 &"a valid transaction message version",
364 )),
365 }
366 }
367 }
368 }
369 }
370
371 deserializer.deserialize_tuple(2, MessageVisitor)
372 }
373}
374
375#[cfg(feature = "wincode")]
376unsafe impl<C: Config> SchemaWrite<C> for VersionedMessage {
377 type Src = Self;
378
379 #[allow(clippy::arithmetic_side_effects)]
381 #[inline(always)]
382 fn size_of(src: &Self::Src) -> WriteResult<usize> {
383 match src {
384 VersionedMessage::Legacy(message) => {
385 <LegacyMessage as SchemaWrite<C>>::size_of(message)
386 }
387 VersionedMessage::V0(message) => {
388 Ok(1 + <v0::Message as SchemaWrite<C>>::size_of(message)?)
389 }
390 VersionedMessage::V1(message) => Ok(1 + message.size()),
391 }
392 }
393
394 #[allow(clippy::arithmetic_side_effects)]
396 #[inline(always)]
397 fn write(mut writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
398 match src {
399 VersionedMessage::Legacy(message) => {
400 <LegacyMessage as SchemaWrite<C>>::write(writer, message)
401 }
402 VersionedMessage::V0(message) => {
403 <u8 as SchemaWrite<C>>::write(&mut writer, &MESSAGE_VERSION_PREFIX)?;
404 <v0::Message as SchemaWrite<C>>::write(writer, message)
405 }
406 VersionedMessage::V1(message) => {
407 <u8 as SchemaWrite<C>>::write(writer.by_ref(), &crate::v1::V1_PREFIX)?;
408 <v1::Message as SchemaWrite<C>>::write(writer, message)
409 }
410 }
411 }
412}
413
414#[cfg(feature = "wincode")]
415unsafe impl<'de, C: Config> SchemaReadContext<'de, C, u8> for VersionedMessage {
416 type Dst = Self;
417
418 fn read_with_context(
419 discriminant: u8,
420 reader: impl Reader<'de>,
421 dst: &mut MaybeUninit<Self::Dst>,
422 ) -> ReadResult<()> {
423 if discriminant & MESSAGE_VERSION_PREFIX != 0 {
428 use wincode::error::invalid_tag_encoding;
429
430 let version = discriminant & !MESSAGE_VERSION_PREFIX;
431 return match version {
432 0 => {
433 let msg = <v0::Message as SchemaRead<C>>::get(reader)?;
434 dst.write(VersionedMessage::V0(msg));
435 Ok(())
436 }
437 1 => {
438 let message = <v1::Message as SchemaRead<C>>::get(reader)?;
439 dst.write(VersionedMessage::V1(message));
440
441 Ok(())
442 }
443 _ => Err(invalid_tag_encoding(version as usize)),
444 };
445 };
446 let legacy =
447 <LegacyMessage as SchemaReadContext<C, _>>::get_with_context(discriminant, reader)?;
448 dst.write(VersionedMessage::Legacy(legacy));
449
450 Ok(())
451 }
452}
453#[cfg(feature = "wincode")]
454unsafe impl<'de, C: Config> SchemaRead<'de, C> for VersionedMessage {
455 type Dst = Self;
456
457 #[inline]
458 fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
459 let discriminant = reader.take_byte()?;
460 <VersionedMessage as SchemaReadContext<C, _>>::read_with_context(discriminant, reader, dst)
461 }
462}
463
464#[cfg(test)]
465mod tests {
466 use {
467 super::*,
468 crate::{
469 v0::MessageAddressTableLookup,
470 v1::{MAX_HEAP_SIZE, MIN_HEAP_SIZE, V1_PREFIX},
471 },
472 alloc::vec,
473 proptest::{
474 collection::vec,
475 option::of,
476 prelude::{any, Just},
477 prop_compose, proptest,
478 },
479 solana_instruction::{AccountMeta, Instruction},
480 };
481
482 #[derive(Clone, Debug)]
483 struct TestMessageData {
484 required_signatures: u8,
485 lifetime: [u8; 32],
486 accounts: Vec<[u8; 32]>,
487 priority_fee: Option<u64>,
488 compute_unit_limit: Option<u32>,
489 loaded_accounts_data_size_limit: Option<u32>,
490 heap_size: Option<u32>,
491 program_id_index: u8,
492 instr_accounts: Vec<u8>,
493 data: Vec<u8>,
494 }
495
496 #[test]
497 fn test_legacy_message_serialization() {
498 let program_id0 = Address::new_unique();
499 let program_id1 = Address::new_unique();
500 let id0 = Address::new_unique();
501 let id1 = Address::new_unique();
502 let id2 = Address::new_unique();
503 let id3 = Address::new_unique();
504 let instructions = vec![
505 Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id0, false)]),
506 Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id1, true)]),
507 Instruction::new_with_bincode(
508 program_id1,
509 &0,
510 vec![AccountMeta::new_readonly(id2, false)],
511 ),
512 Instruction::new_with_bincode(
513 program_id1,
514 &0,
515 vec![AccountMeta::new_readonly(id3, true)],
516 ),
517 ];
518
519 let mut message = LegacyMessage::new(&instructions, Some(&id1));
520 message.recent_blockhash = Hash::new_unique();
521 let wrapped_message = VersionedMessage::Legacy(message.clone());
522
523 {
525 let bytes = bincode::serialize(&message).unwrap();
526 assert_eq!(bytes, bincode::serialize(&wrapped_message).unwrap());
527
528 let message_from_bytes: LegacyMessage = bincode::deserialize(&bytes).unwrap();
529 let wrapped_message_from_bytes: VersionedMessage =
530 bincode::deserialize(&bytes).unwrap();
531
532 assert_eq!(message, message_from_bytes);
533 assert_eq!(wrapped_message, wrapped_message_from_bytes);
534 }
535
536 {
538 let string = serde_json::to_string(&message).unwrap();
539 let message_from_string: LegacyMessage = serde_json::from_str(&string).unwrap();
540 assert_eq!(message, message_from_string);
541 }
542 }
543
544 #[test]
545 fn test_versioned_message_serialization() {
546 let message = VersionedMessage::V0(v0::Message {
547 header: MessageHeader {
548 num_required_signatures: 1,
549 num_readonly_signed_accounts: 0,
550 num_readonly_unsigned_accounts: 0,
551 },
552 recent_blockhash: Hash::new_unique(),
553 account_keys: vec![Address::new_unique()],
554 address_table_lookups: vec![
555 MessageAddressTableLookup {
556 account_key: Address::new_unique(),
557 writable_indexes: vec![1],
558 readonly_indexes: vec![0],
559 },
560 MessageAddressTableLookup {
561 account_key: Address::new_unique(),
562 writable_indexes: vec![0],
563 readonly_indexes: vec![1],
564 },
565 ],
566 instructions: vec![CompiledInstruction {
567 program_id_index: 1,
568 accounts: vec![0, 2, 3, 4],
569 data: vec![],
570 }],
571 });
572
573 let bytes = bincode::serialize(&message).unwrap();
574 let message_from_bytes: VersionedMessage = bincode::deserialize(&bytes).unwrap();
575 assert_eq!(message, message_from_bytes);
576
577 let string = serde_json::to_string(&message).unwrap();
578 let message_from_string: VersionedMessage = serde_json::from_str(&string).unwrap();
579 assert_eq!(message, message_from_string);
580 }
581
582 prop_compose! {
583 fn generate_message_data()
584 (
585 accounts in vec(any::<[u8; 32]>(), 12..=64),
588 lifetime in any::<[u8; 32]>(),
589 priority_fee in of(any::<u64>()),
590 compute_unit_limit in of(0..=1_400_000u32),
591 loaded_accounts_data_size_limit in of(0..=20_480u32),
592 heap_size in of(MIN_HEAP_SIZE.saturating_div(1024)..=MAX_HEAP_SIZE.saturating_div(1024)),
595 required_signatures in 1..=12u8,
596 )
597 (
598 program_id_index in 1u8..accounts.len() as u8,
600 instr_accounts in vec(
602 0u8..accounts.len() as u8,
603 (required_signatures as usize)..=accounts.len(),
604 ),
605 data in vec(any::<u8>(), 0..=2048),
608 accounts in Just(accounts),
609 lifetime in Just(lifetime),
610 priority_fee in Just(priority_fee),
611 compute_unit_limit in Just(compute_unit_limit),
612 loaded_accounts_data_size_limit in Just(loaded_accounts_data_size_limit),
613 heap_size in Just(heap_size.map(|size| size.saturating_mul(1024))),
614 required_signatures in Just(required_signatures),
615 ) -> TestMessageData
616 {
617 TestMessageData {
618 required_signatures,
619 lifetime,
620 accounts,
621 priority_fee,
622 compute_unit_limit,
623 loaded_accounts_data_size_limit,
624 heap_size,
625 program_id_index,
626 instr_accounts,
627 data,
628 }
629 }
630 }
631
632 proptest! {
633 #[test]
634 fn test_v1_message_raw_bytes_roundtrip(test_data in generate_message_data()) {
635 let accounts: Vec<Address> = test_data.accounts.into_iter()
636 .map(Address::new_from_array).collect();
637 let lifetime = Hash::new_from_array(test_data.lifetime);
638
639 let mut builder = v1::MessageBuilder::new()
640 .required_signatures(test_data.required_signatures)
641 .lifetime_specifier(lifetime)
642 .accounts(accounts)
643 .instruction(CompiledInstruction {
644 program_id_index: test_data.program_id_index,
645 accounts: test_data.instr_accounts,
646 data: test_data.data,
647 });
648
649 if let Some(priority_fee) = test_data.priority_fee {
651 builder = builder.priority_fee(priority_fee);
652 }
653 if let Some(compute_unit_limit) = test_data.compute_unit_limit {
654 builder = builder.compute_unit_limit(compute_unit_limit);
655 }
656 if let Some(loaded_accounts_data_size_limit) = test_data.loaded_accounts_data_size_limit {
657 builder = builder.loaded_accounts_data_size_limit(loaded_accounts_data_size_limit);
658 }
659 if let Some(heap_size) = test_data.heap_size {
660 builder = builder.heap_size(heap_size);
661 }
662
663 let message = builder.build().unwrap();
664
665 let bytes = wincode::serialize(&message).unwrap();
667 let parsed = v1::deserialize(&bytes).unwrap();
669
670 assert_eq!(message, parsed);
672 assert_eq!(message, wincode::deserialize(&bytes).unwrap());
673
674 let versioned = VersionedMessage::V1(message);
676 let serialized = versioned.serialize();
677
678 assert!(!serialized.is_empty());
683 assert_eq!(serialized[0], V1_PREFIX);
684 assert_eq!(&serialized[1..], bytes.as_slice());
685 }
686 }
687
688 #[test]
689 fn test_v1_versioned_message_json_roundtrip() {
690 let msg = v1::MessageBuilder::new()
691 .required_signatures(1)
692 .lifetime_specifier(Hash::new_unique())
693 .accounts(vec![Address::new_unique(), Address::new_unique()])
694 .priority_fee(1000)
695 .compute_unit_limit(200_000)
696 .instruction(CompiledInstruction {
697 program_id_index: 1,
698 accounts: vec![0],
699 data: vec![1, 2, 3, 4],
700 })
701 .build()
702 .unwrap();
703
704 let vm = VersionedMessage::V1(msg);
705 let s = serde_json::to_string(&vm).unwrap();
706 let back: VersionedMessage = serde_json::from_str(&s).unwrap();
707 assert_eq!(vm, back);
708 }
709
710 #[cfg(feature = "wincode")]
711 #[test]
712 fn test_v1_wincode_roundtrip() {
713 let test_messages = [
714 v1::MessageBuilder::new()
716 .required_signatures(1)
717 .lifetime_specifier(Hash::new_unique())
718 .accounts(vec![Address::new_unique(), Address::new_unique()])
719 .instruction(CompiledInstruction {
720 program_id_index: 1,
721 accounts: vec![0],
722 data: vec![],
723 })
724 .build()
725 .unwrap(),
726 v1::MessageBuilder::new()
728 .required_signatures(1)
729 .lifetime_specifier(Hash::new_unique())
730 .accounts(vec![Address::new_unique(), Address::new_unique()])
731 .priority_fee(1000)
732 .compute_unit_limit(200_000)
733 .instruction(CompiledInstruction {
734 program_id_index: 1,
735 accounts: vec![0],
736 data: vec![1, 2, 3, 4],
737 })
738 .build()
739 .unwrap(),
740 v1::MessageBuilder::new()
742 .required_signatures(2)
743 .lifetime_specifier(Hash::new_unique())
744 .accounts(vec![
745 Address::new_unique(),
746 Address::new_unique(),
747 Address::new_unique(),
748 ])
749 .heap_size(65536)
750 .instructions(vec![
751 CompiledInstruction {
752 program_id_index: 2,
753 accounts: vec![0, 1],
754 data: vec![0xAA, 0xBB],
755 },
756 CompiledInstruction {
757 program_id_index: 2,
758 accounts: vec![1],
759 data: vec![0xCC],
760 },
761 ])
762 .build()
763 .unwrap(),
764 ];
765
766 for message in test_messages {
767 let versioned = VersionedMessage::V1(message.clone());
768
769 let bytes = wincode::serialize(&versioned).expect("Wincode serialize failed");
771 let deserialized: VersionedMessage =
772 wincode::deserialize(&bytes).expect("Wincode deserialize failed");
773
774 match deserialized {
775 VersionedMessage::V1(parsed) => assert_eq!(parsed, message),
776 _ => panic!("Expected V1 message"),
777 }
778 }
779 }
780}