Skip to main content

solana_message/versions/v1/
message.rs

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