miraland_program/message/
legacy.rs

1//! The original and current Miraland 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 Miraland 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::message::legacy
9//! [`v0`]: crate::message::v0
10//! [future message format]: https://docs.miraland.top/proposals/versioned-transactions
11
12#![allow(clippy::arithmetic_side_effects)]
13
14use {
15    crate::{
16        bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable,
17        hash::Hash,
18        instruction::{CompiledInstruction, Instruction},
19        message::{compiled_keys::CompiledKeys, MessageHeader},
20        pubkey::Pubkey,
21        sanitize::{Sanitize, SanitizeError},
22        short_vec, system_instruction, system_program, sysvar, wasm_bindgen,
23    },
24    lazy_static::lazy_static,
25    std::{convert::TryFrom, str::FromStr},
26};
27
28lazy_static! {
29    // This will be deprecated and so this list shouldn't be modified
30    pub static ref BUILTIN_PROGRAMS_KEYS: [Pubkey; 10] = {
31        let parse = |s| Pubkey::from_str(s).unwrap();
32        [
33            parse("Config1111111111111111111111111111111111111"),
34            parse("Feature111111111111111111111111111111111111"),
35            parse("NativeLoader1111111111111111111111111111111"),
36            parse("Stake11111111111111111111111111111111111111"),
37            parse("StakeConfig11111111111111111111111111111111"),
38            parse("Vote111111111111111111111111111111111111111"),
39            system_program::id(),
40            bpf_loader::id(),
41            bpf_loader_deprecated::id(),
42            bpf_loader_upgradeable::id(),
43        ]
44    };
45}
46
47lazy_static! {
48    // Each element of a key is a u8. We use key[0] as an index into this table of 256 boolean
49    // elements, to store whether or not the first element of any key is present in the static
50    // lists of built-in-program keys or system ids. By using this lookup table, we can very
51    // quickly determine that a key under consideration cannot be in either of these lists (if
52    // the value is "false"), or might be in one of these lists (if the value is "true")
53    pub static ref MAYBE_BUILTIN_KEY_OR_SYSVAR: [bool; 256] = {
54        let mut temp_table: [bool; 256] = [false; 256];
55        BUILTIN_PROGRAMS_KEYS.iter().for_each(|key| temp_table[key.0[0] as usize] = true);
56        sysvar::ALL_IDS.iter().for_each(|key| temp_table[key.0[0] as usize] = true);
57        temp_table
58    };
59}
60
61pub fn is_builtin_key_or_sysvar(key: &Pubkey) -> bool {
62    if MAYBE_BUILTIN_KEY_OR_SYSVAR[key.0[0] as usize] {
63        return sysvar::is_sysvar_id(key) || BUILTIN_PROGRAMS_KEYS.contains(key);
64    }
65    false
66}
67
68fn position(keys: &[Pubkey], key: &Pubkey) -> u8 {
69    keys.iter().position(|k| k == key).unwrap() as u8
70}
71
72fn compile_instruction(ix: &Instruction, keys: &[Pubkey]) -> CompiledInstruction {
73    let accounts: Vec<_> = ix
74        .accounts
75        .iter()
76        .map(|account_meta| position(keys, &account_meta.pubkey))
77        .collect();
78
79    CompiledInstruction {
80        program_id_index: position(keys, &ix.program_id),
81        data: ix.data.clone(),
82        accounts,
83    }
84}
85
86fn compile_instructions(ixs: &[Instruction], keys: &[Pubkey]) -> Vec<CompiledInstruction> {
87    ixs.iter().map(|ix| compile_instruction(ix, keys)).collect()
88}
89
90/// A Miraland transaction message (legacy).
91///
92/// See the [`message`] module documentation for further description.
93///
94/// [`message`]: crate::message
95///
96/// Some constructors accept an optional `payer`, the account responsible for
97/// paying the cost of executing a transaction. In most cases, callers should
98/// specify the payer explicitly in these constructors. In some cases though,
99/// the caller is not _required_ to specify the payer, but is still allowed to:
100/// in the `Message` structure, the first account is always the fee-payer, so if
101/// the caller has knowledge that the first account of the constructed
102/// transaction's `Message` is both a signer and the expected fee-payer, then
103/// redundantly specifying the fee-payer is not strictly required.
104// NOTE: Serialization-related changes must be paired with the custom serialization
105// for versioned messages in the `RemainingLegacyMessage` struct.
106#[wasm_bindgen]
107#[frozen_abi(digest = "2KnLEqfLcTBQqitE22Pp8JYkaqVVbAkGbCfdeHoyxcAU")]
108#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)]
109#[serde(rename_all = "camelCase")]
110pub struct Message {
111    /// The message header, identifying signed and read-only `account_keys`.
112    // NOTE: Serialization-related changes must be paired with the direct read at sigverify.
113    #[wasm_bindgen(skip)]
114    pub header: MessageHeader,
115
116    /// All the account keys used by this transaction.
117    #[wasm_bindgen(skip)]
118    #[serde(with = "short_vec")]
119    pub account_keys: Vec<Pubkey>,
120
121    /// The id of a recent ledger entry.
122    pub recent_blockhash: Hash,
123
124    /// Programs that will be executed in sequence and committed in one atomic transaction if all
125    /// succeed.
126    #[wasm_bindgen(skip)]
127    #[serde(with = "short_vec")]
128    pub instructions: Vec<CompiledInstruction>,
129}
130
131impl Sanitize for Message {
132    fn sanitize(&self) -> std::result::Result<(), SanitizeError> {
133        // signing area and read-only non-signing area should not overlap
134        if self.header.num_required_signatures as usize
135            + self.header.num_readonly_unsigned_accounts as usize
136            > self.account_keys.len()
137        {
138            return Err(SanitizeError::IndexOutOfBounds);
139        }
140
141        // there should be at least 1 RW fee-payer account.
142        if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures {
143            return Err(SanitizeError::IndexOutOfBounds);
144        }
145
146        for ci in &self.instructions {
147            if ci.program_id_index as usize >= self.account_keys.len() {
148                return Err(SanitizeError::IndexOutOfBounds);
149            }
150            // A program cannot be a payer.
151            if ci.program_id_index == 0 {
152                return Err(SanitizeError::IndexOutOfBounds);
153            }
154            for ai in &ci.accounts {
155                if *ai as usize >= self.account_keys.len() {
156                    return Err(SanitizeError::IndexOutOfBounds);
157                }
158            }
159        }
160        self.account_keys.sanitize()?;
161        self.recent_blockhash.sanitize()?;
162        self.instructions.sanitize()?;
163        Ok(())
164    }
165}
166
167impl Message {
168    /// Create a new `Message`.
169    ///
170    /// # Examples
171    ///
172    /// This example uses the [`miraland_sdk`], [`miraland_rpc_client`] and [`anyhow`] crates.
173    ///
174    /// [`miraland_sdk`]: https://docs.rs/miraland-sdk
175    /// [`miraland_rpc_client`]: https://docs.rs/miraland-rpc-client
176    /// [`anyhow`]: https://docs.rs/anyhow
177    ///
178    /// ```
179    /// # use miraland_program::example_mocks::miraland_sdk;
180    /// # use miraland_program::example_mocks::miraland_rpc_client;
181    /// use anyhow::Result;
182    /// use borsh::{BorshSerialize, BorshDeserialize};
183    /// use miraland_rpc_client::rpc_client::RpcClient;
184    /// use miraland_sdk::{
185    ///     instruction::Instruction,
186    ///     message::Message,
187    ///     pubkey::Pubkey,
188    ///     signature::{Keypair, Signer},
189    ///     transaction::Transaction,
190    /// };
191    ///
192    /// // A custom program instruction. This would typically be defined in
193    /// // another crate so it can be shared between the on-chain program and
194    /// // the client.
195    /// #[derive(BorshSerialize, BorshDeserialize)]
196    /// # #[borsh(crate = "borsh")]
197    /// enum BankInstruction {
198    ///     Initialize,
199    ///     Deposit { lamports: u64 },
200    ///     Withdraw { lamports: u64 },
201    /// }
202    ///
203    /// fn send_initialize_tx(
204    ///     client: &RpcClient,
205    ///     program_id: Pubkey,
206    ///     payer: &Keypair
207    /// ) -> Result<()> {
208    ///
209    ///     let bank_instruction = BankInstruction::Initialize;
210    ///
211    ///     let instruction = Instruction::new_with_borsh(
212    ///         program_id,
213    ///         &bank_instruction,
214    ///         vec![],
215    ///     );
216    ///
217    ///     let message = Message::new(
218    ///         &[instruction],
219    ///         Some(&payer.pubkey()),
220    ///     );
221    ///
222    ///     let blockhash = client.get_latest_blockhash()?;
223    ///     let mut tx = Transaction::new(&[payer], message, blockhash);
224    ///     client.send_and_confirm_transaction(&tx)?;
225    ///
226    ///     Ok(())
227    /// }
228    /// #
229    /// # let client = RpcClient::new(String::new());
230    /// # let program_id = Pubkey::new_unique();
231    /// # let payer = Keypair::new();
232    /// # send_initialize_tx(&client, program_id, &payer)?;
233    /// #
234    /// # Ok::<(), anyhow::Error>(())
235    /// ```
236    pub fn new(instructions: &[Instruction], payer: Option<&Pubkey>) -> Self {
237        Self::new_with_blockhash(instructions, payer, &Hash::default())
238    }
239
240    /// Create a new message while setting the blockhash.
241    ///
242    /// # Examples
243    ///
244    /// This example uses the [`miraland_sdk`], [`miraland_rpc_client`] and [`anyhow`] crates.
245    ///
246    /// [`miraland_sdk`]: https://docs.rs/miraland-sdk
247    /// [`miraland_rpc_client`]: https://docs.rs/miraland-rpc-client
248    /// [`anyhow`]: https://docs.rs/anyhow
249    ///
250    /// ```
251    /// # use miraland_program::example_mocks::miraland_sdk;
252    /// # use miraland_program::example_mocks::miraland_rpc_client;
253    /// use anyhow::Result;
254    /// use borsh::{BorshSerialize, BorshDeserialize};
255    /// use miraland_rpc_client::rpc_client::RpcClient;
256    /// use miraland_sdk::{
257    ///     instruction::Instruction,
258    ///     message::Message,
259    ///     pubkey::Pubkey,
260    ///     signature::{Keypair, Signer},
261    ///     transaction::Transaction,
262    /// };
263    ///
264    /// // A custom program instruction. This would typically be defined in
265    /// // another crate so it can be shared between the on-chain program and
266    /// // the client.
267    /// #[derive(BorshSerialize, BorshDeserialize)]
268    /// # #[borsh(crate = "borsh")]
269    /// enum BankInstruction {
270    ///     Initialize,
271    ///     Deposit { lamports: u64 },
272    ///     Withdraw { lamports: u64 },
273    /// }
274    ///
275    /// fn send_initialize_tx(
276    ///     client: &RpcClient,
277    ///     program_id: Pubkey,
278    ///     payer: &Keypair
279    /// ) -> Result<()> {
280    ///
281    ///     let bank_instruction = BankInstruction::Initialize;
282    ///
283    ///     let instruction = Instruction::new_with_borsh(
284    ///         program_id,
285    ///         &bank_instruction,
286    ///         vec![],
287    ///     );
288    ///
289    ///     let blockhash = client.get_latest_blockhash()?;
290    ///
291    ///     let message = Message::new_with_blockhash(
292    ///         &[instruction],
293    ///         Some(&payer.pubkey()),
294    ///         &blockhash,
295    ///     );
296    ///
297    ///     let mut tx = Transaction::new_unsigned(message);
298    ///     tx.sign(&[payer], tx.message.recent_blockhash);
299    ///     client.send_and_confirm_transaction(&tx)?;
300    ///
301    ///     Ok(())
302    /// }
303    /// #
304    /// # let client = RpcClient::new(String::new());
305    /// # let program_id = Pubkey::new_unique();
306    /// # let payer = Keypair::new();
307    /// # send_initialize_tx(&client, program_id, &payer)?;
308    /// #
309    /// # Ok::<(), anyhow::Error>(())
310    /// ```
311    pub fn new_with_blockhash(
312        instructions: &[Instruction],
313        payer: Option<&Pubkey>,
314        blockhash: &Hash,
315    ) -> Self {
316        let compiled_keys = CompiledKeys::compile(instructions, payer.cloned());
317        let (header, account_keys) = compiled_keys
318            .try_into_message_components()
319            .expect("overflow when compiling message keys");
320        let instructions = compile_instructions(instructions, &account_keys);
321        Self::new_with_compiled_instructions(
322            header.num_required_signatures,
323            header.num_readonly_signed_accounts,
324            header.num_readonly_unsigned_accounts,
325            account_keys,
326            *blockhash,
327            instructions,
328        )
329    }
330
331    /// Create a new message for a [nonced transaction].
332    ///
333    /// [nonced transaction]: https://docs.solana.com/implemented-proposals/durable-tx-nonces
334    ///
335    /// In this type of transaction, the blockhash is replaced with a _durable
336    /// transaction nonce_, allowing for extended time to pass between the
337    /// transaction's signing and submission to the blockchain.
338    ///
339    /// # Examples
340    ///
341    /// This example uses the [`miraland_sdk`], [`miraland_rpc_client`] and [`anyhow`] crates.
342    ///
343    /// [`miraland_sdk`]: https://docs.rs/miraland-sdk
344    /// [`miraland_rpc_client`]: https://docs.rs/miraland-client
345    /// [`anyhow`]: https://docs.rs/anyhow
346    ///
347    /// ```
348    /// # use miraland_program::example_mocks::miraland_sdk;
349    /// # use miraland_program::example_mocks::miraland_rpc_client;
350    /// use anyhow::Result;
351    /// use borsh::{BorshSerialize, BorshDeserialize};
352    /// use miraland_rpc_client::rpc_client::RpcClient;
353    /// use miraland_sdk::{
354    ///     hash::Hash,
355    ///     instruction::Instruction,
356    ///     message::Message,
357    ///     nonce,
358    ///     pubkey::Pubkey,
359    ///     signature::{Keypair, Signer},
360    ///     system_instruction,
361    ///     transaction::Transaction,
362    /// };
363    ///
364    /// // A custom program instruction. This would typically be defined in
365    /// // another crate so it can be shared between the on-chain program and
366    /// // the client.
367    /// #[derive(BorshSerialize, BorshDeserialize)]
368    /// # #[borsh(crate = "borsh")]
369    /// enum BankInstruction {
370    ///     Initialize,
371    ///     Deposit { lamports: u64 },
372    ///     Withdraw { lamports: u64 },
373    /// }
374    ///
375    /// // Create a nonced transaction for later signing and submission,
376    /// // returning it and the nonce account's pubkey.
377    /// fn create_offline_initialize_tx(
378    ///     client: &RpcClient,
379    ///     program_id: Pubkey,
380    ///     payer: &Keypair
381    /// ) -> Result<(Transaction, Pubkey)> {
382    ///
383    ///     let bank_instruction = BankInstruction::Initialize;
384    ///     let bank_instruction = Instruction::new_with_borsh(
385    ///         program_id,
386    ///         &bank_instruction,
387    ///         vec![],
388    ///     );
389    ///
390    ///     // This will create a nonce account and assign authority to the
391    ///     // payer so they can sign to advance the nonce and withdraw its rent.
392    ///     let nonce_account = make_nonce_account(client, payer)?;
393    ///
394    ///     let mut message = Message::new_with_nonce(
395    ///         vec![bank_instruction],
396    ///         Some(&payer.pubkey()),
397    ///         &nonce_account,
398    ///         &payer.pubkey()
399    ///     );
400    ///
401    ///     // This transaction will need to be signed later, using the blockhash
402    ///     // stored in the nonce account.
403    ///     let tx = Transaction::new_unsigned(message);
404    ///
405    ///     Ok((tx, nonce_account))
406    /// }
407    ///
408    /// fn make_nonce_account(client: &RpcClient, payer: &Keypair)
409    ///     -> Result<Pubkey>
410    /// {
411    ///     let nonce_account_address = Keypair::new();
412    ///     let nonce_account_size = nonce::State::size();
413    ///     let nonce_rent = client.get_minimum_balance_for_rent_exemption(nonce_account_size)?;
414    ///
415    ///     // Assigning the nonce authority to the payer so they can sign for the withdrawal,
416    ///     // and we can throw away the nonce address secret key.
417    ///     let create_nonce_instr = system_instruction::create_nonce_account(
418    ///         &payer.pubkey(),
419    ///         &nonce_account_address.pubkey(),
420    ///         &payer.pubkey(),
421    ///         nonce_rent,
422    ///     );
423    ///
424    ///     let mut nonce_tx = Transaction::new_with_payer(&create_nonce_instr, Some(&payer.pubkey()));
425    ///     let blockhash = client.get_latest_blockhash()?;
426    ///     nonce_tx.sign(&[&payer, &nonce_account_address], blockhash);
427    ///     client.send_and_confirm_transaction(&nonce_tx)?;
428    ///
429    ///     Ok(nonce_account_address.pubkey())
430    /// }
431    /// #
432    /// # let client = RpcClient::new(String::new());
433    /// # let program_id = Pubkey::new_unique();
434    /// # let payer = Keypair::new();
435    /// # create_offline_initialize_tx(&client, program_id, &payer)?;
436    /// # Ok::<(), anyhow::Error>(())
437    /// ```
438    pub fn new_with_nonce(
439        mut instructions: Vec<Instruction>,
440        payer: Option<&Pubkey>,
441        nonce_account_pubkey: &Pubkey,
442        nonce_authority_pubkey: &Pubkey,
443    ) -> Self {
444        let nonce_ix =
445            system_instruction::advance_nonce_account(nonce_account_pubkey, nonce_authority_pubkey);
446        instructions.insert(0, nonce_ix);
447        Self::new(&instructions, payer)
448    }
449
450    pub fn new_with_compiled_instructions(
451        num_required_signatures: u8,
452        num_readonly_signed_accounts: u8,
453        num_readonly_unsigned_accounts: u8,
454        account_keys: Vec<Pubkey>,
455        recent_blockhash: Hash,
456        instructions: Vec<CompiledInstruction>,
457    ) -> Self {
458        Self {
459            header: MessageHeader {
460                num_required_signatures,
461                num_readonly_signed_accounts,
462                num_readonly_unsigned_accounts,
463            },
464            account_keys,
465            recent_blockhash,
466            instructions,
467        }
468    }
469
470    /// Compute the blake3 hash of this transaction's message.
471    #[cfg(not(target_os = "solana"))]
472    pub fn hash(&self) -> Hash {
473        let message_bytes = self.serialize();
474        Self::hash_raw_message(&message_bytes)
475    }
476
477    /// Compute the blake3 hash of a raw transaction message.
478    #[cfg(not(target_os = "solana"))]
479    pub fn hash_raw_message(message_bytes: &[u8]) -> Hash {
480        use blake3::traits::digest::Digest;
481        let mut hasher = blake3::Hasher::new();
482        hasher.update(b"miraland-tx-message-v1");
483        hasher.update(message_bytes);
484        Hash(hasher.finalize().into())
485    }
486
487    pub fn compile_instruction(&self, ix: &Instruction) -> CompiledInstruction {
488        compile_instruction(ix, &self.account_keys)
489    }
490
491    pub fn serialize(&self) -> Vec<u8> {
492        bincode::serialize(self).unwrap()
493    }
494
495    pub fn program_id(&self, instruction_index: usize) -> Option<&Pubkey> {
496        Some(
497            &self.account_keys[self.instructions.get(instruction_index)?.program_id_index as usize],
498        )
499    }
500
501    pub fn program_index(&self, instruction_index: usize) -> Option<usize> {
502        Some(self.instructions.get(instruction_index)?.program_id_index as usize)
503    }
504
505    pub fn program_ids(&self) -> Vec<&Pubkey> {
506        self.instructions
507            .iter()
508            .map(|ix| &self.account_keys[ix.program_id_index as usize])
509            .collect()
510    }
511
512    pub fn is_key_passed_to_program(&self, key_index: usize) -> bool {
513        if let Ok(key_index) = u8::try_from(key_index) {
514            self.instructions
515                .iter()
516                .any(|ix| ix.accounts.contains(&key_index))
517        } else {
518            false
519        }
520    }
521
522    pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
523        if let Ok(key_index) = u8::try_from(key_index) {
524            self.instructions
525                .iter()
526                .any(|ix| ix.program_id_index == key_index)
527        } else {
528            false
529        }
530    }
531
532    pub fn is_non_loader_key(&self, key_index: usize) -> bool {
533        !self.is_key_called_as_program(key_index) || self.is_key_passed_to_program(key_index)
534    }
535
536    pub fn program_position(&self, index: usize) -> Option<usize> {
537        let program_ids = self.program_ids();
538        program_ids
539            .iter()
540            .position(|&&pubkey| pubkey == self.account_keys[index])
541    }
542
543    pub fn maybe_executable(&self, i: usize) -> bool {
544        self.program_position(i).is_some()
545    }
546
547    pub fn demote_program_id(&self, i: usize) -> bool {
548        self.is_key_called_as_program(i) && !self.is_upgradeable_loader_present()
549    }
550
551    pub fn is_writable(&self, i: usize) -> bool {
552        (i < (self.header.num_required_signatures - self.header.num_readonly_signed_accounts)
553            as usize
554            || (i >= self.header.num_required_signatures as usize
555                && i < self.account_keys.len()
556                    - self.header.num_readonly_unsigned_accounts as usize))
557            && !is_builtin_key_or_sysvar(&self.account_keys[i])
558            && !self.demote_program_id(i)
559    }
560
561    pub fn is_signer(&self, i: usize) -> bool {
562        i < self.header.num_required_signatures as usize
563    }
564
565    #[deprecated]
566    pub fn get_account_keys_by_lock_type(&self) -> (Vec<&Pubkey>, Vec<&Pubkey>) {
567        let mut writable_keys = vec![];
568        let mut readonly_keys = vec![];
569        for (i, key) in self.account_keys.iter().enumerate() {
570            if self.is_writable(i) {
571                writable_keys.push(key);
572            } else {
573                readonly_keys.push(key);
574            }
575        }
576        (writable_keys, readonly_keys)
577    }
578
579    #[deprecated]
580    pub fn deserialize_instruction(
581        index: usize,
582        data: &[u8],
583    ) -> Result<Instruction, SanitizeError> {
584        #[allow(deprecated)]
585        sysvar::instructions::load_instruction_at(index, data)
586    }
587
588    pub fn signer_keys(&self) -> Vec<&Pubkey> {
589        // Clamp in case we're working on un-`sanitize()`ed input
590        let last_key = self
591            .account_keys
592            .len()
593            .min(self.header.num_required_signatures as usize);
594        self.account_keys[..last_key].iter().collect()
595    }
596
597    /// Returns `true` if `account_keys` has any duplicate keys.
598    pub fn has_duplicates(&self) -> bool {
599        // Note: This is an O(n^2) algorithm, but requires no heap allocations. The benchmark
600        // `bench_has_duplicates` in benches/message_processor.rs shows that this implementation is
601        // ~50 times faster than using HashSet for very short slices.
602        for i in 1..self.account_keys.len() {
603            #[allow(clippy::arithmetic_side_effects)]
604            if self.account_keys[i..].contains(&self.account_keys[i - 1]) {
605                return true;
606            }
607        }
608        false
609    }
610
611    /// Returns `true` if any account is the BPF upgradeable loader.
612    pub fn is_upgradeable_loader_present(&self) -> bool {
613        self.account_keys
614            .iter()
615            .any(|&key| key == bpf_loader_upgradeable::id())
616    }
617}
618
619#[cfg(test)]
620mod tests {
621    #![allow(deprecated)]
622    use {
623        super::*,
624        crate::{hash, instruction::AccountMeta, message::MESSAGE_HEADER_LENGTH},
625        std::collections::HashSet,
626    };
627
628    #[test]
629    fn test_builtin_program_keys() {
630        let keys: HashSet<Pubkey> = BUILTIN_PROGRAMS_KEYS.iter().copied().collect();
631        assert_eq!(keys.len(), 10);
632        for k in keys {
633            let k = format!("{k}");
634            assert!(k.ends_with("11111111111111111111111"));
635        }
636    }
637
638    #[test]
639    fn test_builtin_program_keys_abi_freeze() {
640        // Once the feature is flipped on, we can't further modify
641        // BUILTIN_PROGRAMS_KEYS without the risk of breaking consensus.
642        let builtins = format!("{:?}", *BUILTIN_PROGRAMS_KEYS);
643        assert_eq!(
644            format!("{}", hash::hash(builtins.as_bytes())),
645            "ACqmMkYbo9eqK6QrRSrB3HLyR6uHhLf31SCfGUAJjiWj"
646        );
647    }
648
649    #[test]
650    // Ensure there's a way to calculate the number of required signatures.
651    fn test_message_signed_keys_len() {
652        let program_id = Pubkey::default();
653        let id0 = Pubkey::default();
654        let ix = Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, false)]);
655        let message = Message::new(&[ix], None);
656        assert_eq!(message.header.num_required_signatures, 0);
657
658        let ix = Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, true)]);
659        let message = Message::new(&[ix], Some(&id0));
660        assert_eq!(message.header.num_required_signatures, 1);
661    }
662
663    #[test]
664    fn test_message_kitchen_sink() {
665        let program_id0 = Pubkey::new_unique();
666        let program_id1 = Pubkey::new_unique();
667        let id0 = Pubkey::default();
668        let id1 = Pubkey::new_unique();
669        let message = Message::new(
670            &[
671                Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id0, false)]),
672                Instruction::new_with_bincode(program_id1, &0, vec![AccountMeta::new(id1, true)]),
673                Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id1, false)]),
674            ],
675            Some(&id1),
676        );
677        assert_eq!(
678            message.instructions[0],
679            CompiledInstruction::new(2, &0, vec![1])
680        );
681        assert_eq!(
682            message.instructions[1],
683            CompiledInstruction::new(3, &0, vec![0])
684        );
685        assert_eq!(
686            message.instructions[2],
687            CompiledInstruction::new(2, &0, vec![0])
688        );
689    }
690
691    #[test]
692    fn test_message_payer_first() {
693        let program_id = Pubkey::default();
694        let payer = Pubkey::new_unique();
695        let id0 = Pubkey::default();
696
697        let ix = Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, false)]);
698        let message = Message::new(&[ix], Some(&payer));
699        assert_eq!(message.header.num_required_signatures, 1);
700
701        let ix = Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, true)]);
702        let message = Message::new(&[ix], Some(&payer));
703        assert_eq!(message.header.num_required_signatures, 2);
704
705        let ix = Instruction::new_with_bincode(
706            program_id,
707            &0,
708            vec![AccountMeta::new(payer, true), AccountMeta::new(id0, true)],
709        );
710        let message = Message::new(&[ix], Some(&payer));
711        assert_eq!(message.header.num_required_signatures, 2);
712    }
713
714    #[test]
715    fn test_program_position() {
716        let program_id0 = Pubkey::default();
717        let program_id1 = Pubkey::new_unique();
718        let id = Pubkey::new_unique();
719        let message = Message::new(
720            &[
721                Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id, false)]),
722                Instruction::new_with_bincode(program_id1, &0, vec![AccountMeta::new(id, true)]),
723            ],
724            Some(&id),
725        );
726        assert_eq!(message.program_position(0), None);
727        assert_eq!(message.program_position(1), Some(0));
728        assert_eq!(message.program_position(2), Some(1));
729    }
730
731    #[test]
732    fn test_is_writable() {
733        let key0 = Pubkey::new_unique();
734        let key1 = Pubkey::new_unique();
735        let key2 = Pubkey::new_unique();
736        let key3 = Pubkey::new_unique();
737        let key4 = Pubkey::new_unique();
738        let key5 = Pubkey::new_unique();
739
740        let message = Message {
741            header: MessageHeader {
742                num_required_signatures: 3,
743                num_readonly_signed_accounts: 2,
744                num_readonly_unsigned_accounts: 1,
745            },
746            account_keys: vec![key0, key1, key2, key3, key4, key5],
747            recent_blockhash: Hash::default(),
748            instructions: vec![],
749        };
750        assert!(message.is_writable(0));
751        assert!(!message.is_writable(1));
752        assert!(!message.is_writable(2));
753        assert!(message.is_writable(3));
754        assert!(message.is_writable(4));
755        assert!(!message.is_writable(5));
756    }
757
758    #[test]
759    fn test_get_account_keys_by_lock_type() {
760        let program_id = Pubkey::default();
761        let id0 = Pubkey::new_unique();
762        let id1 = Pubkey::new_unique();
763        let id2 = Pubkey::new_unique();
764        let id3 = Pubkey::new_unique();
765        let message = Message::new(
766            &[
767                Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, false)]),
768                Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id1, true)]),
769                Instruction::new_with_bincode(
770                    program_id,
771                    &0,
772                    vec![AccountMeta::new_readonly(id2, false)],
773                ),
774                Instruction::new_with_bincode(
775                    program_id,
776                    &0,
777                    vec![AccountMeta::new_readonly(id3, true)],
778                ),
779            ],
780            Some(&id1),
781        );
782        assert_eq!(
783            message.get_account_keys_by_lock_type(),
784            (vec![&id1, &id0], vec![&id3, &program_id, &id2])
785        );
786    }
787
788    #[test]
789    fn test_program_ids() {
790        let key0 = Pubkey::new_unique();
791        let key1 = Pubkey::new_unique();
792        let loader2 = Pubkey::new_unique();
793        let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
794        let message = Message::new_with_compiled_instructions(
795            1,
796            0,
797            2,
798            vec![key0, key1, loader2],
799            Hash::default(),
800            instructions,
801        );
802        assert_eq!(message.program_ids(), vec![&loader2]);
803    }
804
805    #[test]
806    fn test_is_key_passed_to_program() {
807        let key0 = Pubkey::new_unique();
808        let key1 = Pubkey::new_unique();
809        let loader2 = Pubkey::new_unique();
810        let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
811        let message = Message::new_with_compiled_instructions(
812            1,
813            0,
814            2,
815            vec![key0, key1, loader2],
816            Hash::default(),
817            instructions,
818        );
819
820        assert!(message.is_key_passed_to_program(0));
821        assert!(message.is_key_passed_to_program(1));
822        assert!(!message.is_key_passed_to_program(2));
823    }
824
825    #[test]
826    fn test_is_non_loader_key() {
827        let key0 = Pubkey::new_unique();
828        let key1 = Pubkey::new_unique();
829        let loader2 = Pubkey::new_unique();
830        let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
831        let message = Message::new_with_compiled_instructions(
832            1,
833            0,
834            2,
835            vec![key0, key1, loader2],
836            Hash::default(),
837            instructions,
838        );
839        assert!(message.is_non_loader_key(0));
840        assert!(message.is_non_loader_key(1));
841        assert!(!message.is_non_loader_key(2));
842    }
843
844    #[test]
845    fn test_message_header_len_constant() {
846        assert_eq!(
847            bincode::serialized_size(&MessageHeader::default()).unwrap() as usize,
848            MESSAGE_HEADER_LENGTH
849        );
850    }
851
852    #[test]
853    fn test_message_hash() {
854        // when this test fails, it's most likely due to a new serialized format of a message.
855        // in this case, the domain prefix `miraland-tx-message-v1` should be updated.
856        let program_id0 = Pubkey::from_str("4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM").unwrap();
857        let program_id1 = Pubkey::from_str("8opHzTAnfzRpPEx21XtnrVTX28YQuCpAjcn1PczScKh").unwrap();
858        let id0 = Pubkey::from_str("CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3").unwrap();
859        let id1 = Pubkey::from_str("GcdayuLaLyrdmUu324nahyv33G5poQdLUEZ1nEytDeP").unwrap();
860        let id2 = Pubkey::from_str("LX3EUdRUBUa3TbsYXLEUdj9J3prXkWXvLYSWyYyc2Jj").unwrap();
861        let id3 = Pubkey::from_str("QRSsyMWN1yHT9ir42bgNZUNZ4PdEhcSWCrL2AryKpy5").unwrap();
862        let instructions = vec![
863            Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id0, false)]),
864            Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id1, true)]),
865            Instruction::new_with_bincode(
866                program_id1,
867                &0,
868                vec![AccountMeta::new_readonly(id2, false)],
869            ),
870            Instruction::new_with_bincode(
871                program_id1,
872                &0,
873                vec![AccountMeta::new_readonly(id3, true)],
874            ),
875        ];
876
877        let message = Message::new(&instructions, Some(&id1));
878        assert_eq!(
879            message.hash(),
880            Hash::from_str("7VWCF4quo2CcWQFNUayZiorxpiR5ix8YzLebrXKf3fMF").unwrap()
881        )
882    }
883}