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