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_address::Address,
24    solana_hash::Hash,
25    solana_instruction::Instruction,
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: Address,
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<Address>,
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_account`], and [`anyhow`] crates.
195    ///
196    /// [`solana_rpc_client`]: https://docs.rs/solana-rpc-client
197    /// [`solana_account`]: https://docs.rs/solana-account
198    /// [`anyhow`]: https://docs.rs/anyhow
199    ///
200    /// ```
201    /// # use solana_example_mocks::{
202    /// #     solana_rpc_client,
203    /// #     solana_account,
204    /// #     solana_transaction,
205    /// #     solana_signer,
206    /// #     solana_keypair,
207    /// # };
208    /// # use std::borrow::Cow;
209    /// # use solana_account::Account;
210    /// use anyhow::Result;
211    /// use solana_address_lookup_table_interface::state::{AddressLookupTable, LookupTableMeta};
212    /// use solana_instruction::{AccountMeta, Instruction};
213    /// use solana_keypair::Keypair;
214    /// use solana_message::{AddressLookupTableAccount, VersionedMessage, v0};
215    /// use solana_address::Address;
216    /// use solana_rpc_client::rpc_client::RpcClient;
217    /// use solana_signer::Signer;
218    /// use solana_transaction::versioned::VersionedTransaction;
219    ///
220    /// fn create_tx_with_address_table_lookup(
221    ///     client: &RpcClient,
222    ///     instruction: Instruction,
223    ///     address_lookup_table_key: Address,
224    ///     payer: &Keypair,
225    /// ) -> Result<VersionedTransaction> {
226    ///     # client.set_get_account_response(address_lookup_table_key, Account {
227    ///     #   lamports: 1,
228    ///     #   data: AddressLookupTable {
229    ///     #     meta: LookupTableMeta::default(),
230    ///     #     addresses: Cow::Owned(instruction.accounts.iter().map(|meta| meta.pubkey).collect()),
231    ///     #   }.serialize_for_tests().unwrap(),
232    ///     #   owner: solana_address_lookup_table_interface::program::id(),
233    ///     #   executable: false,
234    ///     # });
235    ///     let raw_account = client.get_account(&address_lookup_table_key)?;
236    ///     let address_lookup_table = AddressLookupTable::deserialize(&raw_account.data)?;
237    ///     let address_lookup_table_account = AddressLookupTableAccount {
238    ///         key: address_lookup_table_key,
239    ///         addresses: address_lookup_table.addresses.to_vec(),
240    ///     };
241    ///
242    ///     let blockhash = client.get_latest_blockhash()?;
243    ///     let tx = VersionedTransaction::try_new(
244    ///         VersionedMessage::V0(v0::Message::try_compile(
245    ///             &payer.pubkey(),
246    ///             &[instruction],
247    ///             &[address_lookup_table_account],
248    ///             blockhash,
249    ///         )?),
250    ///         &[payer],
251    ///     )?;
252    ///
253    ///     # assert!(tx.message.address_table_lookups().unwrap().len() > 0);
254    ///     Ok(tx)
255    /// }
256    /// #
257    /// # let client = RpcClient::new(String::new());
258    /// # let payer = Keypair::new();
259    /// # let address_lookup_table_key = Address::new_unique();
260    /// # let instruction = Instruction::new_with_bincode(Address::new_unique(), &(), vec![
261    /// #   AccountMeta::new(Address::new_unique(), false),
262    /// # ]);
263    /// # create_tx_with_address_table_lookup(&client, instruction, address_lookup_table_key, &payer)?;
264    /// # Ok::<(), anyhow::Error>(())
265    /// ```
266    pub fn try_compile(
267        payer: &Address,
268        instructions: &[Instruction],
269        address_lookup_table_accounts: &[AddressLookupTableAccount],
270        recent_blockhash: Hash,
271    ) -> Result<Self, CompileError> {
272        let mut compiled_keys = CompiledKeys::compile(instructions, Some(*payer));
273
274        let mut address_table_lookups = Vec::with_capacity(address_lookup_table_accounts.len());
275        let mut loaded_addresses_list = Vec::with_capacity(address_lookup_table_accounts.len());
276        for lookup_table_account in address_lookup_table_accounts {
277            if let Some((lookup, loaded_addresses)) =
278                compiled_keys.try_extract_table_lookup(lookup_table_account)?
279            {
280                address_table_lookups.push(lookup);
281                loaded_addresses_list.push(loaded_addresses);
282            }
283        }
284
285        let (header, static_keys) = compiled_keys.try_into_message_components()?;
286        let dynamic_keys = loaded_addresses_list.into_iter().collect();
287        let account_keys = AccountKeys::new(&static_keys, Some(&dynamic_keys));
288        let instructions = account_keys.try_compile_instructions(instructions)?;
289
290        Ok(Self {
291            header,
292            account_keys: static_keys,
293            recent_blockhash,
294            instructions,
295            address_table_lookups,
296        })
297    }
298
299    #[cfg(feature = "bincode")]
300    /// Serialize this message with a version #0 prefix using bincode encoding.
301    pub fn serialize(&self) -> Vec<u8> {
302        bincode::serialize(&(crate::MESSAGE_VERSION_PREFIX, self)).unwrap()
303    }
304
305    /// Returns true if the account at the specified index is called as a program by an instruction
306    pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
307        if let Ok(key_index) = u8::try_from(key_index) {
308            self.instructions
309                .iter()
310                .any(|ix| ix.program_id_index == key_index)
311        } else {
312            false
313        }
314    }
315
316    /// Returns true if the account at the specified index was requested to be
317    /// writable.  This method should not be used directly.
318    fn is_writable_index(&self, key_index: usize) -> bool {
319        let header = &self.header;
320        let num_account_keys = self.account_keys.len();
321        let num_signed_accounts = usize::from(header.num_required_signatures);
322        if key_index >= num_account_keys {
323            let loaded_addresses_index = key_index.saturating_sub(num_account_keys);
324            let num_writable_dynamic_addresses = self
325                .address_table_lookups
326                .iter()
327                .map(|lookup| lookup.writable_indexes.len())
328                .sum();
329            loaded_addresses_index < num_writable_dynamic_addresses
330        } else if key_index >= num_signed_accounts {
331            let num_unsigned_accounts = num_account_keys.saturating_sub(num_signed_accounts);
332            let num_writable_unsigned_accounts = num_unsigned_accounts
333                .saturating_sub(usize::from(header.num_readonly_unsigned_accounts));
334            let unsigned_account_index = key_index.saturating_sub(num_signed_accounts);
335            unsigned_account_index < num_writable_unsigned_accounts
336        } else {
337            let num_writable_signed_accounts = num_signed_accounts
338                .saturating_sub(usize::from(header.num_readonly_signed_accounts));
339            key_index < num_writable_signed_accounts
340        }
341    }
342
343    /// Returns true if any static account key is the bpf upgradeable loader
344    fn is_upgradeable_loader_in_static_keys(&self) -> bool {
345        self.account_keys
346            .iter()
347            .any(|&key| key == bpf_loader_upgradeable::id())
348    }
349
350    /// Returns true if the account at the specified index was requested as
351    /// writable. Before loading addresses, we can't demote write locks properly
352    /// so this should not be used by the runtime. The `reserved_account_keys`
353    /// param is optional to allow clients to approximate writability without
354    /// requiring fetching the latest set of reserved account keys.
355    pub fn is_maybe_writable(
356        &self,
357        key_index: usize,
358        reserved_account_keys: Option<&HashSet<Address>>,
359    ) -> bool {
360        self.is_writable_index(key_index)
361            && !self.is_account_maybe_reserved(key_index, reserved_account_keys)
362            && !{
363                // demote program ids
364                self.is_key_called_as_program(key_index)
365                    && !self.is_upgradeable_loader_in_static_keys()
366            }
367    }
368
369    /// Returns true if the account at the specified index is in the reserved
370    /// account keys set. Before loading addresses, we can't detect reserved
371    /// account keys properly so this shouldn't be used by the runtime.
372    fn is_account_maybe_reserved(
373        &self,
374        key_index: usize,
375        reserved_account_keys: Option<&HashSet<Address>>,
376    ) -> bool {
377        let mut is_maybe_reserved = false;
378        if let Some(reserved_account_keys) = reserved_account_keys {
379            if let Some(key) = self.account_keys.get(key_index) {
380                is_maybe_reserved = reserved_account_keys.contains(key);
381            }
382        }
383        is_maybe_reserved
384    }
385}
386
387#[cfg(test)]
388mod tests {
389    use {super::*, crate::VersionedMessage, solana_instruction::AccountMeta};
390
391    #[test]
392    fn test_sanitize() {
393        assert!(Message {
394            header: MessageHeader {
395                num_required_signatures: 1,
396                ..MessageHeader::default()
397            },
398            account_keys: vec![Address::new_unique()],
399            ..Message::default()
400        }
401        .sanitize()
402        .is_ok());
403    }
404
405    #[test]
406    fn test_sanitize_with_instruction() {
407        assert!(Message {
408            header: MessageHeader {
409                num_required_signatures: 1,
410                ..MessageHeader::default()
411            },
412            account_keys: vec![Address::new_unique(), Address::new_unique()],
413            instructions: vec![CompiledInstruction {
414                program_id_index: 1,
415                accounts: vec![0],
416                data: vec![]
417            }],
418            ..Message::default()
419        }
420        .sanitize()
421        .is_ok());
422    }
423
424    #[test]
425    fn test_sanitize_with_table_lookup() {
426        assert!(Message {
427            header: MessageHeader {
428                num_required_signatures: 1,
429                ..MessageHeader::default()
430            },
431            account_keys: vec![Address::new_unique()],
432            address_table_lookups: vec![MessageAddressTableLookup {
433                account_key: Address::new_unique(),
434                writable_indexes: vec![1, 2, 3],
435                readonly_indexes: vec![0],
436            }],
437            ..Message::default()
438        }
439        .sanitize()
440        .is_ok());
441    }
442
443    #[test]
444    fn test_sanitize_with_table_lookup_and_ix_with_dynamic_program_id() {
445        let message = Message {
446            header: MessageHeader {
447                num_required_signatures: 1,
448                ..MessageHeader::default()
449            },
450            account_keys: vec![Address::new_unique()],
451            address_table_lookups: vec![MessageAddressTableLookup {
452                account_key: Address::new_unique(),
453                writable_indexes: vec![1, 2, 3],
454                readonly_indexes: vec![0],
455            }],
456            instructions: vec![CompiledInstruction {
457                program_id_index: 4,
458                accounts: vec![0, 1, 2, 3],
459                data: vec![],
460            }],
461            ..Message::default()
462        };
463
464        assert!(message.sanitize().is_err());
465    }
466
467    #[test]
468    fn test_sanitize_with_table_lookup_and_ix_with_static_program_id() {
469        assert!(Message {
470            header: MessageHeader {
471                num_required_signatures: 1,
472                ..MessageHeader::default()
473            },
474            account_keys: vec![Address::new_unique(), Address::new_unique()],
475            address_table_lookups: vec![MessageAddressTableLookup {
476                account_key: Address::new_unique(),
477                writable_indexes: vec![1, 2, 3],
478                readonly_indexes: vec![0],
479            }],
480            instructions: vec![CompiledInstruction {
481                program_id_index: 1,
482                accounts: vec![2, 3, 4, 5],
483                data: vec![]
484            }],
485            ..Message::default()
486        }
487        .sanitize()
488        .is_ok());
489    }
490
491    #[test]
492    fn test_sanitize_without_signer() {
493        assert!(Message {
494            header: MessageHeader::default(),
495            account_keys: vec![Address::new_unique()],
496            ..Message::default()
497        }
498        .sanitize()
499        .is_err());
500    }
501
502    #[test]
503    fn test_sanitize_without_writable_signer() {
504        assert!(Message {
505            header: MessageHeader {
506                num_required_signatures: 1,
507                num_readonly_signed_accounts: 1,
508                ..MessageHeader::default()
509            },
510            account_keys: vec![Address::new_unique()],
511            ..Message::default()
512        }
513        .sanitize()
514        .is_err());
515    }
516
517    #[test]
518    fn test_sanitize_with_empty_table_lookup() {
519        assert!(Message {
520            header: MessageHeader {
521                num_required_signatures: 1,
522                ..MessageHeader::default()
523            },
524            account_keys: vec![Address::new_unique()],
525            address_table_lookups: vec![MessageAddressTableLookup {
526                account_key: Address::new_unique(),
527                writable_indexes: vec![],
528                readonly_indexes: vec![],
529            }],
530            ..Message::default()
531        }
532        .sanitize()
533        .is_err());
534    }
535
536    #[test]
537    fn test_sanitize_with_max_account_keys() {
538        assert!(Message {
539            header: MessageHeader {
540                num_required_signatures: 1,
541                ..MessageHeader::default()
542            },
543            account_keys: (0..=u8::MAX).map(|_| Address::new_unique()).collect(),
544            ..Message::default()
545        }
546        .sanitize()
547        .is_ok());
548    }
549
550    #[test]
551    fn test_sanitize_with_too_many_account_keys() {
552        assert!(Message {
553            header: MessageHeader {
554                num_required_signatures: 1,
555                ..MessageHeader::default()
556            },
557            account_keys: (0..=256).map(|_| Address::new_unique()).collect(),
558            ..Message::default()
559        }
560        .sanitize()
561        .is_err());
562    }
563
564    #[test]
565    fn test_sanitize_with_max_table_loaded_keys() {
566        assert!(Message {
567            header: MessageHeader {
568                num_required_signatures: 1,
569                ..MessageHeader::default()
570            },
571            account_keys: vec![Address::new_unique()],
572            address_table_lookups: vec![MessageAddressTableLookup {
573                account_key: Address::new_unique(),
574                writable_indexes: (0..=254).step_by(2).collect(),
575                readonly_indexes: (1..=254).step_by(2).collect(),
576            }],
577            ..Message::default()
578        }
579        .sanitize()
580        .is_ok());
581    }
582
583    #[test]
584    fn test_sanitize_with_too_many_table_loaded_keys() {
585        assert!(Message {
586            header: MessageHeader {
587                num_required_signatures: 1,
588                ..MessageHeader::default()
589            },
590            account_keys: vec![Address::new_unique()],
591            address_table_lookups: vec![MessageAddressTableLookup {
592                account_key: Address::new_unique(),
593                writable_indexes: (0..=255).step_by(2).collect(),
594                readonly_indexes: (1..=255).step_by(2).collect(),
595            }],
596            ..Message::default()
597        }
598        .sanitize()
599        .is_err());
600    }
601
602    #[test]
603    fn test_sanitize_with_invalid_ix_program_id() {
604        let message = Message {
605            header: MessageHeader {
606                num_required_signatures: 1,
607                ..MessageHeader::default()
608            },
609            account_keys: vec![Address::new_unique()],
610            address_table_lookups: vec![MessageAddressTableLookup {
611                account_key: Address::new_unique(),
612                writable_indexes: vec![0],
613                readonly_indexes: vec![],
614            }],
615            instructions: vec![CompiledInstruction {
616                program_id_index: 2,
617                accounts: vec![],
618                data: vec![],
619            }],
620            ..Message::default()
621        };
622
623        assert!(message.sanitize().is_err());
624    }
625
626    #[test]
627    fn test_sanitize_with_invalid_ix_account() {
628        assert!(Message {
629            header: MessageHeader {
630                num_required_signatures: 1,
631                ..MessageHeader::default()
632            },
633            account_keys: vec![Address::new_unique(), Address::new_unique()],
634            address_table_lookups: vec![MessageAddressTableLookup {
635                account_key: Address::new_unique(),
636                writable_indexes: vec![],
637                readonly_indexes: vec![0],
638            }],
639            instructions: vec![CompiledInstruction {
640                program_id_index: 1,
641                accounts: vec![3],
642                data: vec![]
643            }],
644            ..Message::default()
645        }
646        .sanitize()
647        .is_err());
648    }
649
650    #[test]
651    fn test_serialize() {
652        let message = Message::default();
653        let versioned_msg = VersionedMessage::V0(message.clone());
654        assert_eq!(message.serialize(), versioned_msg.serialize());
655    }
656
657    #[test]
658    fn test_try_compile() {
659        let mut keys = vec![];
660        keys.resize_with(7, Address::new_unique);
661
662        let payer = keys[0];
663        let program_id = keys[6];
664        let instructions = vec![Instruction {
665            program_id,
666            accounts: vec![
667                AccountMeta::new(keys[1], true),
668                AccountMeta::new_readonly(keys[2], true),
669                AccountMeta::new(keys[3], false),
670                AccountMeta::new(keys[4], false), // loaded from lut
671                AccountMeta::new_readonly(keys[5], false), // loaded from lut
672            ],
673            data: vec![],
674        }];
675        let address_lookup_table_accounts = vec![
676            AddressLookupTableAccount {
677                key: Address::new_unique(),
678                addresses: vec![keys[4], keys[5], keys[6]],
679            },
680            AddressLookupTableAccount {
681                key: Address::new_unique(),
682                addresses: vec![],
683            },
684        ];
685
686        let recent_blockhash = Hash::new_unique();
687        assert_eq!(
688            Message::try_compile(
689                &payer,
690                &instructions,
691                &address_lookup_table_accounts,
692                recent_blockhash
693            ),
694            Ok(Message {
695                header: MessageHeader {
696                    num_required_signatures: 3,
697                    num_readonly_signed_accounts: 1,
698                    num_readonly_unsigned_accounts: 1
699                },
700                recent_blockhash,
701                account_keys: vec![keys[0], keys[1], keys[2], keys[3], program_id],
702                instructions: vec![CompiledInstruction {
703                    program_id_index: 4,
704                    accounts: vec![1, 2, 3, 5, 6],
705                    data: vec![],
706                },],
707                address_table_lookups: vec![MessageAddressTableLookup {
708                    account_key: address_lookup_table_accounts[0].key,
709                    writable_indexes: vec![0],
710                    readonly_indexes: vec![1],
711                }],
712            })
713        );
714    }
715
716    #[test]
717    fn test_is_maybe_writable() {
718        let key0 = Address::new_unique();
719        let key1 = Address::new_unique();
720        let key2 = Address::new_unique();
721        let key3 = Address::new_unique();
722        let key4 = Address::new_unique();
723        let key5 = Address::new_unique();
724
725        let message = Message {
726            header: MessageHeader {
727                num_required_signatures: 3,
728                num_readonly_signed_accounts: 2,
729                num_readonly_unsigned_accounts: 1,
730            },
731            account_keys: vec![key0, key1, key2, key3, key4, key5],
732            address_table_lookups: vec![MessageAddressTableLookup {
733                account_key: Address::new_unique(),
734                writable_indexes: vec![0],
735                readonly_indexes: vec![1],
736            }],
737            ..Message::default()
738        };
739
740        let reserved_account_keys = HashSet::from([key3]);
741
742        assert!(message.is_maybe_writable(0, Some(&reserved_account_keys)));
743        assert!(!message.is_maybe_writable(1, Some(&reserved_account_keys)));
744        assert!(!message.is_maybe_writable(2, Some(&reserved_account_keys)));
745        assert!(!message.is_maybe_writable(3, Some(&reserved_account_keys)));
746        assert!(message.is_maybe_writable(3, None));
747        assert!(message.is_maybe_writable(4, Some(&reserved_account_keys)));
748        assert!(!message.is_maybe_writable(5, Some(&reserved_account_keys)));
749        assert!(message.is_maybe_writable(6, Some(&reserved_account_keys)));
750        assert!(!message.is_maybe_writable(7, Some(&reserved_account_keys)));
751        assert!(!message.is_maybe_writable(8, Some(&reserved_account_keys)));
752    }
753
754    #[test]
755    fn test_is_account_maybe_reserved() {
756        let key0 = Address::new_unique();
757        let key1 = Address::new_unique();
758
759        let message = Message {
760            account_keys: vec![key0, key1],
761            address_table_lookups: vec![MessageAddressTableLookup {
762                account_key: Address::new_unique(),
763                writable_indexes: vec![0],
764                readonly_indexes: vec![1],
765            }],
766            ..Message::default()
767        };
768
769        let reserved_account_keys = HashSet::from([key1]);
770
771        assert!(!message.is_account_maybe_reserved(0, Some(&reserved_account_keys)));
772        assert!(message.is_account_maybe_reserved(1, Some(&reserved_account_keys)));
773        assert!(!message.is_account_maybe_reserved(2, Some(&reserved_account_keys)));
774        assert!(!message.is_account_maybe_reserved(3, Some(&reserved_account_keys)));
775        assert!(!message.is_account_maybe_reserved(4, Some(&reserved_account_keys)));
776        assert!(!message.is_account_maybe_reserved(0, None));
777        assert!(!message.is_account_maybe_reserved(1, None));
778        assert!(!message.is_account_maybe_reserved(2, None));
779        assert!(!message.is_account_maybe_reserved(3, None));
780        assert!(!message.is_account_maybe_reserved(4, None));
781    }
782}