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