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