Skip to main content

solana_message/versions/v0/
mod.rs

1//! A future Solana message format.
2//!
3//! This crate defines two versions of `Message` in their own modules:
4//! [`legacy`] and [`v0`]. `legacy` is the current version as of Solana 1.10.0.
5//! `v0` is a [future message format] that encodes more account keys into a
6//! transaction than the legacy format.
7//!
8//! [`legacy`]: crate::legacy
9//! [`v0`]: crate::v0
10//! [future message format]: https://docs.solanalabs.com/proposals/versioned-transactions
11
12pub use loaded::*;
13#[cfg(feature = "serde")]
14use serde_derive::{Deserialize, Serialize};
15#[cfg(feature = "frozen-abi")]
16use solana_frozen_abi_macro::{frozen_abi, AbiExample, StableAbi, StableAbiSample};
17use {
18    crate::{
19        compiled_instruction::CompiledInstruction,
20        compiled_keys::{CompileError, CompiledKeys},
21        AccountKeys, AddressLookupTableAccount, MessageHeader,
22    },
23    alloc::vec::Vec,
24    solana_address::Address,
25    solana_hash::Hash,
26    solana_instruction::Instruction,
27    solana_sanitize::SanitizeError,
28};
29#[cfg(feature = "std")]
30use {solana_sdk_ids::bpf_loader_upgradeable, std::collections::HashSet};
31#[cfg(feature = "wincode")]
32use {
33    solana_short_vec::ShortU16,
34    wincode::{containers, SchemaRead, SchemaWrite},
35};
36
37mod loaded;
38
39/// Address table lookups describe an on-chain address lookup table to use
40/// for loading more readonly and writable accounts in a single tx.
41#[cfg_attr(
42    feature = "frozen-abi",
43    derive(AbiExample, StableAbi, StableAbiSample),
44    frozen_abi(
45        abi_digest = "BgfDMK6KNGWbvzMmSAcwMsMoL25jmE7x7CCzCeYtMnqa",
46        abi_serializer = ["bincode", "wincode"],
47        test_roundtrip = "eq_and_wire"
48    )
49)]
50#[cfg_attr(
51    feature = "serde",
52    derive(Deserialize, Serialize),
53    serde(rename_all = "camelCase")
54)]
55#[cfg_attr(feature = "wincode", derive(SchemaWrite, SchemaRead))]
56#[derive(Default, Debug, PartialEq, Eq, Clone)]
57pub struct MessageAddressTableLookup {
58    /// Address lookup table account key
59    pub account_key: Address,
60    /// List of indexes used to load writable account addresses
61    #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
62    #[cfg_attr(feature = "wincode", wincode(with = "containers::Vec<_, ShortU16>"))]
63    pub writable_indexes: Vec<u8>,
64    /// List of indexes used to load readonly account addresses
65    #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
66    #[cfg_attr(feature = "wincode", wincode(with = "containers::Vec<_, ShortU16>"))]
67    pub readonly_indexes: Vec<u8>,
68}
69
70/// A Solana transaction message (v0).
71///
72/// This message format supports succinct account loading with
73/// on-chain address lookup tables.
74///
75/// See the crate documentation for further description.
76///
77#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
78#[cfg_attr(
79    feature = "serde",
80    derive(Deserialize, Serialize),
81    serde(rename_all = "camelCase")
82)]
83#[cfg_attr(feature = "wincode", derive(SchemaWrite, SchemaRead))]
84#[derive(Default, Debug, PartialEq, Eq, Clone)]
85pub struct Message {
86    /// The message header, identifying signed and read-only `account_keys`.
87    /// Header values only describe static `account_keys`, they do not describe
88    /// any additional account keys loaded via address table lookups.
89    pub header: MessageHeader,
90
91    /// List of accounts loaded by this transaction.
92    #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
93    #[cfg_attr(feature = "wincode", wincode(with = "containers::Vec<_, ShortU16>"))]
94    pub account_keys: Vec<Address>,
95
96    /// The blockhash of a recent block.
97    pub recent_blockhash: Hash,
98
99    /// Instructions that invoke a designated program, are executed in sequence,
100    /// and committed in one atomic transaction if all succeed.
101    ///
102    /// # Notes
103    ///
104    /// Program indexes must index into the list of message `account_keys` because
105    /// program id's cannot be dynamically loaded from a lookup table.
106    ///
107    /// Account indexes must index into the list of addresses
108    /// constructed from the concatenation of three key lists:
109    ///   1) message `account_keys`
110    ///   2) ordered list of keys loaded from `writable` lookup table indexes
111    ///   3) ordered list of keys loaded from `readable` lookup table indexes
112    #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
113    #[cfg_attr(feature = "wincode", wincode(with = "containers::Vec<_, ShortU16>"))]
114    pub instructions: Vec<CompiledInstruction>,
115
116    /// List of address table lookups used to load additional accounts
117    /// for this transaction.
118    #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
119    #[cfg_attr(feature = "wincode", wincode(with = "containers::Vec<_, ShortU16>"))]
120    pub address_table_lookups: Vec<MessageAddressTableLookup>,
121}
122
123impl Message {
124    /// Sanitize message fields and compiled instruction indexes
125    pub fn sanitize(&self) -> Result<(), SanitizeError> {
126        let num_static_account_keys = self.account_keys.len();
127        if usize::from(self.header.num_required_signatures)
128            .saturating_add(usize::from(self.header.num_readonly_unsigned_accounts))
129            > num_static_account_keys
130        {
131            return Err(SanitizeError::IndexOutOfBounds);
132        }
133
134        // there should be at least 1 RW fee-payer account.
135        if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures {
136            return Err(SanitizeError::InvalidValue);
137        }
138
139        let num_dynamic_account_keys = {
140            let mut total_lookup_keys: usize = 0;
141            for lookup in &self.address_table_lookups {
142                let num_lookup_indexes = lookup
143                    .writable_indexes
144                    .len()
145                    .saturating_add(lookup.readonly_indexes.len());
146
147                // each lookup table must be used to load at least one account
148                if num_lookup_indexes == 0 {
149                    return Err(SanitizeError::InvalidValue);
150                }
151
152                total_lookup_keys = total_lookup_keys.saturating_add(num_lookup_indexes);
153            }
154            total_lookup_keys
155        };
156
157        // this is redundant with the above sanitization checks which require that:
158        // 1) the header describes at least 1 RW account
159        // 2) the header doesn't describe more account keys than the number of account keys
160        if num_static_account_keys == 0 {
161            return Err(SanitizeError::InvalidValue);
162        }
163
164        // the combined number of static and dynamic account keys must be <= 256
165        // since account indices are encoded as `u8`
166        // Note that this is different from the per-transaction account load cap
167        // as defined in `Bank::get_transaction_account_lock_limit`
168        let total_account_keys = num_static_account_keys.saturating_add(num_dynamic_account_keys);
169        if total_account_keys > 256 {
170            return Err(SanitizeError::IndexOutOfBounds);
171        }
172
173        // `expect` is safe because of earlier check that
174        // `num_static_account_keys` is non-zero
175        let max_account_ix = total_account_keys
176            .checked_sub(1)
177            .expect("message doesn't contain any account keys");
178
179        // reject program ids loaded from lookup tables so that
180        // static analysis on program instructions can be performed
181        // without loading on-chain data from a bank
182        let max_program_id_ix =
183            // `expect` is safe because of earlier check that
184            // `num_static_account_keys` is non-zero
185            num_static_account_keys
186                .checked_sub(1)
187                .expect("message doesn't contain any static account keys");
188
189        for ci in &self.instructions {
190            if usize::from(ci.program_id_index) > max_program_id_ix {
191                return Err(SanitizeError::IndexOutOfBounds);
192            }
193            // A program cannot be a payer.
194            if ci.program_id_index == 0 {
195                return Err(SanitizeError::IndexOutOfBounds);
196            }
197            for ai in &ci.accounts {
198                if usize::from(*ai) > max_account_ix {
199                    return Err(SanitizeError::IndexOutOfBounds);
200                }
201            }
202        }
203
204        Ok(())
205    }
206}
207
208impl Message {
209    /// Create a signable transaction message from a `payer` public key,
210    /// `recent_blockhash`, list of `instructions`, and a list of
211    /// `address_lookup_table_accounts`.
212    ///
213    /// # Examples
214    ///
215    /// This example uses the [`solana_rpc_client`], [`solana_account`], and [`anyhow`] crates.
216    ///
217    /// [`solana_rpc_client`]: https://docs.rs/solana-rpc-client
218    /// [`solana_account`]: https://docs.rs/solana-account
219    /// [`anyhow`]: https://docs.rs/anyhow
220    ///
221    /// ```
222    /// # use solana_example_mocks::{
223    /// #     solana_rpc_client,
224    /// #     solana_account,
225    /// #     solana_signer,
226    /// #     solana_keypair,
227    /// # };
228    /// # extern crate alloc;
229    /// # use alloc::borrow::Cow;
230    /// # use solana_account::Account;
231    /// use anyhow::Result;
232    /// use solana_address_lookup_table_interface::state::{AddressLookupTable, LookupTableMeta};
233    /// use solana_instruction::{AccountMeta, Instruction};
234    /// use solana_keypair::Keypair;
235    /// use solana_message::{AddressLookupTableAccount, VersionedMessage, v0};
236    /// use solana_address::Address;
237    /// use solana_rpc_client::rpc_client::RpcClient;
238    /// use solana_signer::Signer;
239    /// # mod solana_transaction {
240    /// #     pub mod versioned {
241    /// #         use solana_example_mocks::{solana_keypair::Keypair, solana_signer::SignerError};
242    /// #         use solana_message::VersionedMessage;
243    /// #         pub struct VersionedTransaction {
244    /// #             pub message: solana_message::VersionedMessage,
245    /// #         }
246    /// #         impl VersionedTransaction {
247    /// #             pub fn try_new(
248    /// #                 message: VersionedMessage,
249    /// #                 _keypairs: &[&Keypair],
250    /// #             ) -> core::result::Result<Self, solana_example_mocks::solana_signer::SignerError> {
251    /// #                 Ok(VersionedTransaction {
252    /// #                     message,
253    /// #                 })
254    /// #             }
255    /// #         }
256    /// #     }
257    /// # }
258    /// use solana_transaction::versioned::VersionedTransaction;
259    ///
260    /// fn create_tx_with_address_table_lookup(
261    ///     client: &RpcClient,
262    ///     instruction: Instruction,
263    ///     address_lookup_table_key: Address,
264    ///     payer: &Keypair,
265    /// ) -> Result<VersionedTransaction> {
266    ///     # client.set_get_account_response(address_lookup_table_key, Account {
267    ///     #   lamports: 1,
268    ///     #   data: AddressLookupTable {
269    ///     #     meta: LookupTableMeta::default(),
270    ///     #     addresses: Cow::Owned(instruction.accounts.iter().map(|meta| meta.pubkey).collect()),
271    ///     #   }.serialize_for_tests().unwrap(),
272    ///     #   owner: solana_address_lookup_table_interface::program::id(),
273    ///     #   executable: false,
274    ///     # });
275    ///     let raw_account = client.get_account(&address_lookup_table_key)?;
276    ///     let address_lookup_table = AddressLookupTable::deserialize(&raw_account.data)?;
277    ///     let address_lookup_table_account = AddressLookupTableAccount {
278    ///         key: address_lookup_table_key,
279    ///         addresses: address_lookup_table.addresses.to_vec(),
280    ///     };
281    ///
282    ///     let blockhash = client.get_latest_blockhash()?;
283    ///     let tx = VersionedTransaction::try_new(
284    ///         VersionedMessage::V0(v0::Message::try_compile(
285    ///             &payer.pubkey(),
286    ///             &[instruction],
287    ///             &[address_lookup_table_account],
288    ///             blockhash,
289    ///         )?),
290    ///         &[payer],
291    ///     )?;
292    ///
293    ///     # assert!(tx.message.address_table_lookups().unwrap().len() > 0);
294    ///     Ok(tx)
295    /// }
296    /// #
297    /// # let client = RpcClient::new(String::new());
298    /// # let payer = Keypair::new();
299    /// # let address_lookup_table_key = Address::new_unique();
300    /// # let instruction = Instruction::new_with_bincode(Address::new_unique(), &(), vec![
301    /// #   AccountMeta::new(Address::new_unique(), false),
302    /// # ]);
303    /// # create_tx_with_address_table_lookup(&client, instruction, address_lookup_table_key, &payer)?;
304    /// # Ok::<(), anyhow::Error>(())
305    /// ```
306    pub fn try_compile(
307        payer: &Address,
308        instructions: &[Instruction],
309        address_lookup_table_accounts: &[AddressLookupTableAccount],
310        recent_blockhash: Hash,
311    ) -> Result<Self, CompileError> {
312        let mut compiled_keys = CompiledKeys::compile(instructions, Some(*payer));
313
314        let mut address_table_lookups = Vec::with_capacity(address_lookup_table_accounts.len());
315        let mut loaded_addresses_list = Vec::with_capacity(address_lookup_table_accounts.len());
316        for lookup_table_account in address_lookup_table_accounts {
317            if let Some((lookup, loaded_addresses)) =
318                compiled_keys.try_extract_table_lookup(lookup_table_account)?
319            {
320                address_table_lookups.push(lookup);
321                loaded_addresses_list.push(loaded_addresses);
322            }
323        }
324
325        let (header, static_keys) = compiled_keys.try_into_message_components()?;
326        let dynamic_keys = loaded_addresses_list.into_iter().collect();
327        let account_keys = AccountKeys::new(&static_keys, Some(&dynamic_keys));
328        let instructions = account_keys.try_compile_instructions(instructions)?;
329
330        Ok(Self {
331            header,
332            account_keys: static_keys,
333            recent_blockhash,
334            instructions,
335            address_table_lookups,
336        })
337    }
338
339    #[cfg(feature = "wincode")]
340    /// Serialize this message with a version #0 prefix using wincode encoding.
341    pub fn serialize(&self) -> Vec<u8> {
342        wincode::serialize(&(crate::MESSAGE_VERSION_PREFIX, self)).unwrap()
343    }
344
345    /// Returns true if the account at the specified index is called as a program by an instruction
346    pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
347        if let Ok(key_index) = u8::try_from(key_index) {
348            self.instructions
349                .iter()
350                .any(|ix| ix.program_id_index == key_index)
351        } else {
352            false
353        }
354    }
355
356    /// Returns true if the account at the specified index was requested to be
357    /// writable.  This method should not be used directly.
358    #[cfg(feature = "std")]
359    fn is_writable_index(&self, key_index: usize) -> bool {
360        let header = &self.header;
361        let num_account_keys = self.account_keys.len();
362        let num_signed_accounts = usize::from(header.num_required_signatures);
363        if key_index >= num_account_keys {
364            let loaded_addresses_index = key_index.saturating_sub(num_account_keys);
365            let num_writable_dynamic_addresses = self
366                .address_table_lookups
367                .iter()
368                .map(|lookup| lookup.writable_indexes.len())
369                .sum();
370            loaded_addresses_index < num_writable_dynamic_addresses
371        } else if key_index >= num_signed_accounts {
372            let num_unsigned_accounts = num_account_keys.saturating_sub(num_signed_accounts);
373            let num_writable_unsigned_accounts = num_unsigned_accounts
374                .saturating_sub(usize::from(header.num_readonly_unsigned_accounts));
375            let unsigned_account_index = key_index.saturating_sub(num_signed_accounts);
376            unsigned_account_index < num_writable_unsigned_accounts
377        } else {
378            let num_writable_signed_accounts = num_signed_accounts
379                .saturating_sub(usize::from(header.num_readonly_signed_accounts));
380            key_index < num_writable_signed_accounts
381        }
382    }
383
384    /// Returns true if any static account key is the bpf upgradeable loader
385    #[cfg(feature = "std")]
386    fn is_upgradeable_loader_in_static_keys(&self) -> bool {
387        self.account_keys
388            .iter()
389            .any(|&key| key == bpf_loader_upgradeable::id())
390    }
391
392    /// Returns true if the account at the specified index was requested as
393    /// writable. Before loading addresses, we can't demote write locks properly
394    /// so this should not be used by the runtime. The `reserved_account_keys`
395    /// param is optional to allow clients to approximate writability without
396    /// requiring fetching the latest set of reserved account keys.
397    #[cfg(feature = "std")]
398    pub fn is_maybe_writable(
399        &self,
400        key_index: usize,
401        reserved_account_keys: Option<&HashSet<Address>>,
402    ) -> bool {
403        self.is_writable_index(key_index)
404            && !self.is_account_maybe_reserved(key_index, reserved_account_keys)
405            && !{
406                // demote program ids
407                self.is_key_called_as_program(key_index)
408                    && !self.is_upgradeable_loader_in_static_keys()
409            }
410    }
411
412    /// Returns true if the account at the specified index is in the reserved
413    /// account keys set. Before loading addresses, we can't detect reserved
414    /// account keys properly so this shouldn't be used by the runtime.
415    #[cfg(feature = "std")]
416    fn is_account_maybe_reserved(
417        &self,
418        key_index: usize,
419        reserved_account_keys: Option<&HashSet<Address>>,
420    ) -> bool {
421        let mut is_maybe_reserved = false;
422        if let Some(reserved_account_keys) = reserved_account_keys {
423            if let Some(key) = self.account_keys.get(key_index) {
424                is_maybe_reserved = reserved_account_keys.contains(key);
425            }
426        }
427        is_maybe_reserved
428    }
429}
430
431#[cfg(test)]
432mod tests {
433    use {super::*, crate::VersionedMessage, alloc::vec, solana_instruction::AccountMeta};
434
435    #[test]
436    fn test_sanitize() {
437        assert!(Message {
438            header: MessageHeader {
439                num_required_signatures: 1,
440                ..MessageHeader::default()
441            },
442            account_keys: vec![Address::new_unique()],
443            ..Message::default()
444        }
445        .sanitize()
446        .is_ok());
447    }
448
449    #[test]
450    fn test_sanitize_with_instruction() {
451        assert!(Message {
452            header: MessageHeader {
453                num_required_signatures: 1,
454                ..MessageHeader::default()
455            },
456            account_keys: vec![Address::new_unique(), Address::new_unique()],
457            instructions: vec![CompiledInstruction {
458                program_id_index: 1,
459                accounts: vec![0],
460                data: vec![]
461            }],
462            ..Message::default()
463        }
464        .sanitize()
465        .is_ok());
466    }
467
468    #[test]
469    fn test_sanitize_with_table_lookup() {
470        assert!(Message {
471            header: MessageHeader {
472                num_required_signatures: 1,
473                ..MessageHeader::default()
474            },
475            account_keys: vec![Address::new_unique()],
476            address_table_lookups: vec![MessageAddressTableLookup {
477                account_key: Address::new_unique(),
478                writable_indexes: vec![1, 2, 3],
479                readonly_indexes: vec![0],
480            }],
481            ..Message::default()
482        }
483        .sanitize()
484        .is_ok());
485    }
486
487    #[test]
488    fn test_sanitize_with_table_lookup_and_ix_with_dynamic_program_id() {
489        let message = Message {
490            header: MessageHeader {
491                num_required_signatures: 1,
492                ..MessageHeader::default()
493            },
494            account_keys: vec![Address::new_unique()],
495            address_table_lookups: vec![MessageAddressTableLookup {
496                account_key: Address::new_unique(),
497                writable_indexes: vec![1, 2, 3],
498                readonly_indexes: vec![0],
499            }],
500            instructions: vec![CompiledInstruction {
501                program_id_index: 4,
502                accounts: vec![0, 1, 2, 3],
503                data: vec![],
504            }],
505            ..Message::default()
506        };
507
508        assert!(message.sanitize().is_err());
509    }
510
511    #[test]
512    fn test_sanitize_with_table_lookup_and_ix_with_static_program_id() {
513        assert!(Message {
514            header: MessageHeader {
515                num_required_signatures: 1,
516                ..MessageHeader::default()
517            },
518            account_keys: vec![Address::new_unique(), Address::new_unique()],
519            address_table_lookups: vec![MessageAddressTableLookup {
520                account_key: Address::new_unique(),
521                writable_indexes: vec![1, 2, 3],
522                readonly_indexes: vec![0],
523            }],
524            instructions: vec![CompiledInstruction {
525                program_id_index: 1,
526                accounts: vec![2, 3, 4, 5],
527                data: vec![]
528            }],
529            ..Message::default()
530        }
531        .sanitize()
532        .is_ok());
533    }
534
535    #[test]
536    fn test_sanitize_without_signer() {
537        assert!(Message {
538            header: MessageHeader::default(),
539            account_keys: vec![Address::new_unique()],
540            ..Message::default()
541        }
542        .sanitize()
543        .is_err());
544    }
545
546    #[test]
547    fn test_sanitize_without_writable_signer() {
548        assert!(Message {
549            header: MessageHeader {
550                num_required_signatures: 1,
551                num_readonly_signed_accounts: 1,
552                ..MessageHeader::default()
553            },
554            account_keys: vec![Address::new_unique()],
555            ..Message::default()
556        }
557        .sanitize()
558        .is_err());
559    }
560
561    #[test]
562    fn test_sanitize_with_empty_table_lookup() {
563        assert!(Message {
564            header: MessageHeader {
565                num_required_signatures: 1,
566                ..MessageHeader::default()
567            },
568            account_keys: vec![Address::new_unique()],
569            address_table_lookups: vec![MessageAddressTableLookup {
570                account_key: Address::new_unique(),
571                writable_indexes: vec![],
572                readonly_indexes: vec![],
573            }],
574            ..Message::default()
575        }
576        .sanitize()
577        .is_err());
578    }
579
580    #[test]
581    fn test_sanitize_with_max_account_keys() {
582        assert!(Message {
583            header: MessageHeader {
584                num_required_signatures: 1,
585                ..MessageHeader::default()
586            },
587            account_keys: (0..=u8::MAX).map(|_| Address::new_unique()).collect(),
588            ..Message::default()
589        }
590        .sanitize()
591        .is_ok());
592    }
593
594    #[test]
595    fn test_sanitize_with_too_many_account_keys() {
596        assert!(Message {
597            header: MessageHeader {
598                num_required_signatures: 1,
599                ..MessageHeader::default()
600            },
601            account_keys: (0..=256).map(|_| Address::new_unique()).collect(),
602            ..Message::default()
603        }
604        .sanitize()
605        .is_err());
606    }
607
608    #[test]
609    fn test_sanitize_with_max_table_loaded_keys() {
610        assert!(Message {
611            header: MessageHeader {
612                num_required_signatures: 1,
613                ..MessageHeader::default()
614            },
615            account_keys: vec![Address::new_unique()],
616            address_table_lookups: vec![MessageAddressTableLookup {
617                account_key: Address::new_unique(),
618                writable_indexes: (0..=254).step_by(2).collect(),
619                readonly_indexes: (1..=254).step_by(2).collect(),
620            }],
621            ..Message::default()
622        }
623        .sanitize()
624        .is_ok());
625    }
626
627    #[test]
628    fn test_sanitize_with_too_many_table_loaded_keys() {
629        assert!(Message {
630            header: MessageHeader {
631                num_required_signatures: 1,
632                ..MessageHeader::default()
633            },
634            account_keys: vec![Address::new_unique()],
635            address_table_lookups: vec![MessageAddressTableLookup {
636                account_key: Address::new_unique(),
637                writable_indexes: (0..=255).step_by(2).collect(),
638                readonly_indexes: (1..=255).step_by(2).collect(),
639            }],
640            ..Message::default()
641        }
642        .sanitize()
643        .is_err());
644    }
645
646    #[test]
647    fn test_sanitize_with_invalid_ix_program_id() {
648        let message = Message {
649            header: MessageHeader {
650                num_required_signatures: 1,
651                ..MessageHeader::default()
652            },
653            account_keys: vec![Address::new_unique()],
654            address_table_lookups: vec![MessageAddressTableLookup {
655                account_key: Address::new_unique(),
656                writable_indexes: vec![0],
657                readonly_indexes: vec![],
658            }],
659            instructions: vec![CompiledInstruction {
660                program_id_index: 2,
661                accounts: vec![],
662                data: vec![],
663            }],
664            ..Message::default()
665        };
666
667        assert!(message.sanitize().is_err());
668    }
669
670    #[test]
671    fn test_sanitize_with_invalid_ix_account() {
672        assert!(Message {
673            header: MessageHeader {
674                num_required_signatures: 1,
675                ..MessageHeader::default()
676            },
677            account_keys: vec![Address::new_unique(), Address::new_unique()],
678            address_table_lookups: vec![MessageAddressTableLookup {
679                account_key: Address::new_unique(),
680                writable_indexes: vec![],
681                readonly_indexes: vec![0],
682            }],
683            instructions: vec![CompiledInstruction {
684                program_id_index: 1,
685                accounts: vec![3],
686                data: vec![]
687            }],
688            ..Message::default()
689        }
690        .sanitize()
691        .is_err());
692    }
693
694    #[test]
695    fn test_serialize() {
696        let message = Message::default();
697        let versioned_msg = VersionedMessage::V0(message.clone());
698        assert_eq!(message.serialize(), versioned_msg.serialize());
699    }
700
701    #[test]
702    fn test_try_compile() {
703        let mut keys = vec![];
704        keys.resize_with(7, Address::new_unique);
705
706        let payer = keys[0];
707        let program_id = keys[6];
708        let instructions = vec![Instruction {
709            program_id,
710            accounts: vec![
711                AccountMeta::new(keys[1], true),
712                AccountMeta::new_readonly(keys[2], true),
713                AccountMeta::new(keys[3], false),
714                AccountMeta::new(keys[4], false), // loaded from lut
715                AccountMeta::new_readonly(keys[5], false), // loaded from lut
716            ],
717            data: vec![],
718        }];
719        let address_lookup_table_accounts = vec![
720            AddressLookupTableAccount {
721                key: Address::new_unique(),
722                addresses: vec![keys[4], keys[5], keys[6]],
723            },
724            AddressLookupTableAccount {
725                key: Address::new_unique(),
726                addresses: vec![],
727            },
728        ];
729
730        let recent_blockhash = Hash::new_unique();
731        assert_eq!(
732            Message::try_compile(
733                &payer,
734                &instructions,
735                &address_lookup_table_accounts,
736                recent_blockhash
737            ),
738            Ok(Message {
739                header: MessageHeader {
740                    num_required_signatures: 3,
741                    num_readonly_signed_accounts: 1,
742                    num_readonly_unsigned_accounts: 1
743                },
744                recent_blockhash,
745                account_keys: vec![keys[0], keys[1], keys[2], keys[3], program_id],
746                instructions: vec![CompiledInstruction {
747                    program_id_index: 4,
748                    accounts: vec![1, 2, 3, 5, 6],
749                    data: vec![],
750                },],
751                address_table_lookups: vec![MessageAddressTableLookup {
752                    account_key: address_lookup_table_accounts[0].key,
753                    writable_indexes: vec![0],
754                    readonly_indexes: vec![1],
755                }],
756            })
757        );
758    }
759
760    #[test]
761    fn test_is_maybe_writable() {
762        let key0 = Address::new_unique();
763        let key1 = Address::new_unique();
764        let key2 = Address::new_unique();
765        let key3 = Address::new_unique();
766        let key4 = Address::new_unique();
767        let key5 = Address::new_unique();
768
769        let message = Message {
770            header: MessageHeader {
771                num_required_signatures: 3,
772                num_readonly_signed_accounts: 2,
773                num_readonly_unsigned_accounts: 1,
774            },
775            account_keys: vec![key0, key1, key2, key3, key4, key5],
776            address_table_lookups: vec![MessageAddressTableLookup {
777                account_key: Address::new_unique(),
778                writable_indexes: vec![0],
779                readonly_indexes: vec![1],
780            }],
781            ..Message::default()
782        };
783
784        let reserved_account_keys = HashSet::from([key3]);
785
786        assert!(message.is_maybe_writable(0, Some(&reserved_account_keys)));
787        assert!(!message.is_maybe_writable(1, Some(&reserved_account_keys)));
788        assert!(!message.is_maybe_writable(2, Some(&reserved_account_keys)));
789        assert!(!message.is_maybe_writable(3, Some(&reserved_account_keys)));
790        assert!(message.is_maybe_writable(3, None));
791        assert!(message.is_maybe_writable(4, Some(&reserved_account_keys)));
792        assert!(!message.is_maybe_writable(5, Some(&reserved_account_keys)));
793        assert!(message.is_maybe_writable(6, Some(&reserved_account_keys)));
794        assert!(!message.is_maybe_writable(7, Some(&reserved_account_keys)));
795        assert!(!message.is_maybe_writable(8, Some(&reserved_account_keys)));
796    }
797
798    #[test]
799    fn test_is_account_maybe_reserved() {
800        let key0 = Address::new_unique();
801        let key1 = Address::new_unique();
802
803        let message = Message {
804            account_keys: vec![key0, key1],
805            address_table_lookups: vec![MessageAddressTableLookup {
806                account_key: Address::new_unique(),
807                writable_indexes: vec![0],
808                readonly_indexes: vec![1],
809            }],
810            ..Message::default()
811        };
812
813        let reserved_account_keys = HashSet::from([key1]);
814
815        assert!(!message.is_account_maybe_reserved(0, Some(&reserved_account_keys)));
816        assert!(message.is_account_maybe_reserved(1, Some(&reserved_account_keys)));
817        assert!(!message.is_account_maybe_reserved(2, Some(&reserved_account_keys)));
818        assert!(!message.is_account_maybe_reserved(3, Some(&reserved_account_keys)));
819        assert!(!message.is_account_maybe_reserved(4, Some(&reserved_account_keys)));
820        assert!(!message.is_account_maybe_reserved(0, None));
821        assert!(!message.is_account_maybe_reserved(1, None));
822        assert!(!message.is_account_maybe_reserved(2, None));
823        assert!(!message.is_account_maybe_reserved(3, None));
824        assert!(!message.is_account_maybe_reserved(4, None));
825    }
826}