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