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};
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
41/// Bit mask that indicates whether a serialized message is versioned.
42pub const MESSAGE_VERSION_PREFIX: u8 = 0x80;
43
44/// Either a legacy message, v0 or a v1 message.
45///
46/// # Serialization
47///
48/// If the first bit is set, the remaining 7 bits will be used to determine
49/// which message version is serialized starting from version `0`. If the first
50/// is bit is not set, all bytes are used to encode the legacy `Message`
51/// format.
52#[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    /// Returns true if the account at the specified index signed this
103    /// message.
104    pub fn is_signer(&self, index: usize) -> bool {
105        index < usize::from(self.header().num_required_signatures)
106    }
107
108    /// Returns true if the account at the specified index is writable by the
109    /// instructions in this message. Since dynamically loaded addresses can't
110    /// have write locks demoted without loading addresses, this shouldn't be
111    /// used in the runtime.
112    #[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    /// Returns true if the account at the specified index is an input to some
126    /// program instruction in this message.
127    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    /// Returns true if the account at the specified index is not invoked as a
146    /// program or, if invoked, is passed to a program.
147    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    /// Program instructions that will be executed in sequence and committed in
168    /// one atomic transaction if all succeed.
169    #[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    /// Compute the blake3 hash of this transaction's message
185    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    /// Compute the blake3 hash of a raw transaction message
192    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                // Note that this format does not match the wire format per SIMD-0385.
228
229                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            // Serde's integer visitors bubble up to u64 so check the prefix
260            // with this function instead of visit_u8. This approach is
261            // necessary because serde_json directly calls visit_u64 for
262            // unsigned integers.
263            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                        // The remaining fields of the legacy Message struct after the first byte.
307                        #[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                                // will never happen since tuple length is always 2
321                                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                                        // will never happen since tuple length is always 2
342                                        de::Error::invalid_length(1, &self)
343                                    },
344                                )?))
345                            }
346                            1 => {
347                                Ok(VersionedMessage::V1(seq.next_element()?.ok_or_else(
348                                    || {
349                                        // will never happen since tuple length is always 2
350                                        de::Error::invalid_length(1, &self)
351                                    },
352                                )?))
353                            }
354                            127 => {
355                                // 0xff is used as the first byte of the off-chain messages
356                                // which corresponds to version 127 of the versioned messages.
357                                // This explicit check is added to prevent the usage of version 127
358                                // in the runtime as a valid transaction.
359                                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    // V0 and V1 add +1 for message version prefix
380    #[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    // V0 and V1 add +1 for message version prefix
395    #[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 the first bit is set, the remaining 7 bits will be used to determine
424        // which message version is serialized starting from version `0`. If the first
425        // is bit is not set, all bytes are used to encode the legacy `Message`
426        // format.
427        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        // bincode
524        {
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        // serde_json
537        {
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                // Generate between 12 and 64 accounts since we need at least the
586                // amount of `required_signatures`.
587                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 must be a multiple of 1024 and between MIN_HEAP_SIZE
593                // and MAX_HEAP_SIZE if specified.
594                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                // The `program_id_index` cannot be 0 (payer).
599                program_id_index in 1u8..accounts.len() as u8,
600                // we need to have at least `required_signatures` accounts.
601                instr_accounts in vec(
602                    0u8..accounts.len() as u8,
603                    (required_signatures as usize)..=accounts.len(),
604                ),
605                // Keep instruction data relatively small to avoid hitting the maximum
606                // transaction size when combined with the accounts.
607                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            // config values.
650            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            // Serialize V1 to raw bytes (without the version prefix).
666            let bytes = wincode::serialize(&message).unwrap();
667            // Deserialize from raw bytes.
668            let parsed = v1::deserialize(&bytes).unwrap();
669
670            // Messages should match.
671            assert_eq!(message, parsed);
672            assert_eq!(message, wincode::deserialize(&bytes).unwrap());
673
674            // Wrap in VersionedMessage and test `serialize()`.
675            let versioned = VersionedMessage::V1(message);
676            let serialized = versioned.serialize();
677
678            // Assert that everything worked:
679            // - serialized message is not empty.
680            // - first byte is the version prefix with the correct version.
681            // - remaining bytes match the original serialized message.
682            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            // Minimal message
715            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            // With config
727            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            // Multiple instructions
741            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            // Wincode roundtrip
770            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}