Skip to main content

solana_message/versions/
mod.rs

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