Skip to main content

solana_message/versions/
mod.rs

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
43/// Bit mask that indicates whether a serialized message is versioned.
44pub const MESSAGE_VERSION_PREFIX: u8 = 0x80;
45
46/// Either a legacy message, v0 or a v1 message.
47///
48/// # Serialization
49///
50/// If the first bit is set, the remaining 7 bits will be used to determine
51/// which message version is serialized starting from version `0`. If the first
52/// is bit is not set, all bytes are used to encode the legacy `Message`
53/// format.
54#[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    /// Returns true if the account at the specified index signed this
100    /// message.
101    pub fn is_signer(&self, index: usize) -> bool {
102        index < usize::from(self.header().num_required_signatures)
103    }
104
105    /// Returns true if the account at the specified index is writable by the
106    /// instructions in this message. Since dynamically loaded addresses can't
107    /// have write locks demoted without loading addresses, this shouldn't be
108    /// used in the runtime.
109    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    /// Returns true if the account at the specified index is an input to some
122    /// program instruction in this message.
123    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    /// Returns true if the account at the specified index is not invoked as a
142    /// program or, if invoked, is passed to a program.
143    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    /// Program instructions that will be executed in sequence and committed in
164    /// one atomic transaction if all succeed.
165    #[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    /// Compute the blake3 hash of this transaction's message
181    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    /// Compute the blake3 hash of a raw transaction message
188    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                // Note that this format does not match the wire format per SIMD-0385.
224
225                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            // Serde's integer visitors bubble up to u64 so check the prefix
256            // with this function instead of visit_u8. This approach is
257            // necessary because serde_json directly calls visit_u64 for
258            // unsigned integers.
259            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                        // The remaining fields of the legacy Message struct after the first byte.
303                        #[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                                // will never happen since tuple length is always 2
317                                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                                        // will never happen since tuple length is always 2
338                                        de::Error::invalid_length(1, &self)
339                                    },
340                                )?))
341                            }
342                            1 => {
343                                Ok(VersionedMessage::V1(seq.next_element()?.ok_or_else(
344                                    || {
345                                        // will never happen since tuple length is always 2
346                                        de::Error::invalid_length(1, &self)
347                                    },
348                                )?))
349                            }
350                            127 => {
351                                // 0xff is used as the first byte of the off-chain messages
352                                // which corresponds to version 127 of the versioned messages.
353                                // This explicit check is added to prevent the usage of version 127
354                                // in the runtime as a valid transaction.
355                                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    // V0 and V1 add +1 for message version prefix
376    #[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    // V0 and V1 add +1 for message version prefix
391    #[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                // SAFETY: buffer has sufficient capacity for serialization.
406                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        // If the first bit is set, the remaining 7 bits will be used to determine
427        // which message version is serialized starting from version `0`. If the first
428        // is bit is not set, all bytes are used to encode the legacy `Message`
429        // format.
430        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                    // -1 for already-read variant byte
444                    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                    // SAFETY: `deserialize` validates that we read `consumed` bytes.
449                    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        // We've already read the variant byte which, in the legacy case, represents
462        // the `num_required_signatures` field.
463        // As such, we need to write the remaining fields into the message manually,
464        // as calling `LegacyMessage::read` will miss the first field.
465        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        // bincode
542        {
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        // serde_json
555        {
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                // Generate between 12 and 64 accounts since we need at least the
604                // amount of `required_signatures`.
605                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                // The `program_id_index` cannot be 0 (payer).
615                program_id_index in 1u8..accounts.len() as u8,
616                // we need to have at least `required_signatures` accounts.
617                instr_accounts in vec(
618                    0u8..accounts.len() as u8,
619                    (required_signatures as usize)..=accounts.len(),
620                ),
621                // Keep instruction data relatively small to avoid hitting the maximum
622                // transaction size when combined with the accounts.
623                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            // config values.
666            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            // Serialize V1 to raw bytes.
682            let bytes = v1::serialize(&message);
683            // Deserialize from raw bytes.
684            let (parsed, _) = v1::deserialize(&bytes).unwrap();
685
686            // Messages should match.
687            assert_eq!(message, parsed);
688
689            // Wrap in VersionedMessage and test `serialize()`.
690            let versioned = VersionedMessage::V1(message);
691            let serialized = versioned.serialize();
692
693            // Assert that everything worked:
694            // - serialized message is not empty.
695            // - first byte is the version prefix with the correct version.
696            // - remaining bytes match the original serialized message.
697            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            // Minimal message
730            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            // With config
742            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            // Multiple instructions
756            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            // Wincode roundtrip
785            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}