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