Skip to main content

solana_message/versions/v0/
mod.rs

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