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