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