Skip to main content

solana_message/versions/v1/
message.rs

1//! Core Message type for V1 transactions (SIMD-0385).
2//!
3//! A new transaction format that is designed to enable larger transactions
4//! sizes while not having the address lookup table features introduced in
5//! v0 transactions. The v1 transaction format also does not require compute
6//! budget instructions to be present within the transaction.
7//!
8//! # Binary Format
9//!
10//! ```text
11//! ┌────────────────────────────────────────────────────────┐
12//! │ * LegacyHeader (3 x u8)                                │
13//! │                                                        │
14//! │ * TransactionConfigMask (u32, little-endian)           │
15//! │                                                        │
16//! │ * LifetimeSpecifier [u8; 32] (blockhash)               │
17//! │                                                        │
18//! │ * NumInstructions (u8, max 64)                         │
19//! │                                                        │
20//! │ * NumAddresses (u8, max 64)                            │
21//! │                                                        │
22//! │ * Addresses [[u8; 32] x NumAddresses]                  │
23//! │                                                        │
24//! │ * ConfigValues ([[u8; 4] * variable based on mask])    │
25//! │                                                        │
26//! │ * InstructionHeaders [(u8, u8, u16) x NumInstructions] │
27//! │                                                        │
28//! │ * InstructionPayloads (variable based on headers)      │
29//! │    └─ Per NumInstructions:                             │
30//! │         +- [u8] account indices                        │
31//! │         └─ [u8] instruction data                       │
32//! └────────────────────────────────────────────────────────┘
33//! ```
34
35// Re-export for convenient access to the message builder in tests.
36#[cfg(test)]
37pub use self::tests::MessageBuilder;
38#[cfg(feature = "serde")]
39use serde_derive::{Deserialize, Serialize};
40#[cfg(feature = "frozen-abi")]
41use solana_frozen_abi_macro::AbiExample;
42#[cfg(feature = "wincode")]
43use {
44    crate::v1::{InstructionHeader, FIXED_HEADER_SIZE},
45    core::{mem::MaybeUninit, slice::from_raw_parts},
46    wincode::{
47        config::{Config, ConfigCore},
48        context,
49        io::{Reader, Writer},
50        len::SeqLen,
51        ReadResult, SchemaRead, SchemaReadContext, SchemaWrite, WriteResult,
52    },
53};
54use {
55    crate::{
56        compiled_instruction::CompiledInstruction,
57        compiled_keys::CompiledKeys,
58        v1::{
59            MessageError, TransactionConfig, TransactionConfigMask, MAX_ADDRESSES, MAX_HEAP_SIZE,
60            MAX_INSTRUCTIONS, MAX_SIGNATURES, MIN_HEAP_SIZE,
61        },
62        AccountKeys, CompileError, MessageHeader,
63    },
64    core::mem::size_of,
65    solana_address::Address,
66    solana_hash::Hash,
67    solana_instruction::Instruction,
68    solana_sanitize::{Sanitize, SanitizeError},
69    std::collections::HashSet,
70};
71
72/// A V1 transaction message (SIMD-0385) supporting 4KB transactions with inline compute budget.
73///
74/// # Important
75///
76/// This message format does not support bincode binary serialization. Use the provided
77/// `serialize` and `deserialize` functions for binary encoding/decoding.
78#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
79#[cfg_attr(
80    feature = "serde",
81    derive(Serialize, Deserialize),
82    serde(rename_all = "camelCase")
83)]
84#[derive(Debug, Clone, PartialEq, Eq, Default)]
85pub struct Message {
86    /// The message header describing signer/readonly account counts.
87    pub header: MessageHeader,
88
89    /// Configuration for transaction parameters.
90    pub config: TransactionConfig,
91
92    /// The lifetime specifier (blockhash) that determines when this transaction expires.
93    pub lifetime_specifier: Hash,
94
95    /// All account addresses referenced by this message.
96    ///
97    /// The length should be specified as an `u8`. Unlike V0, V1 does not support
98    /// address lookup tables. The ordering of the addresses is unchanged from prior
99    /// transaction formats:
100    ///
101    ///   - `num_required_signatures-num_readonly_signed_accounts` additional addresses
102    ///     for which the transaction contains signatures and are loaded as writable, of
103    ///     which the first is the fee payer.
104    ///
105    ///   - `num_readonly_signed_accounts` addresses for which the transaction contains
106    ///     signatures and are loaded as readonly.
107    ///
108    ///   - `num_addresses-num_required_signatures-num_readonly_unsigned_accounts` addresses
109    ///     for which the transaction does not contain signatures and are loaded as writable.
110    ///
111    ///   - `num_readonly_unsigned_accounts` addresses for which the transaction does not
112    ///     contain signatures and are loaded as readonly.
113    pub account_keys: Vec<Address>,
114
115    /// Program instructions to execute.
116    pub instructions: Vec<CompiledInstruction>,
117}
118
119impl Message {
120    /// Create a new V1 message.
121    pub fn new(
122        header: MessageHeader,
123        config: TransactionConfig,
124        lifetime_specifier: Hash,
125        account_keys: Vec<Address>,
126        instructions: Vec<CompiledInstruction>,
127    ) -> Self {
128        Self {
129            header,
130            config,
131            lifetime_specifier,
132            account_keys,
133            instructions,
134        }
135    }
136
137    /// Create a signable transaction message from a `payer` public key,
138    /// `recent_blockhash`, list of `instructions` and a transaction `config`.
139    ///
140    /// # Examples
141    ///
142    /// This example uses the [`solana_rpc_client`], [`solana_account`], and [`anyhow`] crates.
143    ///
144    /// [`solana_rpc_client`]: https://docs.rs/solana-rpc-client
145    /// [`solana_account`]: https://docs.rs/solana-account
146    /// [`anyhow`]: https://docs.rs/anyhow
147    ///
148    /// ```
149    /// # use solana_example_mocks::{
150    /// #     solana_rpc_client,
151    /// #     solana_account,
152    /// #     solana_signer,
153    /// #     solana_keypair,
154    /// # };
155    /// # use std::borrow::Cow;
156    /// # use solana_account::Account;
157    /// use anyhow::Result;
158    /// use solana_instruction::{AccountMeta, Instruction};
159    /// use solana_keypair::Keypair;
160    /// use solana_message::{VersionedMessage, v1};
161    /// use solana_address::Address;
162    /// use solana_rpc_client::rpc_client::RpcClient;
163    /// use solana_signer::Signer;
164    /// # mod solana_transaction {
165    /// #     pub mod versioned {
166    /// #         use solana_example_mocks::{solana_keypair::Keypair, solana_signer::SignerError};
167    /// #         use solana_message::VersionedMessage;
168    /// #         pub struct VersionedTransaction {
169    /// #             pub message: solana_message::VersionedMessage,
170    /// #         }
171    /// #         impl VersionedTransaction {
172    /// #             pub fn try_new(
173    /// #                 message: VersionedMessage,
174    /// #                 _keypairs: &[&Keypair],
175    /// #             ) -> std::result::Result<Self, solana_example_mocks::solana_signer::SignerError> {
176    /// #                 Ok(VersionedTransaction {
177    /// #                     message,
178    /// #                 })
179    /// #             }
180    /// #         }
181    /// #     }
182    /// # }
183    /// use solana_transaction::versioned::VersionedTransaction;
184    ///
185    /// fn create_v1_tx(
186    ///     client: &RpcClient,
187    ///     instruction: Instruction,
188    ///     payer: &Keypair,
189    /// ) -> Result<VersionedTransaction> {
190    ///     let blockhash = client.get_latest_blockhash()?;
191    ///     let tx = VersionedTransaction::try_new(
192    ///         VersionedMessage::V1(v1::Message::try_compile(
193    ///             &payer.pubkey(),
194    ///             &[instruction],
195    ///             blockhash,
196    ///         )?),
197    ///         &[payer],
198    ///     )?;
199    ///
200    ///     Ok(tx)
201    /// }
202    /// #
203    /// # let client = RpcClient::new(String::new());
204    /// # let payer = Keypair::new();
205    /// # let instruction = Instruction::new_with_bincode(Address::new_unique(), &(), vec![
206    /// #   AccountMeta::new(Address::new_unique(), false),
207    /// # ]);
208    /// # create_v1_tx(&client, instruction, &payer)?;
209    /// # Ok::<(), anyhow::Error>(())
210    /// ```
211    pub fn try_compile(
212        payer: &Address,
213        instructions: &[Instruction],
214        recent_blockhash: Hash,
215    ) -> Result<Self, CompileError> {
216        Self::try_compile_with_config(
217            payer,
218            instructions,
219            recent_blockhash,
220            TransactionConfig::empty(),
221        )
222    }
223
224    /// Create a signable transaction message from a `payer` public key,
225    /// `recent_blockhash`, list of `instructions` and a transaction `config`.
226    ///
227    /// # Examples
228    ///
229    /// This example uses the [`solana_rpc_client`], [`solana_account`], and [`anyhow`] crates.
230    ///
231    /// [`solana_rpc_client`]: https://docs.rs/solana-rpc-client
232    /// [`solana_account`]: https://docs.rs/solana-account
233    /// [`anyhow`]: https://docs.rs/anyhow
234    ///
235    /// ```
236    /// # use solana_example_mocks::{
237    /// #     solana_rpc_client,
238    /// #     solana_account,
239    /// #     solana_signer,
240    /// #     solana_keypair,
241    /// # };
242    /// # use std::borrow::Cow;
243    /// # use solana_account::Account;
244    /// use anyhow::Result;
245    /// use solana_instruction::{AccountMeta, Instruction};
246    /// use solana_keypair::Keypair;
247    /// use solana_message::{VersionedMessage, v1, v1::TransactionConfig};
248    /// use solana_address::Address;
249    /// use solana_rpc_client::rpc_client::RpcClient;
250    /// use solana_signer::Signer;
251    /// # mod solana_transaction {
252    /// #     pub mod versioned {
253    /// #         use solana_example_mocks::{solana_keypair::Keypair, solana_signer::SignerError};
254    /// #         use solana_message::VersionedMessage;
255    /// #         pub struct VersionedTransaction {
256    /// #             pub message: solana_message::VersionedMessage,
257    /// #         }
258    /// #         impl VersionedTransaction {
259    /// #             pub fn try_new(
260    /// #                 message: VersionedMessage,
261    /// #                 _keypairs: &[&Keypair],
262    /// #             ) -> std::result::Result<Self, solana_example_mocks::solana_signer::SignerError> {
263    /// #                 Ok(VersionedTransaction {
264    /// #                     message,
265    /// #                 })
266    /// #             }
267    /// #         }
268    /// #     }
269    /// # }
270    /// use solana_transaction::versioned::VersionedTransaction;
271    ///
272    /// fn create_v1_tx(
273    ///     client: &RpcClient,
274    ///     instruction: Instruction,
275    ///     payer: &Keypair,
276    /// ) -> Result<VersionedTransaction> {
277    ///     let blockhash = client.get_latest_blockhash()?;
278    ///     let tx = VersionedTransaction::try_new(
279    ///         VersionedMessage::V1(v1::Message::try_compile_with_config(
280    ///             &payer.pubkey(),
281    ///             &[instruction],
282    ///             blockhash,
283    ///             TransactionConfig::empty().with_compute_unit_limit(100),
284    ///         )?),
285    ///         &[payer],
286    ///     )?;
287    ///
288    ///     Ok(tx)
289    /// }
290    /// #
291    /// # let client = RpcClient::new(String::new());
292    /// # let payer = Keypair::new();
293    /// # let instruction = Instruction::new_with_bincode(Address::new_unique(), &(), vec![
294    /// #   AccountMeta::new(Address::new_unique(), false),
295    /// # ]);
296    /// # create_v1_tx(&client, instruction, &payer)?;
297    /// # Ok::<(), anyhow::Error>(())
298    /// ```
299    pub fn try_compile_with_config(
300        payer: &Address,
301        instructions: &[Instruction],
302        recent_blockhash: Hash,
303        config: TransactionConfig,
304    ) -> Result<Self, CompileError> {
305        let compiled_keys = CompiledKeys::compile(instructions, Some(*payer));
306        let (header, static_keys) = compiled_keys.try_into_message_components()?;
307
308        let account_keys = AccountKeys::new(&static_keys, None);
309        let instructions = account_keys.try_compile_instructions(instructions)?;
310
311        Ok(Self {
312            header,
313            config,
314            lifetime_specifier: recent_blockhash,
315            account_keys: static_keys,
316            instructions,
317        })
318    }
319
320    /// Returns the fee payer address (first account key).
321    pub fn fee_payer(&self) -> Option<&Address> {
322        self.account_keys.first()
323    }
324
325    /// Account keys are ordered with signers first: `[signers..., non-signers...]`.
326    /// An index falls in the signer region if it's less than `num_required_signatures`.
327    pub fn is_signer(&self, index: usize) -> bool {
328        index < usize::from(self.header.num_required_signatures)
329    }
330
331    /// Returns true if the account at this index is both a signer and writable.
332    pub fn is_signer_writable(&self, index: usize) -> bool {
333        if !self.is_signer(index) {
334            return false;
335        }
336        // Within the signer region, the first (num_required_signatures - num_readonly_signed)
337        // accounts are writable signers.
338        let num_writable_signers = usize::from(self.header.num_required_signatures)
339            .saturating_sub(usize::from(self.header.num_readonly_signed_accounts));
340        index < num_writable_signers
341    }
342
343    /// Returns true if any instruction invokes the account at this index as a program.
344    pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
345        crate::is_key_called_as_program(&self.instructions, key_index)
346    }
347
348    /// Returns `true` if the account at the specified index was requested to be
349    /// writable.
350    ///
351    /// This method should not be used directly.
352    #[inline(always)]
353    pub(crate) fn is_writable_index(&self, i: usize) -> bool {
354        crate::is_writable_index(i, self.header, &self.account_keys)
355    }
356
357    /// Returns true if the BPF upgradeable loader is present in the account keys.
358    pub fn is_upgradeable_loader_present(&self) -> bool {
359        crate::is_upgradeable_loader_present(&self.account_keys)
360    }
361
362    /// Returns `true` if the account at the specified index was requested as
363    /// writable.
364    ///
365    ///
366    /// # Important
367    ///
368    /// Before loading addresses, we can't demote write locks properly so this should
369    /// not be used by the runtime. The `reserved_account_keys` parameter is optional
370    /// to allow clients to approximate writability without requiring fetching the latest
371    /// set of reserved account keys.
372    ///
373    /// Program accounts are demoted from writable to readonly, unless the upgradeable
374    /// loader is present in which case they are left as writable since upgradeable
375    /// programs need to be writable for upgrades.
376    pub fn is_maybe_writable(
377        &self,
378        key_index: usize,
379        reserved_account_keys: Option<&HashSet<Address>>,
380    ) -> bool {
381        crate::is_maybe_writable(
382            key_index,
383            self.header,
384            &self.account_keys,
385            &self.instructions,
386            reserved_account_keys,
387        )
388    }
389
390    pub fn demote_program_id(&self, i: usize) -> bool {
391        crate::is_program_id_write_demoted(i, &self.account_keys, &self.instructions)
392    }
393
394    /// Serialize this message with the V1 version prefix byte.
395    #[cfg(feature = "wincode")]
396    pub fn serialize(&self) -> Vec<u8> {
397        wincode::serialize(&(crate::v1::V1_PREFIX, self)).unwrap()
398    }
399
400    /// Calculate the serialized size of the message in bytes.
401    #[allow(clippy::arithmetic_side_effects)]
402    #[inline(always)]
403    pub fn size(&self) -> usize {
404        size_of::<MessageHeader>()                           // legacy header
405            + size_of::<TransactionConfigMask>()             // config mask
406            + size_of::<Hash>()                              // lifetime specifier
407            + size_of::<u8>()                                // number of instructions
408            + size_of::<u8>()                                // number of addresses
409            + self.account_keys.len() * size_of::<Address>() // addresses
410            + self.config.size()                             // config values
411            + self.instructions.len()
412                * (
413                    size_of::<u8>()
414                    + size_of::<u8>()
415                    + size_of::<u16>()
416                )                                            // instruction headers
417            + self
418                .instructions
419                .iter()
420                .map(|ix| {
421                    (ix.accounts.len() * size_of::<u8>())
422                    + ix.data.len()
423                })
424                .sum::<usize>() // instruction payloads
425    }
426
427    pub fn validate(&self) -> Result<(), MessageError> {
428        // `num_required_signatures` <= 12
429        if self.header.num_required_signatures > MAX_SIGNATURES {
430            return Err(MessageError::TooManySignatures);
431        }
432
433        // `num_instructions` <= 64
434        if self.instructions.len() > MAX_INSTRUCTIONS as usize {
435            return Err(MessageError::TooManyInstructions);
436        }
437
438        let num_account_keys = self.account_keys.len();
439
440        // `num_addresses` <= 64
441        if num_account_keys > MAX_ADDRESSES as usize {
442            return Err(MessageError::TooManyAddresses);
443        }
444
445        // `num_addresses` >= `num_required_signatures` + `num_readonly_unsigned_accounts`
446        let min_accounts = usize::from(self.header.num_required_signatures)
447            .saturating_add(usize::from(self.header.num_readonly_unsigned_accounts));
448
449        if num_account_keys < min_accounts {
450            return Err(MessageError::NotEnoughAddressesForSignatures);
451        }
452
453        // must have at least 1 RW fee-payer (`num_readonly_signed` < `num_required_signatures`)
454        if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures {
455            return Err(MessageError::ZeroSigners);
456        }
457
458        // no duplicate addresses
459        let unique_keys: HashSet<_> = self.account_keys.iter().collect();
460        if unique_keys.len() != num_account_keys {
461            return Err(MessageError::DuplicateAddresses);
462        }
463
464        // validate config mask (2-bit fields must have both bits set or neither)
465        let mask: TransactionConfigMask = self.config.into();
466
467        if mask.has_invalid_priority_fee_bits() {
468            return Err(MessageError::InvalidConfigMask);
469        }
470
471        // if specified, heap size must be a multiple of 1024 and within valid bounds
472        if let Some(heap_size) = self.config.heap_size {
473            if heap_size % 1024 != 0 {
474                return Err(MessageError::InvalidHeapSize);
475            }
476
477            if !(MIN_HEAP_SIZE..=MAX_HEAP_SIZE).contains(&heap_size) {
478                return Err(MessageError::InvalidHeapSize);
479            }
480        }
481
482        // instruction account indices must be < `num_addresses`
483        let max_account_index = num_account_keys
484            .checked_sub(1)
485            .ok_or(MessageError::NotEnoughAccountKeys)?;
486
487        for instruction in &self.instructions {
488            // program id must be in static accounts
489            if usize::from(instruction.program_id_index) > max_account_index {
490                return Err(MessageError::InvalidInstructionAccountIndex);
491            }
492
493            // program cannot be fee payer
494            if instruction.program_id_index == 0 {
495                return Err(MessageError::InvalidInstructionAccountIndex);
496            }
497
498            // instruction accounts count must fit in u8
499            if instruction.accounts.len() > u8::MAX as usize {
500                return Err(MessageError::InstructionAccountsTooLarge);
501            }
502
503            // instruction data length must fit in u16
504            if instruction.data.len() > u16::MAX as usize {
505                return Err(MessageError::InstructionDataTooLarge);
506            }
507
508            // all account indices must be valid
509            for &account_index in &instruction.accounts {
510                if usize::from(account_index) > max_account_index {
511                    return Err(MessageError::InvalidInstructionAccountIndex);
512                }
513            }
514        }
515
516        Ok(())
517    }
518}
519
520impl Sanitize for Message {
521    fn sanitize(&self) -> Result<(), SanitizeError> {
522        Ok(self.validate()?)
523    }
524}
525
526#[cfg(feature = "wincode")]
527unsafe impl<C: ConfigCore> SchemaWrite<C> for Message {
528    type Src = Self;
529
530    #[inline(always)]
531    fn size_of(src: &Self::Src) -> WriteResult<usize> {
532        Ok(src.size())
533    }
534
535    fn write(mut writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
536        // SAFETY: `Message::size()` yields the exact number of bytes to be written.
537        let mut writer = unsafe { writer.as_trusted_for(src.size()) }?;
538        writer.write(&[
539            src.header.num_required_signatures,
540            src.header.num_readonly_signed_accounts,
541            src.header.num_readonly_unsigned_accounts,
542        ])?;
543        let mask = TransactionConfigMask::from(&src.config).0.to_le_bytes();
544        writer.write(&mask)?;
545        writer.write(src.lifetime_specifier.as_bytes())?;
546        writer.write(&[src.instructions.len() as u8, src.account_keys.len() as u8])?;
547
548        // SAFETY: `Address` is `#[repr(transparent)]` over `[u8; 32]`, so it is safe to
549        // treat as bytes.
550        #[expect(clippy::arithmetic_side_effects)]
551        let account_keys = unsafe {
552            from_raw_parts(
553                src.account_keys.as_ptr().cast::<u8>(),
554                src.account_keys.len() * size_of::<Address>(),
555            )
556        };
557        writer.write(account_keys)?;
558
559        if let Some(value) = src.config.priority_fee {
560            writer.write(&value.to_le_bytes())?;
561        }
562        if let Some(value) = src.config.compute_unit_limit {
563            writer.write(&value.to_le_bytes())?;
564        }
565        if let Some(value) = src.config.loaded_accounts_data_size_limit {
566            writer.write(&value.to_le_bytes())?;
567        }
568        if let Some(value) = src.config.heap_size {
569            writer.write(&value.to_le_bytes())?;
570        }
571
572        for ix in &src.instructions {
573            writer.write(&[ix.program_id_index, ix.accounts.len() as u8])?;
574            writer.write(&(ix.data.len() as u16).to_le_bytes())?;
575        }
576
577        for ix in &src.instructions {
578            writer.write(&ix.accounts)?;
579            writer.write(&ix.data)?;
580        }
581
582        writer.finish()?;
583
584        Ok(())
585    }
586}
587
588/// Serialize the message.
589#[cfg(feature = "wincode")]
590#[inline]
591#[deprecated(since = "4.1.2", note = "use `Message::serialize` instead")]
592pub fn serialize(message: &Message) -> Vec<u8> {
593    wincode::serialize(message).unwrap()
594}
595
596#[cfg(feature = "wincode")]
597unsafe impl<'de, C: Config> SchemaRead<'de, C> for Message {
598    type Dst = Message;
599
600    #[expect(clippy::arithmetic_side_effects)]
601    fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
602        let (header, lifetime_specifier, config_mask, num_instructions, num_addresses) = {
603            // SAFETY: the following reads consume exactly `FIXED_HEADER_SIZE` bytes.
604            // - MessageHeader (3 bytes)
605            // - TransactionConfigMask (4 bytes)
606            // - Hash (32 bytes)
607            // - num_instructions (1 byte)
608            // - num_addresses (1 byte)
609            let mut reader = unsafe { reader.as_trusted_for(FIXED_HEADER_SIZE)? };
610            let header = <MessageHeader as SchemaRead<C>>::get(reader.by_ref())?;
611            let config_mask = TransactionConfigMask(u32::from_le_bytes(reader.take_array()?));
612            let lifetime_specifier = <Hash as SchemaRead<C>>::get(reader.by_ref())?;
613            let num_instructions = reader.take_byte()? as usize;
614            let num_addresses = reader.take_byte()? as usize;
615            (
616                header,
617                lifetime_specifier,
618                config_mask,
619                num_instructions,
620                num_addresses,
621            )
622        };
623
624        <C::LengthEncoding as SeqLen<C>>::prealloc_check::<Address>(num_addresses)?;
625        let account_keys = <Vec<Address> as SchemaReadContext<C, context::Len>>::get_with_context(
626            context::Len(num_addresses),
627            reader.by_ref(),
628        )?;
629
630        let mut config = TransactionConfig::empty();
631        if config_mask.has_priority_fee() {
632            config.priority_fee = Some(u64::from_le_bytes(reader.take_array()?));
633        }
634        if config_mask.has_compute_unit_limit() {
635            config.compute_unit_limit = Some(u32::from_le_bytes(reader.take_array()?));
636        }
637        if config_mask.has_loaded_accounts_data_size() {
638            config.loaded_accounts_data_size_limit = Some(u32::from_le_bytes(reader.take_array()?));
639        }
640        if config_mask.has_heap_size() {
641            config.heap_size = Some(u32::from_le_bytes(reader.take_array()?));
642        }
643
644        // SAFETY:
645        // - `take_borrowed(num_instructions * size_of::<InstructionHeader>())` returns
646        //   exactly the requested number of bytes, or errors.
647        // - `take_borrowed` returns a stable borrow from the backing buffer, so the
648        //   resulting slice remains valid across subsequent reader operations.
649        // - `InstructionHeader` has alignment 1 and is encoded exactly as
650        //   4 bytes `(u8, u8, [u8; 2])`.
651        let instruction_headers = unsafe {
652            from_raw_parts(
653                reader
654                    .take_borrowed(num_instructions * size_of::<InstructionHeader>())?
655                    .as_ptr() as *const InstructionHeader,
656                num_instructions,
657            )
658        };
659        let mut instructions = Vec::with_capacity(num_instructions);
660        for header in instruction_headers {
661            let program_id_index = header.0;
662            let num_accounts = header.1 as usize;
663            let data_len = u16::from_le_bytes(header.2) as usize;
664
665            <C::LengthEncoding as SeqLen<C>>::prealloc_check::<u8>(num_accounts)?;
666            let accounts = <Vec<u8> as SchemaReadContext<C, context::Len>>::get_with_context(
667                context::Len(num_accounts),
668                reader.by_ref(),
669            )?;
670            <C::LengthEncoding as SeqLen<C>>::prealloc_check::<u8>(data_len)?;
671            let data = <Vec<u8> as SchemaReadContext<C, context::Len>>::get_with_context(
672                context::Len(data_len),
673                reader.by_ref(),
674            )?;
675
676            instructions.push(CompiledInstruction {
677                program_id_index,
678                accounts,
679                data,
680            });
681        }
682
683        dst.write(Message {
684            header,
685            lifetime_specifier,
686            config,
687            account_keys,
688            instructions,
689        });
690
691        Ok(())
692    }
693}
694
695/// Deserialize the message from the provided input buffer, returning the message and
696/// the number of bytes read.
697#[cfg(feature = "wincode")]
698#[inline]
699pub fn deserialize(input: &[u8]) -> wincode::ReadResult<Message> {
700    wincode::deserialize(input)
701}
702
703#[cfg(test)]
704mod tests {
705    use {super::*, solana_sdk_ids::bpf_loader_upgradeable};
706
707    /// Builder for constructing V1 messages.
708    ///
709    /// This is used in tests to simplify message construction and validation. For
710    /// client code, users should construct messages using `try_compile` or
711    /// `try_compile_with_config`.
712    #[derive(Debug, Clone, Default)]
713    pub struct MessageBuilder {
714        header: MessageHeader,
715        config: TransactionConfig,
716        lifetime_specifier: Option<Hash>,
717        account_keys: Vec<Address>,
718        instructions: Vec<CompiledInstruction>,
719    }
720
721    impl MessageBuilder {
722        pub fn new() -> Self {
723            Self::default()
724        }
725
726        #[must_use]
727        pub fn required_signatures(mut self, count: u8) -> Self {
728            self.header.num_required_signatures = count;
729            self
730        }
731
732        #[must_use]
733        pub fn readonly_signed_accounts(mut self, count: u8) -> Self {
734            self.header.num_readonly_signed_accounts = count;
735            self
736        }
737
738        #[must_use]
739        pub fn readonly_unsigned_accounts(mut self, count: u8) -> Self {
740            self.header.num_readonly_unsigned_accounts = count;
741            self
742        }
743
744        #[must_use]
745        pub fn lifetime_specifier(mut self, hash: Hash) -> Self {
746            self.lifetime_specifier = Some(hash);
747            self
748        }
749
750        #[must_use]
751        pub fn config(mut self, config: TransactionConfig) -> Self {
752            self.config = config;
753            self
754        }
755
756        #[must_use]
757        pub fn priority_fee(mut self, fee: u64) -> Self {
758            self.config.priority_fee = Some(fee);
759            self
760        }
761
762        #[must_use]
763        pub fn compute_unit_limit(mut self, limit: u32) -> Self {
764            self.config.compute_unit_limit = Some(limit);
765            self
766        }
767
768        #[must_use]
769        pub fn loaded_accounts_data_size_limit(mut self, limit: u32) -> Self {
770            self.config.loaded_accounts_data_size_limit = Some(limit);
771            self
772        }
773
774        #[must_use]
775        pub fn heap_size(mut self, size: u32) -> Self {
776            self.config.heap_size = Some(size);
777            self
778        }
779
780        #[must_use]
781        pub fn account(mut self, key: Address) -> Self {
782            self.account_keys.push(key);
783            self
784        }
785
786        #[must_use]
787        pub fn accounts(mut self, keys: Vec<Address>) -> Self {
788            self.account_keys = keys;
789            self
790        }
791
792        #[must_use]
793        pub fn instruction(mut self, instruction: CompiledInstruction) -> Self {
794            self.instructions.push(instruction);
795            self
796        }
797
798        #[must_use]
799        pub fn instructions(mut self, instructions: Vec<CompiledInstruction>) -> Self {
800            self.instructions = instructions;
801            self
802        }
803
804        /// Build the message, validating all constraints.
805        pub fn build(self) -> Result<Message, MessageError> {
806            let lifetime_specifier = self
807                .lifetime_specifier
808                .ok_or(MessageError::MissingLifetimeSpecifier)?;
809
810            let message = Message::new(
811                self.header,
812                self.config,
813                lifetime_specifier,
814                self.account_keys,
815                self.instructions,
816            );
817
818            message.validate()?;
819
820            Ok(message)
821        }
822    }
823
824    fn create_test_message() -> Message {
825        MessageBuilder::new()
826            .required_signatures(1)
827            .readonly_unsigned_accounts(1)
828            .lifetime_specifier(Hash::new_unique())
829            .accounts(vec![
830                Address::new_unique(), // fee payer
831                Address::new_unique(), // program
832                Address::new_unique(), // readonly account
833            ])
834            .compute_unit_limit(200_000)
835            .instruction(CompiledInstruction {
836                program_id_index: 1,
837                accounts: vec![0, 2],
838                data: vec![1, 2, 3, 4],
839            })
840            .build()
841            .unwrap()
842    }
843
844    #[test]
845    fn fee_payer_returns_first_account() {
846        let fee_payer = Address::new_unique();
847        let message = MessageBuilder::new()
848            .required_signatures(1)
849            .lifetime_specifier(Hash::new_unique())
850            .accounts(vec![fee_payer, Address::new_unique()])
851            .build()
852            .unwrap();
853
854        assert_eq!(message.fee_payer(), Some(&fee_payer));
855    }
856
857    #[test]
858    fn fee_payer_returns_none_for_empty_accounts() {
859        // Direct construction to bypass builder validation
860        let message = Message::new(
861            MessageHeader::default(),
862            TransactionConfig::default(),
863            Hash::new_unique(),
864            vec![],
865            vec![],
866        );
867
868        assert_eq!(message.fee_payer(), None);
869    }
870
871    #[test]
872    fn is_signer_checks_signature_requirement() {
873        let message = create_test_message();
874        assert!(message.is_signer(0)); // Fee payer is signer
875        assert!(!message.is_signer(1)); // Program is not signer
876        assert!(!message.is_signer(2)); // Readonly account is not signer
877    }
878
879    #[test]
880    fn is_signer_writable_identifies_writable_signers() {
881        let message = MessageBuilder::new()
882            .required_signatures(3)
883            .readonly_signed_accounts(1) // Last signer is readonly
884            .lifetime_specifier(Hash::new_unique())
885            .accounts(vec![
886                Address::new_unique(), // 0: writable signer
887                Address::new_unique(), // 1: writable signer
888                Address::new_unique(), // 2: readonly signer
889                Address::new_unique(), // 3: non-signer
890            ])
891            .build()
892            .unwrap();
893
894        // Writable signers
895        assert!(message.is_signer_writable(0));
896        assert!(message.is_signer_writable(1));
897        // Readonly signer
898        assert!(!message.is_signer_writable(2));
899        // Non-signers
900        assert!(!message.is_signer_writable(3));
901        assert!(!message.is_signer_writable(100));
902    }
903
904    #[test]
905    fn is_signer_writable_all_writable_when_no_readonly() {
906        let message = MessageBuilder::new()
907            .required_signatures(2)
908            .readonly_signed_accounts(0) // All signers are writable
909            .lifetime_specifier(Hash::new_unique())
910            .accounts(vec![
911                Address::new_unique(),
912                Address::new_unique(),
913                Address::new_unique(),
914            ])
915            .build()
916            .unwrap();
917
918        assert!(message.is_signer_writable(0));
919        assert!(message.is_signer_writable(1));
920        assert!(!message.is_signer_writable(2)); // Not a signer
921    }
922
923    #[test]
924    fn is_key_called_as_program_detects_program_indices() {
925        let message = create_test_message();
926        // program_id_index = 1 in create_test_message
927        assert!(message.is_key_called_as_program(1));
928        assert!(!message.is_key_called_as_program(0));
929        assert!(!message.is_key_called_as_program(2));
930        // Index > u8::MAX can't match any program_id_index
931        assert!(!message.is_key_called_as_program(256));
932        assert!(!message.is_key_called_as_program(10_000));
933    }
934
935    #[test]
936    fn is_upgradeable_loader_present_detects_loader() {
937        let message = create_test_message();
938        assert!(!message.is_upgradeable_loader_present());
939
940        let mut message_with_loader = create_test_message();
941        message_with_loader
942            .account_keys
943            .push(bpf_loader_upgradeable::id());
944        assert!(message_with_loader.is_upgradeable_loader_present());
945    }
946
947    #[test]
948    fn is_writable_index_respects_header_layout() {
949        let message = create_test_message();
950        // Account layout: [writable signer (fee payer), writable unsigned (program), readonly unsigned]
951        assert!(message.is_writable_index(0)); // Fee payer is writable
952        assert!(message.is_writable_index(1)); // Program position is writable unsigned
953        assert!(!message.is_writable_index(2)); // Last account is readonly
954    }
955
956    #[test]
957    fn is_writable_index_handles_mixed_signer_permissions() {
958        let mut message = create_test_message();
959        // 2 signers: first writable, second readonly
960        message.header.num_required_signatures = 2;
961        message.header.num_readonly_signed_accounts = 1;
962        message.header.num_readonly_unsigned_accounts = 1;
963        message.account_keys = vec![
964            Address::new_unique(), // writable signer
965            Address::new_unique(), // readonly signer
966            Address::new_unique(), // readonly unsigned
967        ];
968        message.instructions[0].program_id_index = 2;
969        message.instructions[0].accounts = vec![0, 1];
970
971        assert!(message.sanitize().is_ok());
972        assert!(message.is_writable_index(0)); // writable signer
973        assert!(!message.is_writable_index(1)); // readonly signer
974        assert!(!message.is_writable_index(2)); // readonly unsigned
975        assert!(!message.is_writable_index(999)); // out of bounds
976    }
977
978    #[test]
979    fn is_maybe_writable_returns_false_for_readonly_index() {
980        let message = create_test_message();
981        // Index 2 is readonly unsigned
982        assert!(!message.is_writable_index(2));
983        assert!(!message.is_maybe_writable(2, None));
984        // Even with empty reserved set
985        assert!(!message.is_maybe_writable(2, Some(&HashSet::new())));
986    }
987
988    #[test]
989    fn is_maybe_writable_demotes_reserved_accounts() {
990        let message = create_test_message();
991        let reserved = HashSet::from([message.account_keys[0]]);
992        // Fee payer is writable by index, but reserved → demoted
993        assert!(message.is_writable_index(0));
994        assert!(!message.is_maybe_writable(0, Some(&reserved)));
995    }
996
997    #[test]
998    fn is_maybe_writable_demotes_programs_without_upgradeable_loader() {
999        let message = create_test_message();
1000        // Index 1 is writable unsigned, called as program, no upgradeable loader
1001        assert!(message.is_writable_index(1));
1002        assert!(message.is_key_called_as_program(1));
1003        assert!(!message.is_upgradeable_loader_present());
1004        assert!(!message.is_maybe_writable(1, None));
1005    }
1006
1007    #[test]
1008    fn is_maybe_writable_preserves_programs_with_upgradeable_loader() {
1009        let mut message = create_test_message();
1010        // Add upgradeable loader to account keys
1011        message.account_keys.push(bpf_loader_upgradeable::id());
1012
1013        assert!(message.sanitize().is_ok());
1014        assert!(message.is_writable_index(1));
1015        assert!(message.is_key_called_as_program(1));
1016        assert!(message.is_upgradeable_loader_present());
1017        // Program not demoted because upgradeable loader is present
1018        assert!(message.is_maybe_writable(1, None));
1019    }
1020
1021    #[test]
1022    fn sanitize_accepts_valid_message() {
1023        let message = create_test_message();
1024        assert!(message.sanitize().is_ok());
1025    }
1026
1027    #[test]
1028    fn sanitize_rejects_zero_signers() {
1029        let mut message = create_test_message();
1030        message.header.num_required_signatures = 0;
1031        assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1032    }
1033
1034    #[test]
1035    fn sanitize_rejects_over_12_signatures() {
1036        let mut message = create_test_message();
1037        message.header.num_required_signatures = MAX_SIGNATURES + 1;
1038        message.account_keys = (0..MAX_SIGNATURES + 1)
1039            .map(|_| Address::new_unique())
1040            .collect();
1041        assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1042    }
1043
1044    #[test]
1045    fn sanitize_rejects_over_64_addresses() {
1046        let mut message = create_test_message();
1047        message.account_keys = (0..65).map(|_| Address::new_unique()).collect();
1048        assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1049    }
1050
1051    #[test]
1052    fn sanitize_rejects_over_64_instructions() {
1053        let mut message = create_test_message();
1054        message.instructions = (0..65) // exceeds 64 max
1055            .map(|_| CompiledInstruction {
1056                program_id_index: 1,
1057                accounts: vec![0],
1058                data: vec![],
1059            })
1060            .collect();
1061        assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1062    }
1063
1064    #[test]
1065    fn sanitize_rejects_insufficient_accounts_for_header() {
1066        let mut message = create_test_message();
1067        // min_accounts = num_required_signatures + num_readonly_unsigned_accounts
1068        // Set readonly_unsigned high so min_accounts > account_keys.len()
1069        message.header.num_readonly_unsigned_accounts = 10;
1070        assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1071    }
1072
1073    #[test]
1074    fn sanitize_rejects_all_signers_readonly() {
1075        let mut message = create_test_message();
1076        message.header.num_readonly_signed_accounts = 1; // All signers readonly
1077        assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1078    }
1079
1080    #[test]
1081    fn sanitize_rejects_duplicate_addresses() {
1082        let mut message = create_test_message();
1083        let dup = message.account_keys[0];
1084        message.account_keys[1] = dup;
1085        assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1086    }
1087
1088    #[test]
1089    fn sanitize_rejects_unaligned_heap_size() {
1090        let mut message = create_test_message();
1091        message.config.heap_size = Some(1025); // Not a multiple of 1024
1092        assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1093    }
1094
1095    #[test]
1096    fn sanitize_rejects_heap_size_below_minimum() {
1097        let mut message = create_test_message();
1098        message.config.heap_size = Some(MIN_HEAP_SIZE - 1);
1099        assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1100    }
1101
1102    #[test]
1103    fn sanitize_rejects_heap_size_above_maximum() {
1104        let mut message = create_test_message();
1105        message.config.heap_size = Some(MAX_HEAP_SIZE + 1);
1106        assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1107    }
1108
1109    #[test]
1110    fn sanitize_accepts_minimum_heap_size() {
1111        let mut message = create_test_message();
1112        message.config.heap_size = Some(MIN_HEAP_SIZE);
1113        assert!(message.sanitize().is_ok());
1114    }
1115
1116    #[test]
1117    fn sanitize_accepts_maximum_heap_size() {
1118        let mut message = create_test_message();
1119        message.config.heap_size = Some(MAX_HEAP_SIZE);
1120        assert!(message.sanitize().is_ok());
1121    }
1122
1123    #[test]
1124    fn sanitize_accepts_aligned_heap_size() {
1125        let mut message = create_test_message();
1126        message.config.heap_size = Some(65536); // 64KB, valid
1127        assert!(message.sanitize().is_ok());
1128    }
1129
1130    #[test]
1131    fn sanitize_rejects_invalid_program_id_index() {
1132        let mut message = create_test_message();
1133        message.instructions[0].program_id_index = 99;
1134        assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1135    }
1136
1137    #[test]
1138    fn sanitize_rejects_fee_payer_as_program() {
1139        let mut message = create_test_message();
1140        message.instructions[0].program_id_index = 0;
1141        assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1142    }
1143
1144    #[test]
1145    fn sanitize_rejects_instruction_with_too_many_accounts() {
1146        let mut message = create_test_message();
1147        message.instructions[0].accounts = vec![0u8; (u8::MAX as usize) + 1];
1148        assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1149    }
1150
1151    #[test]
1152    fn sanitize_rejects_invalid_instruction_account_index() {
1153        let mut message = create_test_message();
1154        message.instructions[0].accounts = vec![0, 99]; // 99 is out of bounds
1155        assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1156    }
1157
1158    #[test]
1159    fn sanitize_accepts_64_addresses() {
1160        let mut message = create_test_message();
1161        message.account_keys = (0..MAX_ADDRESSES).map(|_| Address::new_unique()).collect();
1162        message.header.num_required_signatures = 1;
1163        message.header.num_readonly_signed_accounts = 0;
1164        message.header.num_readonly_unsigned_accounts = 1;
1165        message.instructions[0].program_id_index = 1;
1166        message.instructions[0].accounts = vec![0, 2];
1167        assert!(message.sanitize().is_ok());
1168    }
1169
1170    #[test]
1171    fn sanitize_accepts_64_instructions() {
1172        let mut message = create_test_message();
1173        message.instructions = (0..MAX_INSTRUCTIONS)
1174            .map(|_| CompiledInstruction {
1175                program_id_index: 1,
1176                accounts: vec![0, 2],
1177                data: vec![1, 2, 3],
1178            })
1179            .collect();
1180        assert!(message.sanitize().is_ok());
1181    }
1182
1183    #[test]
1184    fn size_matches_serialized_length() {
1185        let test_cases = [
1186            // Minimal message
1187            MessageBuilder::new()
1188                .required_signatures(1)
1189                .lifetime_specifier(Hash::new_unique())
1190                .accounts(vec![Address::new_unique()])
1191                .build()
1192                .unwrap(),
1193            // With config
1194            MessageBuilder::new()
1195                .required_signatures(1)
1196                .lifetime_specifier(Hash::new_unique())
1197                .accounts(vec![Address::new_unique(), Address::new_unique()])
1198                .priority_fee(1000)
1199                .compute_unit_limit(200_000)
1200                .instruction(CompiledInstruction {
1201                    program_id_index: 1,
1202                    accounts: vec![0],
1203                    data: vec![1, 2, 3, 4],
1204                })
1205                .build()
1206                .unwrap(),
1207            // Multiple instructions with varying data
1208            MessageBuilder::new()
1209                .required_signatures(2)
1210                .readonly_signed_accounts(1)
1211                .readonly_unsigned_accounts(1)
1212                .lifetime_specifier(Hash::new_unique())
1213                .accounts(vec![
1214                    Address::new_unique(),
1215                    Address::new_unique(),
1216                    Address::new_unique(),
1217                    Address::new_unique(),
1218                ])
1219                .heap_size(65536)
1220                .instructions(vec![
1221                    CompiledInstruction {
1222                        program_id_index: 2,
1223                        accounts: vec![0, 1],
1224                        data: vec![],
1225                    },
1226                    CompiledInstruction {
1227                        program_id_index: 3,
1228                        accounts: vec![0, 1, 2],
1229                        data: vec![0xAA; 100],
1230                    },
1231                ])
1232                .build()
1233                .unwrap(),
1234        ];
1235
1236        for message in &test_cases {
1237            assert_eq!(message.size(), wincode::serialize(message).unwrap().len());
1238        }
1239    }
1240
1241    #[test]
1242    fn byte_layout_without_config() {
1243        let fee_payer = Address::new_from_array([1u8; 32]);
1244        let program = Address::new_from_array([2u8; 32]);
1245        let blockhash = Hash::new_from_array([0xAB; 32]);
1246
1247        let message = MessageBuilder::new()
1248            .required_signatures(1)
1249            .lifetime_specifier(blockhash)
1250            .accounts(vec![fee_payer, program])
1251            .instruction(CompiledInstruction {
1252                program_id_index: 1,
1253                accounts: vec![0],
1254                data: vec![0xDE, 0xAD],
1255            })
1256            .build()
1257            .unwrap();
1258
1259        let bytes = wincode::serialize(&message).unwrap();
1260
1261        // Build expected bytes manually per SIMD-0385
1262        //
1263        // num_required_signatures
1264        // num_readonly_signed_accounts
1265        // num_readonly_unsigned_accounts
1266        let mut expected = vec![1, 0, 0];
1267        expected.extend_from_slice(&0u32.to_le_bytes()); // ConfigMask = 0
1268        expected.extend_from_slice(&[0xAB; 32]); // LifetimeSpecifier
1269        expected.push(1); // NumInstructions
1270        expected.push(2); // NumAddresses
1271        expected.extend_from_slice(&[1u8; 32]); // fee_payer
1272        expected.extend_from_slice(&[2u8; 32]); // program
1273                                                // ConfigValues: none
1274        expected.push(1); // program_id_index
1275        expected.push(1); // num_accounts
1276        expected.extend_from_slice(&2u16.to_le_bytes()); // data_len
1277        expected.push(0); // account index 0
1278        expected.extend_from_slice(&[0xDE, 0xAD]); // data
1279        assert_eq!(bytes, expected);
1280    }
1281
1282    #[test]
1283    fn byte_layout_with_config() {
1284        let fee_payer = Address::new_from_array([1u8; 32]);
1285        let program = Address::new_from_array([2u8; 32]);
1286        let blockhash = Hash::new_from_array([0xBB; 32]);
1287
1288        let message = MessageBuilder::new()
1289            .required_signatures(1)
1290            .lifetime_specifier(blockhash)
1291            .accounts(vec![fee_payer, program])
1292            .priority_fee(0x0102030405060708u64)
1293            .compute_unit_limit(0x11223344u32)
1294            .instruction(CompiledInstruction {
1295                program_id_index: 1,
1296                accounts: vec![],
1297                data: vec![],
1298            })
1299            .build()
1300            .unwrap();
1301
1302        let bytes = wincode::serialize(&message).unwrap();
1303
1304        let mut expected = vec![1, 0, 0];
1305        // ConfigMask: priority fee (bits 0,1) + CU limit (bit 2) = 0b111 = 7
1306        expected.extend_from_slice(&7u32.to_le_bytes());
1307        expected.extend_from_slice(&[0xBB; 32]);
1308        expected.push(1);
1309        expected.push(2);
1310        expected.extend_from_slice(&[1u8; 32]);
1311        expected.extend_from_slice(&[2u8; 32]);
1312        // Priority fee as u64 LE
1313        expected.extend_from_slice(&0x0102030405060708u64.to_le_bytes());
1314        // Compute unit limit as u32 LE
1315        expected.extend_from_slice(&0x11223344u32.to_le_bytes());
1316        expected.push(1); // program_id_index
1317        expected.push(0); // num_accounts
1318        expected.extend_from_slice(&0u16.to_le_bytes()); // data_len
1319
1320        assert_eq!(bytes, expected);
1321    }
1322
1323    #[test]
1324    fn roundtrip_preserves_all_config_fields() {
1325        let message = MessageBuilder::new()
1326            .required_signatures(1)
1327            .lifetime_specifier(Hash::new_unique())
1328            .accounts(vec![Address::new_unique(), Address::new_unique()])
1329            .priority_fee(1000)
1330            .compute_unit_limit(200_000)
1331            .loaded_accounts_data_size_limit(1_000_000)
1332            .heap_size(65536)
1333            .instruction(CompiledInstruction {
1334                program_id_index: 1,
1335                accounts: vec![0],
1336                data: vec![],
1337            })
1338            .build()
1339            .unwrap();
1340
1341        let serialized = wincode::serialize(&message).unwrap();
1342        let deserialized = deserialize(&serialized).unwrap();
1343        assert_eq!(message.config, deserialized.config);
1344    }
1345}