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,
60            MAX_INSTRUCTIONS, MAX_SIGNATURES,
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    /// Calculate the serialized size of the message in bytes.
395    #[allow(clippy::arithmetic_side_effects)]
396    #[inline(always)]
397    pub fn size(&self) -> usize {
398        size_of::<MessageHeader>()                           // legacy header
399            + size_of::<TransactionConfigMask>()             // config mask
400            + size_of::<Hash>()                              // lifetime specifier
401            + size_of::<u8>()                                // number of instructions
402            + size_of::<u8>()                                // number of addresses
403            + self.account_keys.len() * size_of::<Address>() // addresses
404            + self.config.size()                             // config values
405            + self.instructions.len()
406                * (
407                    size_of::<u8>()
408                    + size_of::<u8>()
409                    + size_of::<u16>()
410                )                                            // instruction headers
411            + self
412                .instructions
413                .iter()
414                .map(|ix| {
415                    (ix.accounts.len() * size_of::<u8>())
416                    + ix.data.len()
417                })
418                .sum::<usize>() // instruction payloads
419    }
420
421    pub fn validate(&self) -> Result<(), MessageError> {
422        // `num_required_signatures` <= 12
423        if self.header.num_required_signatures > MAX_SIGNATURES {
424            return Err(MessageError::TooManySignatures);
425        }
426
427        // `num_instructions` <= 64
428        if self.instructions.len() > MAX_INSTRUCTIONS as usize {
429            return Err(MessageError::TooManyInstructions);
430        }
431
432        let num_account_keys = self.account_keys.len();
433
434        // `num_addresses` <= 64
435        if num_account_keys > MAX_ADDRESSES as usize {
436            return Err(MessageError::TooManyAddresses);
437        }
438
439        // `num_addresses` >= `num_required_signatures` + `num_readonly_unsigned_accounts`
440        let min_accounts = usize::from(self.header.num_required_signatures)
441            .saturating_add(usize::from(self.header.num_readonly_unsigned_accounts));
442
443        if num_account_keys < min_accounts {
444            return Err(MessageError::NotEnoughAddressesForSignatures);
445        }
446
447        // must have at least 1 RW fee-payer (`num_readonly_signed` < `num_required_signatures`)
448        if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures {
449            return Err(MessageError::ZeroSigners);
450        }
451
452        // no duplicate addresses
453        let unique_keys: HashSet<_> = self.account_keys.iter().collect();
454        if unique_keys.len() != num_account_keys {
455            return Err(MessageError::DuplicateAddresses);
456        }
457
458        // validate config mask (2-bit fields must have both bits set or neither)
459        let mask: TransactionConfigMask = self.config.into();
460
461        if mask.has_invalid_priority_fee_bits() {
462            return Err(MessageError::InvalidConfigMask);
463        }
464
465        // heap size must be a multiple of 1024
466        if let Some(heap_size) = self.config.heap_size {
467            if heap_size % 1024 != 0 {
468                return Err(MessageError::InvalidHeapSize);
469            }
470        }
471
472        // instruction account indices must be < `num_addresses`
473        let max_account_index = num_account_keys
474            .checked_sub(1)
475            .ok_or(MessageError::NotEnoughAccountKeys)?;
476
477        for instruction in &self.instructions {
478            // program id must be in static accounts
479            if usize::from(instruction.program_id_index) > max_account_index {
480                return Err(MessageError::InvalidInstructionAccountIndex);
481            }
482
483            // program cannot be fee payer
484            if instruction.program_id_index == 0 {
485                return Err(MessageError::InvalidInstructionAccountIndex);
486            }
487
488            // instruction accounts count must fit in u8
489            if instruction.accounts.len() > u8::MAX as usize {
490                return Err(MessageError::InstructionAccountsTooLarge);
491            }
492
493            // instruction data length must fit in u16
494            if instruction.data.len() > u16::MAX as usize {
495                return Err(MessageError::InstructionDataTooLarge);
496            }
497
498            // all account indices must be valid
499            for &account_index in &instruction.accounts {
500                if usize::from(account_index) > max_account_index {
501                    return Err(MessageError::InvalidInstructionAccountIndex);
502                }
503            }
504        }
505
506        Ok(())
507    }
508}
509
510impl Sanitize for Message {
511    fn sanitize(&self) -> Result<(), SanitizeError> {
512        Ok(self.validate()?)
513    }
514}
515
516#[cfg(feature = "wincode")]
517unsafe impl<C: ConfigCore> SchemaWrite<C> for Message {
518    type Src = Self;
519
520    #[inline(always)]
521    fn size_of(src: &Self::Src) -> WriteResult<usize> {
522        Ok(src.size())
523    }
524
525    fn write(mut writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
526        // SAFETY: `Message::size()` yields the exact number of bytes to be written.
527        let mut writer = unsafe { writer.as_trusted_for(src.size()) }?;
528        writer.write(&[
529            src.header.num_required_signatures,
530            src.header.num_readonly_signed_accounts,
531            src.header.num_readonly_unsigned_accounts,
532        ])?;
533        let mask = TransactionConfigMask::from(&src.config).0.to_le_bytes();
534        writer.write(&mask)?;
535        writer.write(src.lifetime_specifier.as_bytes())?;
536        writer.write(&[src.instructions.len() as u8, src.account_keys.len() as u8])?;
537
538        // SAFETY: `Address` is `#[repr(transparent)]` over `[u8; 32]`, so it is safe to
539        // treat as bytes.
540        #[expect(clippy::arithmetic_side_effects)]
541        let account_keys = unsafe {
542            from_raw_parts(
543                src.account_keys.as_ptr().cast::<u8>(),
544                src.account_keys.len() * size_of::<Address>(),
545            )
546        };
547        writer.write(account_keys)?;
548
549        if let Some(value) = src.config.priority_fee {
550            writer.write(&value.to_le_bytes())?;
551        }
552        if let Some(value) = src.config.compute_unit_limit {
553            writer.write(&value.to_le_bytes())?;
554        }
555        if let Some(value) = src.config.loaded_accounts_data_size_limit {
556            writer.write(&value.to_le_bytes())?;
557        }
558        if let Some(value) = src.config.heap_size {
559            writer.write(&value.to_le_bytes())?;
560        }
561
562        for ix in &src.instructions {
563            writer.write(&[ix.program_id_index, ix.accounts.len() as u8])?;
564            writer.write(&(ix.data.len() as u16).to_le_bytes())?;
565        }
566
567        for ix in &src.instructions {
568            writer.write(&ix.accounts)?;
569            writer.write(&ix.data)?;
570        }
571
572        writer.finish()?;
573
574        Ok(())
575    }
576}
577
578/// Serialize the message.
579#[cfg(feature = "wincode")]
580#[inline]
581pub fn serialize(message: &Message) -> Vec<u8> {
582    wincode::serialize(message).unwrap()
583}
584
585#[cfg(feature = "wincode")]
586unsafe impl<'de, C: Config> SchemaRead<'de, C> for Message {
587    type Dst = Message;
588
589    #[expect(clippy::arithmetic_side_effects)]
590    fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
591        let (header, lifetime_specifier, config_mask, num_instructions, num_addresses) = {
592            // SAFETY: the following reads consume exactly `FIXED_HEADER_SIZE` bytes.
593            // - MessageHeader (3 bytes)
594            // - TransactionConfigMask (4 bytes)
595            // - Hash (32 bytes)
596            // - num_instructions (1 byte)
597            // - num_addresses (1 byte)
598            let mut reader = unsafe { reader.as_trusted_for(FIXED_HEADER_SIZE)? };
599            let header = <MessageHeader as SchemaRead<C>>::get(reader.by_ref())?;
600            let config_mask = TransactionConfigMask(u32::from_le_bytes(reader.take_array()?));
601            let lifetime_specifier = <Hash as SchemaRead<C>>::get(reader.by_ref())?;
602            let num_instructions = reader.take_byte()? as usize;
603            let num_addresses = reader.take_byte()? as usize;
604            (
605                header,
606                lifetime_specifier,
607                config_mask,
608                num_instructions,
609                num_addresses,
610            )
611        };
612
613        <C::LengthEncoding as SeqLen<C>>::prealloc_check::<Address>(num_addresses)?;
614        let account_keys = <Vec<Address> as SchemaReadContext<C, context::Len>>::get_with_context(
615            context::Len(num_addresses),
616            reader.by_ref(),
617        )?;
618
619        let mut config = TransactionConfig::empty();
620        if config_mask.has_priority_fee() {
621            config.priority_fee = Some(u64::from_le_bytes(reader.take_array()?));
622        }
623        if config_mask.has_compute_unit_limit() {
624            config.compute_unit_limit = Some(u32::from_le_bytes(reader.take_array()?));
625        }
626        if config_mask.has_loaded_accounts_data_size() {
627            config.loaded_accounts_data_size_limit = Some(u32::from_le_bytes(reader.take_array()?));
628        }
629        if config_mask.has_heap_size() {
630            config.heap_size = Some(u32::from_le_bytes(reader.take_array()?));
631        }
632
633        // SAFETY:
634        // - `take_borrowed(num_instructions * size_of::<InstructionHeader>())` returns
635        //   exactly the requested number of bytes, or errors.
636        // - `take_borrowed` returns a stable borrow from the backing buffer, so the
637        //   resulting slice remains valid across subsequent reader operations.
638        // - `InstructionHeader` has alignment 1 and is encoded exactly as
639        //   4 bytes `(u8, u8, [u8; 2])`.
640        let instruction_headers = unsafe {
641            from_raw_parts(
642                reader
643                    .take_borrowed(num_instructions * size_of::<InstructionHeader>())?
644                    .as_ptr() as *const InstructionHeader,
645                num_instructions,
646            )
647        };
648        let mut instructions = Vec::with_capacity(num_instructions);
649        for header in instruction_headers {
650            let program_id_index = header.0;
651            let num_accounts = header.1 as usize;
652            let data_len = u16::from_le_bytes(header.2) as usize;
653
654            <C::LengthEncoding as SeqLen<C>>::prealloc_check::<u8>(num_accounts)?;
655            let accounts = <Vec<u8> as SchemaReadContext<C, context::Len>>::get_with_context(
656                context::Len(num_accounts),
657                reader.by_ref(),
658            )?;
659            <C::LengthEncoding as SeqLen<C>>::prealloc_check::<u8>(data_len)?;
660            let data = <Vec<u8> as SchemaReadContext<C, context::Len>>::get_with_context(
661                context::Len(data_len),
662                reader.by_ref(),
663            )?;
664
665            instructions.push(CompiledInstruction {
666                program_id_index,
667                accounts,
668                data,
669            });
670        }
671
672        dst.write(Message {
673            header,
674            lifetime_specifier,
675            config,
676            account_keys,
677            instructions,
678        });
679
680        Ok(())
681    }
682}
683
684/// Deserialize the message from the provided input buffer, returning the message and
685/// the number of bytes read.
686#[cfg(feature = "wincode")]
687#[inline]
688pub fn deserialize(input: &[u8]) -> wincode::ReadResult<Message> {
689    wincode::deserialize(input)
690}
691
692#[cfg(test)]
693mod tests {
694    use {super::*, solana_sdk_ids::bpf_loader_upgradeable};
695
696    /// Builder for constructing V1 messages.
697    ///
698    /// This is used in tests to simplify message construction and validation. For
699    /// client code, users should construct messages using `try_compile` or
700    /// `try_compile_with_config`.
701    #[derive(Debug, Clone, Default)]
702    pub struct MessageBuilder {
703        header: MessageHeader,
704        config: TransactionConfig,
705        lifetime_specifier: Option<Hash>,
706        account_keys: Vec<Address>,
707        instructions: Vec<CompiledInstruction>,
708    }
709
710    impl MessageBuilder {
711        pub fn new() -> Self {
712            Self::default()
713        }
714
715        #[must_use]
716        pub fn required_signatures(mut self, count: u8) -> Self {
717            self.header.num_required_signatures = count;
718            self
719        }
720
721        #[must_use]
722        pub fn readonly_signed_accounts(mut self, count: u8) -> Self {
723            self.header.num_readonly_signed_accounts = count;
724            self
725        }
726
727        #[must_use]
728        pub fn readonly_unsigned_accounts(mut self, count: u8) -> Self {
729            self.header.num_readonly_unsigned_accounts = count;
730            self
731        }
732
733        #[must_use]
734        pub fn lifetime_specifier(mut self, hash: Hash) -> Self {
735            self.lifetime_specifier = Some(hash);
736            self
737        }
738
739        #[must_use]
740        pub fn config(mut self, config: TransactionConfig) -> Self {
741            self.config = config;
742            self
743        }
744
745        #[must_use]
746        pub fn priority_fee(mut self, fee: u64) -> Self {
747            self.config.priority_fee = Some(fee);
748            self
749        }
750
751        #[must_use]
752        pub fn compute_unit_limit(mut self, limit: u32) -> Self {
753            self.config.compute_unit_limit = Some(limit);
754            self
755        }
756
757        #[must_use]
758        pub fn loaded_accounts_data_size_limit(mut self, limit: u32) -> Self {
759            self.config.loaded_accounts_data_size_limit = Some(limit);
760            self
761        }
762
763        #[must_use]
764        pub fn heap_size(mut self, size: u32) -> Self {
765            self.config.heap_size = Some(size);
766            self
767        }
768
769        #[must_use]
770        pub fn account(mut self, key: Address) -> Self {
771            self.account_keys.push(key);
772            self
773        }
774
775        #[must_use]
776        pub fn accounts(mut self, keys: Vec<Address>) -> Self {
777            self.account_keys = keys;
778            self
779        }
780
781        #[must_use]
782        pub fn instruction(mut self, instruction: CompiledInstruction) -> Self {
783            self.instructions.push(instruction);
784            self
785        }
786
787        #[must_use]
788        pub fn instructions(mut self, instructions: Vec<CompiledInstruction>) -> Self {
789            self.instructions = instructions;
790            self
791        }
792
793        /// Build the message, validating all constraints.
794        pub fn build(self) -> Result<Message, MessageError> {
795            let lifetime_specifier = self
796                .lifetime_specifier
797                .ok_or(MessageError::MissingLifetimeSpecifier)?;
798
799            let message = Message::new(
800                self.header,
801                self.config,
802                lifetime_specifier,
803                self.account_keys,
804                self.instructions,
805            );
806
807            message.validate()?;
808
809            Ok(message)
810        }
811    }
812
813    fn create_test_message() -> Message {
814        MessageBuilder::new()
815            .required_signatures(1)
816            .readonly_unsigned_accounts(1)
817            .lifetime_specifier(Hash::new_unique())
818            .accounts(vec![
819                Address::new_unique(), // fee payer
820                Address::new_unique(), // program
821                Address::new_unique(), // readonly account
822            ])
823            .compute_unit_limit(200_000)
824            .instruction(CompiledInstruction {
825                program_id_index: 1,
826                accounts: vec![0, 2],
827                data: vec![1, 2, 3, 4],
828            })
829            .build()
830            .unwrap()
831    }
832
833    #[test]
834    fn fee_payer_returns_first_account() {
835        let fee_payer = Address::new_unique();
836        let message = MessageBuilder::new()
837            .required_signatures(1)
838            .lifetime_specifier(Hash::new_unique())
839            .accounts(vec![fee_payer, Address::new_unique()])
840            .build()
841            .unwrap();
842
843        assert_eq!(message.fee_payer(), Some(&fee_payer));
844    }
845
846    #[test]
847    fn fee_payer_returns_none_for_empty_accounts() {
848        // Direct construction to bypass builder validation
849        let message = Message::new(
850            MessageHeader::default(),
851            TransactionConfig::default(),
852            Hash::new_unique(),
853            vec![],
854            vec![],
855        );
856
857        assert_eq!(message.fee_payer(), None);
858    }
859
860    #[test]
861    fn is_signer_checks_signature_requirement() {
862        let message = create_test_message();
863        assert!(message.is_signer(0)); // Fee payer is signer
864        assert!(!message.is_signer(1)); // Program is not signer
865        assert!(!message.is_signer(2)); // Readonly account is not signer
866    }
867
868    #[test]
869    fn is_signer_writable_identifies_writable_signers() {
870        let message = MessageBuilder::new()
871            .required_signatures(3)
872            .readonly_signed_accounts(1) // Last signer is readonly
873            .lifetime_specifier(Hash::new_unique())
874            .accounts(vec![
875                Address::new_unique(), // 0: writable signer
876                Address::new_unique(), // 1: writable signer
877                Address::new_unique(), // 2: readonly signer
878                Address::new_unique(), // 3: non-signer
879            ])
880            .build()
881            .unwrap();
882
883        // Writable signers
884        assert!(message.is_signer_writable(0));
885        assert!(message.is_signer_writable(1));
886        // Readonly signer
887        assert!(!message.is_signer_writable(2));
888        // Non-signers
889        assert!(!message.is_signer_writable(3));
890        assert!(!message.is_signer_writable(100));
891    }
892
893    #[test]
894    fn is_signer_writable_all_writable_when_no_readonly() {
895        let message = MessageBuilder::new()
896            .required_signatures(2)
897            .readonly_signed_accounts(0) // All signers are writable
898            .lifetime_specifier(Hash::new_unique())
899            .accounts(vec![
900                Address::new_unique(),
901                Address::new_unique(),
902                Address::new_unique(),
903            ])
904            .build()
905            .unwrap();
906
907        assert!(message.is_signer_writable(0));
908        assert!(message.is_signer_writable(1));
909        assert!(!message.is_signer_writable(2)); // Not a signer
910    }
911
912    #[test]
913    fn is_key_called_as_program_detects_program_indices() {
914        let message = create_test_message();
915        // program_id_index = 1 in create_test_message
916        assert!(message.is_key_called_as_program(1));
917        assert!(!message.is_key_called_as_program(0));
918        assert!(!message.is_key_called_as_program(2));
919        // Index > u8::MAX can't match any program_id_index
920        assert!(!message.is_key_called_as_program(256));
921        assert!(!message.is_key_called_as_program(10_000));
922    }
923
924    #[test]
925    fn is_upgradeable_loader_present_detects_loader() {
926        let message = create_test_message();
927        assert!(!message.is_upgradeable_loader_present());
928
929        let mut message_with_loader = create_test_message();
930        message_with_loader
931            .account_keys
932            .push(bpf_loader_upgradeable::id());
933        assert!(message_with_loader.is_upgradeable_loader_present());
934    }
935
936    #[test]
937    fn is_writable_index_respects_header_layout() {
938        let message = create_test_message();
939        // Account layout: [writable signer (fee payer), writable unsigned (program), readonly unsigned]
940        assert!(message.is_writable_index(0)); // Fee payer is writable
941        assert!(message.is_writable_index(1)); // Program position is writable unsigned
942        assert!(!message.is_writable_index(2)); // Last account is readonly
943    }
944
945    #[test]
946    fn is_writable_index_handles_mixed_signer_permissions() {
947        let mut message = create_test_message();
948        // 2 signers: first writable, second readonly
949        message.header.num_required_signatures = 2;
950        message.header.num_readonly_signed_accounts = 1;
951        message.header.num_readonly_unsigned_accounts = 1;
952        message.account_keys = vec![
953            Address::new_unique(), // writable signer
954            Address::new_unique(), // readonly signer
955            Address::new_unique(), // readonly unsigned
956        ];
957        message.instructions[0].program_id_index = 2;
958        message.instructions[0].accounts = vec![0, 1];
959
960        assert!(message.sanitize().is_ok());
961        assert!(message.is_writable_index(0)); // writable signer
962        assert!(!message.is_writable_index(1)); // readonly signer
963        assert!(!message.is_writable_index(2)); // readonly unsigned
964        assert!(!message.is_writable_index(999)); // out of bounds
965    }
966
967    #[test]
968    fn is_maybe_writable_returns_false_for_readonly_index() {
969        let message = create_test_message();
970        // Index 2 is readonly unsigned
971        assert!(!message.is_writable_index(2));
972        assert!(!message.is_maybe_writable(2, None));
973        // Even with empty reserved set
974        assert!(!message.is_maybe_writable(2, Some(&HashSet::new())));
975    }
976
977    #[test]
978    fn is_maybe_writable_demotes_reserved_accounts() {
979        let message = create_test_message();
980        let reserved = HashSet::from([message.account_keys[0]]);
981        // Fee payer is writable by index, but reserved → demoted
982        assert!(message.is_writable_index(0));
983        assert!(!message.is_maybe_writable(0, Some(&reserved)));
984    }
985
986    #[test]
987    fn is_maybe_writable_demotes_programs_without_upgradeable_loader() {
988        let message = create_test_message();
989        // Index 1 is writable unsigned, called as program, no upgradeable loader
990        assert!(message.is_writable_index(1));
991        assert!(message.is_key_called_as_program(1));
992        assert!(!message.is_upgradeable_loader_present());
993        assert!(!message.is_maybe_writable(1, None));
994    }
995
996    #[test]
997    fn is_maybe_writable_preserves_programs_with_upgradeable_loader() {
998        let mut message = create_test_message();
999        // Add upgradeable loader to account keys
1000        message.account_keys.push(bpf_loader_upgradeable::id());
1001
1002        assert!(message.sanitize().is_ok());
1003        assert!(message.is_writable_index(1));
1004        assert!(message.is_key_called_as_program(1));
1005        assert!(message.is_upgradeable_loader_present());
1006        // Program not demoted because upgradeable loader is present
1007        assert!(message.is_maybe_writable(1, None));
1008    }
1009
1010    #[test]
1011    fn sanitize_accepts_valid_message() {
1012        let message = create_test_message();
1013        assert!(message.sanitize().is_ok());
1014    }
1015
1016    #[test]
1017    fn sanitize_rejects_zero_signers() {
1018        let mut message = create_test_message();
1019        message.header.num_required_signatures = 0;
1020        assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1021    }
1022
1023    #[test]
1024    fn sanitize_rejects_over_12_signatures() {
1025        let mut message = create_test_message();
1026        message.header.num_required_signatures = MAX_SIGNATURES + 1;
1027        message.account_keys = (0..MAX_SIGNATURES + 1)
1028            .map(|_| Address::new_unique())
1029            .collect();
1030        assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1031    }
1032
1033    #[test]
1034    fn sanitize_rejects_over_64_addresses() {
1035        let mut message = create_test_message();
1036        message.account_keys = (0..65).map(|_| Address::new_unique()).collect();
1037        assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1038    }
1039
1040    #[test]
1041    fn sanitize_rejects_over_64_instructions() {
1042        let mut message = create_test_message();
1043        message.instructions = (0..65) // exceeds 64 max
1044            .map(|_| CompiledInstruction {
1045                program_id_index: 1,
1046                accounts: vec![0],
1047                data: vec![],
1048            })
1049            .collect();
1050        assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1051    }
1052
1053    #[test]
1054    fn sanitize_rejects_insufficient_accounts_for_header() {
1055        let mut message = create_test_message();
1056        // min_accounts = num_required_signatures + num_readonly_unsigned_accounts
1057        // Set readonly_unsigned high so min_accounts > account_keys.len()
1058        message.header.num_readonly_unsigned_accounts = 10;
1059        assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1060    }
1061
1062    #[test]
1063    fn sanitize_rejects_all_signers_readonly() {
1064        let mut message = create_test_message();
1065        message.header.num_readonly_signed_accounts = 1; // All signers readonly
1066        assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1067    }
1068
1069    #[test]
1070    fn sanitize_rejects_duplicate_addresses() {
1071        let mut message = create_test_message();
1072        let dup = message.account_keys[0];
1073        message.account_keys[1] = dup;
1074        assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1075    }
1076
1077    #[test]
1078    fn sanitize_rejects_unaligned_heap_size() {
1079        let mut message = create_test_message();
1080        message.config.heap_size = Some(1025); // Not a multiple of 1024
1081        assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1082    }
1083
1084    #[test]
1085    fn sanitize_accepts_aligned_heap_size() {
1086        let mut message = create_test_message();
1087        message.config.heap_size = Some(65536); // 64KB, valid
1088        assert!(message.sanitize().is_ok());
1089    }
1090
1091    #[test]
1092    fn sanitize_rejects_invalid_program_id_index() {
1093        let mut message = create_test_message();
1094        message.instructions[0].program_id_index = 99;
1095        assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1096    }
1097
1098    #[test]
1099    fn sanitize_rejects_fee_payer_as_program() {
1100        let mut message = create_test_message();
1101        message.instructions[0].program_id_index = 0;
1102        assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1103    }
1104
1105    #[test]
1106    fn sanitize_rejects_instruction_with_too_many_accounts() {
1107        let mut message = create_test_message();
1108        message.instructions[0].accounts = vec![0u8; (u8::MAX as usize) + 1];
1109        assert_eq!(message.sanitize(), Err(SanitizeError::InvalidValue));
1110    }
1111
1112    #[test]
1113    fn sanitize_rejects_invalid_instruction_account_index() {
1114        let mut message = create_test_message();
1115        message.instructions[0].accounts = vec![0, 99]; // 99 is out of bounds
1116        assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1117    }
1118
1119    #[test]
1120    fn sanitize_accepts_64_addresses() {
1121        let mut message = create_test_message();
1122        message.account_keys = (0..MAX_ADDRESSES).map(|_| Address::new_unique()).collect();
1123        message.header.num_required_signatures = 1;
1124        message.header.num_readonly_signed_accounts = 0;
1125        message.header.num_readonly_unsigned_accounts = 1;
1126        message.instructions[0].program_id_index = 1;
1127        message.instructions[0].accounts = vec![0, 2];
1128        assert!(message.sanitize().is_ok());
1129    }
1130
1131    #[test]
1132    fn sanitize_accepts_64_instructions() {
1133        let mut message = create_test_message();
1134        message.instructions = (0..MAX_INSTRUCTIONS)
1135            .map(|_| CompiledInstruction {
1136                program_id_index: 1,
1137                accounts: vec![0, 2],
1138                data: vec![1, 2, 3],
1139            })
1140            .collect();
1141        assert!(message.sanitize().is_ok());
1142    }
1143
1144    #[test]
1145    fn size_matches_serialized_length() {
1146        let test_cases = [
1147            // Minimal message
1148            MessageBuilder::new()
1149                .required_signatures(1)
1150                .lifetime_specifier(Hash::new_unique())
1151                .accounts(vec![Address::new_unique()])
1152                .build()
1153                .unwrap(),
1154            // With config
1155            MessageBuilder::new()
1156                .required_signatures(1)
1157                .lifetime_specifier(Hash::new_unique())
1158                .accounts(vec![Address::new_unique(), Address::new_unique()])
1159                .priority_fee(1000)
1160                .compute_unit_limit(200_000)
1161                .instruction(CompiledInstruction {
1162                    program_id_index: 1,
1163                    accounts: vec![0],
1164                    data: vec![1, 2, 3, 4],
1165                })
1166                .build()
1167                .unwrap(),
1168            // Multiple instructions with varying data
1169            MessageBuilder::new()
1170                .required_signatures(2)
1171                .readonly_signed_accounts(1)
1172                .readonly_unsigned_accounts(1)
1173                .lifetime_specifier(Hash::new_unique())
1174                .accounts(vec![
1175                    Address::new_unique(),
1176                    Address::new_unique(),
1177                    Address::new_unique(),
1178                    Address::new_unique(),
1179                ])
1180                .heap_size(65536)
1181                .instructions(vec![
1182                    CompiledInstruction {
1183                        program_id_index: 2,
1184                        accounts: vec![0, 1],
1185                        data: vec![],
1186                    },
1187                    CompiledInstruction {
1188                        program_id_index: 3,
1189                        accounts: vec![0, 1, 2],
1190                        data: vec![0xAA; 100],
1191                    },
1192                ])
1193                .build()
1194                .unwrap(),
1195        ];
1196
1197        for message in &test_cases {
1198            assert_eq!(message.size(), serialize(message).len());
1199        }
1200    }
1201
1202    #[test]
1203    fn byte_layout_without_config() {
1204        let fee_payer = Address::new_from_array([1u8; 32]);
1205        let program = Address::new_from_array([2u8; 32]);
1206        let blockhash = Hash::new_from_array([0xAB; 32]);
1207
1208        let message = MessageBuilder::new()
1209            .required_signatures(1)
1210            .lifetime_specifier(blockhash)
1211            .accounts(vec![fee_payer, program])
1212            .instruction(CompiledInstruction {
1213                program_id_index: 1,
1214                accounts: vec![0],
1215                data: vec![0xDE, 0xAD],
1216            })
1217            .build()
1218            .unwrap();
1219
1220        let bytes = serialize(&message);
1221
1222        // Build expected bytes manually per SIMD-0385
1223        //
1224        // num_required_signatures
1225        // num_readonly_signed_accounts
1226        // num_readonly_unsigned_accounts
1227        let mut expected = vec![1, 0, 0];
1228        expected.extend_from_slice(&0u32.to_le_bytes()); // ConfigMask = 0
1229        expected.extend_from_slice(&[0xAB; 32]); // LifetimeSpecifier
1230        expected.push(1); // NumInstructions
1231        expected.push(2); // NumAddresses
1232        expected.extend_from_slice(&[1u8; 32]); // fee_payer
1233        expected.extend_from_slice(&[2u8; 32]); // program
1234                                                // ConfigValues: none
1235        expected.push(1); // program_id_index
1236        expected.push(1); // num_accounts
1237        expected.extend_from_slice(&2u16.to_le_bytes()); // data_len
1238        expected.push(0); // account index 0
1239        expected.extend_from_slice(&[0xDE, 0xAD]); // data
1240        assert_eq!(bytes, expected);
1241    }
1242
1243    #[test]
1244    fn byte_layout_with_config() {
1245        let fee_payer = Address::new_from_array([1u8; 32]);
1246        let program = Address::new_from_array([2u8; 32]);
1247        let blockhash = Hash::new_from_array([0xBB; 32]);
1248
1249        let message = MessageBuilder::new()
1250            .required_signatures(1)
1251            .lifetime_specifier(blockhash)
1252            .accounts(vec![fee_payer, program])
1253            .priority_fee(0x0102030405060708u64)
1254            .compute_unit_limit(0x11223344u32)
1255            .instruction(CompiledInstruction {
1256                program_id_index: 1,
1257                accounts: vec![],
1258                data: vec![],
1259            })
1260            .build()
1261            .unwrap();
1262
1263        let bytes = serialize(&message);
1264
1265        let mut expected = vec![1, 0, 0];
1266        // ConfigMask: priority fee (bits 0,1) + CU limit (bit 2) = 0b111 = 7
1267        expected.extend_from_slice(&7u32.to_le_bytes());
1268        expected.extend_from_slice(&[0xBB; 32]);
1269        expected.push(1);
1270        expected.push(2);
1271        expected.extend_from_slice(&[1u8; 32]);
1272        expected.extend_from_slice(&[2u8; 32]);
1273        // Priority fee as u64 LE
1274        expected.extend_from_slice(&0x0102030405060708u64.to_le_bytes());
1275        // Compute unit limit as u32 LE
1276        expected.extend_from_slice(&0x11223344u32.to_le_bytes());
1277        expected.push(1); // program_id_index
1278        expected.push(0); // num_accounts
1279        expected.extend_from_slice(&0u16.to_le_bytes()); // data_len
1280
1281        assert_eq!(bytes, expected);
1282    }
1283
1284    #[test]
1285    fn roundtrip_preserves_all_config_fields() {
1286        let message = MessageBuilder::new()
1287            .required_signatures(1)
1288            .lifetime_specifier(Hash::new_unique())
1289            .accounts(vec![Address::new_unique(), Address::new_unique()])
1290            .priority_fee(1000)
1291            .compute_unit_limit(200_000)
1292            .loaded_accounts_data_size_limit(1_000_000)
1293            .heap_size(65536)
1294            .instruction(CompiledInstruction {
1295                program_id_index: 1,
1296                accounts: vec![0],
1297                data: vec![],
1298            })
1299            .build()
1300            .unwrap();
1301
1302        let serialized = serialize(&message);
1303        let deserialized = deserialize(&serialized).unwrap();
1304        assert_eq!(message.config, deserialized.config);
1305    }
1306}