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