solana_program/message/
legacy.rs

1//! The original and current Safecoin 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 Safecoin 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.solana.com/proposals/transactions-v2
11
12#![allow(clippy::integer_arithmetic)]
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    // Copied keys over since direct references create cyclical dependency.
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 Safecoin 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 [`solana_sdk`], [`safecoin_client`] and [`anyhow`] crates.
173    ///
174    /// [`solana_sdk`]: https://docs.rs/safecoin-sdk
175    /// [`safecoin_client`]: https://docs.rs/safecoin-client
176    /// [`anyhow`]: https://docs.rs/anyhow
177    ///
178    /// ```
179    /// # use solana_program::example_mocks::solana_sdk;
180    /// # use solana_program::example_mocks::safecoin_client;
181    /// use anyhow::Result;
182    /// use borsh::{BorshSerialize, BorshDeserialize};
183    /// use safecoin_client::rpc_client::RpcClient;
184    /// use solana_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    /// enum BankInstruction {
197    ///     Initialize,
198    ///     Deposit { lamports: u64 },
199    ///     Withdraw { lamports: u64 },
200    /// }
201    ///
202    /// fn send_initialize_tx(
203    ///     client: &RpcClient,
204    ///     program_id: Pubkey,
205    ///     payer: &Keypair
206    /// ) -> Result<()> {
207    ///
208    ///     let bank_instruction = BankInstruction::Initialize;
209    ///
210    ///     let instruction = Instruction::new_with_borsh(
211    ///         program_id,
212    ///         &bank_instruction,
213    ///         vec![],
214    ///     );
215    ///
216    ///     let message = Message::new(
217    ///         &[instruction],
218    ///         Some(&payer.pubkey()),
219    ///     );
220    ///
221    ///     let blockhash = client.get_latest_blockhash()?;
222    ///     let mut tx = Transaction::new(&[payer], message, blockhash);
223    ///     client.send_and_confirm_transaction(&tx)?;
224    ///
225    ///     Ok(())
226    /// }
227    /// #
228    /// # let client = RpcClient::new(String::new());
229    /// # let program_id = Pubkey::new_unique();
230    /// # let payer = Keypair::new();
231    /// # send_initialize_tx(&client, program_id, &payer)?;
232    /// #
233    /// # Ok::<(), anyhow::Error>(())
234    /// ```
235    pub fn new(instructions: &[Instruction], payer: Option<&Pubkey>) -> Self {
236        Self::new_with_blockhash(instructions, payer, &Hash::default())
237    }
238
239    /// Create a new message while setting the blockhash.
240    ///
241    /// # Examples
242    ///
243    /// This example uses the [`solana_sdk`], [`safecoin_client`] and [`anyhow`] crates.
244    ///
245    /// [`solana_sdk`]: https://docs.rs/safecoin-sdk
246    /// [`safecoin_client`]: https://docs.rs/safecoin-client
247    /// [`anyhow`]: https://docs.rs/anyhow
248    ///
249    /// ```
250    /// # use solana_program::example_mocks::solana_sdk;
251    /// # use solana_program::example_mocks::safecoin_client;
252    /// use anyhow::Result;
253    /// use borsh::{BorshSerialize, BorshDeserialize};
254    /// use safecoin_client::rpc_client::RpcClient;
255    /// use solana_sdk::{
256    ///     instruction::Instruction,
257    ///     message::Message,
258    ///     pubkey::Pubkey,
259    ///     signature::{Keypair, Signer},
260    ///     transaction::Transaction,
261    /// };
262    ///
263    /// // A custom program instruction. This would typically be defined in
264    /// // another crate so it can be shared between the on-chain program and
265    /// // the client.
266    /// #[derive(BorshSerialize, BorshDeserialize)]
267    /// enum BankInstruction {
268    ///     Initialize,
269    ///     Deposit { lamports: u64 },
270    ///     Withdraw { lamports: u64 },
271    /// }
272    ///
273    /// fn send_initialize_tx(
274    ///     client: &RpcClient,
275    ///     program_id: Pubkey,
276    ///     payer: &Keypair
277    /// ) -> Result<()> {
278    ///
279    ///     let bank_instruction = BankInstruction::Initialize;
280    ///
281    ///     let instruction = Instruction::new_with_borsh(
282    ///         program_id,
283    ///         &bank_instruction,
284    ///         vec![],
285    ///     );
286    ///
287    ///     let blockhash = client.get_latest_blockhash()?;
288    ///
289    ///     let message = Message::new_with_blockhash(
290    ///         &[instruction],
291    ///         Some(&payer.pubkey()),
292    ///         &blockhash,
293    ///     );
294    ///
295    ///     let mut tx = Transaction::new_unsigned(message);
296    ///     tx.sign(&[payer], tx.message.recent_blockhash);
297    ///     client.send_and_confirm_transaction(&tx)?;
298    ///
299    ///     Ok(())
300    /// }
301    /// #
302    /// # let client = RpcClient::new(String::new());
303    /// # let program_id = Pubkey::new_unique();
304    /// # let payer = Keypair::new();
305    /// # send_initialize_tx(&client, program_id, &payer)?;
306    /// #
307    /// # Ok::<(), anyhow::Error>(())
308    /// ```
309    pub fn new_with_blockhash(
310        instructions: &[Instruction],
311        payer: Option<&Pubkey>,
312        blockhash: &Hash,
313    ) -> Self {
314        let compiled_keys = CompiledKeys::compile(instructions, payer.cloned());
315        let (header, account_keys) = compiled_keys
316            .try_into_message_components()
317            .expect("overflow when compiling message keys");
318        let instructions = compile_instructions(instructions, &account_keys);
319        Self::new_with_compiled_instructions(
320            header.num_required_signatures,
321            header.num_readonly_signed_accounts,
322            header.num_readonly_unsigned_accounts,
323            account_keys,
324            *blockhash,
325            instructions,
326        )
327    }
328
329    /// Create a new message for a [nonced transaction].
330    ///
331    /// [nonced transaction]: https://docs.solana.com/implemented-proposals/durable-tx-nonces
332    ///
333    /// In this type of transaction, the blockhash is replaced with a _durable
334    /// transaction nonce_, allowing for extended time to pass between the
335    /// transaction's signing and submission to the blockchain.
336    ///
337    /// # Examples
338    ///
339    /// This example uses the [`solana_sdk`], [`safecoin_client`] and [`anyhow`] crates.
340    ///
341    /// [`solana_sdk`]: https://docs.rs/safecoin-sdk
342    /// [`safecoin_client`]: https://docs.rs/safecoin-client
343    /// [`anyhow`]: https://docs.rs/anyhow
344    ///
345    /// ```
346    /// # use solana_program::example_mocks::solana_sdk;
347    /// # use solana_program::example_mocks::safecoin_client;
348    /// use anyhow::Result;
349    /// use borsh::{BorshSerialize, BorshDeserialize};
350    /// use safecoin_client::rpc_client::RpcClient;
351    /// use solana_sdk::{
352    ///     hash::Hash,
353    ///     instruction::Instruction,
354    ///     message::Message,
355    ///     nonce,
356    ///     pubkey::Pubkey,
357    ///     signature::{Keypair, Signer},
358    ///     system_instruction,
359    ///     transaction::Transaction,
360    /// };
361    ///
362    /// // A custom program instruction. This would typically be defined in
363    /// // another crate so it can be shared between the on-chain program and
364    /// // the client.
365    /// #[derive(BorshSerialize, BorshDeserialize)]
366    /// enum BankInstruction {
367    ///     Initialize,
368    ///     Deposit { lamports: u64 },
369    ///     Withdraw { lamports: u64 },
370    /// }
371    ///
372    /// // Create a nonced transaction for later signing and submission,
373    /// // returning it and the nonce account's pubkey.
374    /// fn create_offline_initialize_tx(
375    ///     client: &RpcClient,
376    ///     program_id: Pubkey,
377    ///     payer: &Keypair
378    /// ) -> Result<(Transaction, Pubkey)> {
379    ///
380    ///     let bank_instruction = BankInstruction::Initialize;
381    ///     let bank_instruction = Instruction::new_with_borsh(
382    ///         program_id,
383    ///         &bank_instruction,
384    ///         vec![],
385    ///     );
386    ///
387    ///     // This will create a nonce account and assign authority to the
388    ///     // payer so they can sign to advance the nonce and withdraw its rent.
389    ///     let nonce_account = make_nonce_account(client, payer)?;
390    ///
391    ///     let mut message = Message::new_with_nonce(
392    ///         vec![bank_instruction],
393    ///         Some(&payer.pubkey()),
394    ///         &nonce_account,
395    ///         &payer.pubkey()
396    ///     );
397    ///
398    ///     // This transaction will need to be signed later, using the blockhash
399    ///     // stored in the nonce account.
400    ///     let tx = Transaction::new_unsigned(message);
401    ///
402    ///     Ok((tx, nonce_account))
403    /// }
404    ///
405    /// fn make_nonce_account(client: &RpcClient, payer: &Keypair)
406    ///     -> Result<Pubkey>
407    /// {
408    ///     let nonce_account_address = Keypair::new();
409    ///     let nonce_account_size = nonce::State::size();
410    ///     let nonce_rent = client.get_minimum_balance_for_rent_exemption(nonce_account_size)?;
411    ///
412    ///     // Assigning the nonce authority to the payer so they can sign for the withdrawal,
413    ///     // and we can throw away the nonce address secret key.
414    ///     let create_nonce_instr = system_instruction::create_nonce_account(
415    ///         &payer.pubkey(),
416    ///         &nonce_account_address.pubkey(),
417    ///         &payer.pubkey(),
418    ///         nonce_rent,
419    ///     );
420    ///
421    ///     let mut nonce_tx = Transaction::new_with_payer(&create_nonce_instr, Some(&payer.pubkey()));
422    ///     let blockhash = client.get_latest_blockhash()?;
423    ///     nonce_tx.sign(&[&payer, &nonce_account_address], blockhash);
424    ///     client.send_and_confirm_transaction(&nonce_tx)?;
425    ///
426    ///     Ok(nonce_account_address.pubkey())
427    /// }
428    /// #
429    /// # let client = RpcClient::new(String::new());
430    /// # let program_id = Pubkey::new_unique();
431    /// # let payer = Keypair::new();
432    /// # create_offline_initialize_tx(&client, program_id, &payer)?;
433    /// # Ok::<(), anyhow::Error>(())
434    /// ```
435    pub fn new_with_nonce(
436        mut instructions: Vec<Instruction>,
437        payer: Option<&Pubkey>,
438        nonce_account_pubkey: &Pubkey,
439        nonce_authority_pubkey: &Pubkey,
440    ) -> Self {
441        let nonce_ix =
442            system_instruction::advance_nonce_account(nonce_account_pubkey, nonce_authority_pubkey);
443        instructions.insert(0, nonce_ix);
444        Self::new(&instructions, payer)
445    }
446
447    pub fn new_with_compiled_instructions(
448        num_required_signatures: u8,
449        num_readonly_signed_accounts: u8,
450        num_readonly_unsigned_accounts: u8,
451        account_keys: Vec<Pubkey>,
452        recent_blockhash: Hash,
453        instructions: Vec<CompiledInstruction>,
454    ) -> Self {
455        Self {
456            header: MessageHeader {
457                num_required_signatures,
458                num_readonly_signed_accounts,
459                num_readonly_unsigned_accounts,
460            },
461            account_keys,
462            recent_blockhash,
463            instructions,
464        }
465    }
466
467    /// Compute the blake3 hash of this transaction's message.
468    #[cfg(not(target_os = "solana"))]
469    pub fn hash(&self) -> Hash {
470        let message_bytes = self.serialize();
471        Self::hash_raw_message(&message_bytes)
472    }
473
474    /// Compute the blake3 hash of a raw transaction message.
475    #[cfg(not(target_os = "solana"))]
476    pub fn hash_raw_message(message_bytes: &[u8]) -> Hash {
477        use blake3::traits::digest::Digest;
478        let mut hasher = blake3::Hasher::new();
479        hasher.update(b"solana-tx-message-v1");
480        hasher.update(message_bytes);
481        Hash(<[u8; crate::hash::HASH_BYTES]>::try_from(hasher.finalize().as_slice()).unwrap())
482    }
483
484    pub fn compile_instruction(&self, ix: &Instruction) -> CompiledInstruction {
485        compile_instruction(ix, &self.account_keys)
486    }
487
488    pub fn serialize(&self) -> Vec<u8> {
489        bincode::serialize(self).unwrap()
490    }
491
492    pub fn program_id(&self, instruction_index: usize) -> Option<&Pubkey> {
493        Some(
494            &self.account_keys[self.instructions.get(instruction_index)?.program_id_index as usize],
495        )
496    }
497
498    pub fn program_index(&self, instruction_index: usize) -> Option<usize> {
499        Some(self.instructions.get(instruction_index)?.program_id_index as usize)
500    }
501
502    pub fn program_ids(&self) -> Vec<&Pubkey> {
503        self.instructions
504            .iter()
505            .map(|ix| &self.account_keys[ix.program_id_index as usize])
506            .collect()
507    }
508
509    pub fn is_key_passed_to_program(&self, key_index: usize) -> bool {
510        if let Ok(key_index) = u8::try_from(key_index) {
511            self.instructions
512                .iter()
513                .any(|ix| ix.accounts.contains(&key_index))
514        } else {
515            false
516        }
517    }
518
519    pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
520        if let Ok(key_index) = u8::try_from(key_index) {
521            self.instructions
522                .iter()
523                .any(|ix| ix.program_id_index == key_index)
524        } else {
525            false
526        }
527    }
528
529    pub fn is_non_loader_key(&self, key_index: usize) -> bool {
530        !self.is_key_called_as_program(key_index) || self.is_key_passed_to_program(key_index)
531    }
532
533    pub fn program_position(&self, index: usize) -> Option<usize> {
534        let program_ids = self.program_ids();
535        program_ids
536            .iter()
537            .position(|&&pubkey| pubkey == self.account_keys[index])
538    }
539
540    pub fn maybe_executable(&self, i: usize) -> bool {
541        self.program_position(i).is_some()
542    }
543
544    pub fn demote_program_id(&self, i: usize) -> bool {
545        self.is_key_called_as_program(i) && !self.is_upgradeable_loader_present()
546    }
547
548    pub fn is_writable(&self, i: usize) -> bool {
549        (i < (self.header.num_required_signatures - self.header.num_readonly_signed_accounts)
550            as usize
551            || (i >= self.header.num_required_signatures as usize
552                && i < self.account_keys.len()
553                    - self.header.num_readonly_unsigned_accounts as usize))
554            && !is_builtin_key_or_sysvar(&self.account_keys[i])
555            && !self.demote_program_id(i)
556    }
557
558    pub fn is_signer(&self, i: usize) -> bool {
559        i < self.header.num_required_signatures as usize
560    }
561
562    #[deprecated]
563    pub fn get_account_keys_by_lock_type(&self) -> (Vec<&Pubkey>, Vec<&Pubkey>) {
564        let mut writable_keys = vec![];
565        let mut readonly_keys = vec![];
566        for (i, key) in self.account_keys.iter().enumerate() {
567            if self.is_writable(i) {
568                writable_keys.push(key);
569            } else {
570                readonly_keys.push(key);
571            }
572        }
573        (writable_keys, readonly_keys)
574    }
575
576    #[deprecated]
577    pub fn deserialize_instruction(
578        index: usize,
579        data: &[u8],
580    ) -> Result<Instruction, SanitizeError> {
581        #[allow(deprecated)]
582        sysvar::instructions::load_instruction_at(index, data)
583    }
584
585    pub fn signer_keys(&self) -> Vec<&Pubkey> {
586        // Clamp in case we're working on un-`sanitize()`ed input
587        let last_key = self
588            .account_keys
589            .len()
590            .min(self.header.num_required_signatures as usize);
591        self.account_keys[..last_key].iter().collect()
592    }
593
594    /// Returns `true` if `account_keys` has any duplicate keys.
595    pub fn has_duplicates(&self) -> bool {
596        // Note: This is an O(n^2) algorithm, but requires no heap allocations. The benchmark
597        // `bench_has_duplicates` in benches/message_processor.rs shows that this implementation is
598        // ~50 times faster than using HashSet for very short slices.
599        for i in 1..self.account_keys.len() {
600            #[allow(clippy::integer_arithmetic)]
601            if self.account_keys[i..].contains(&self.account_keys[i - 1]) {
602                return true;
603            }
604        }
605        false
606    }
607
608    /// Returns `true` if any account is the BPF upgradeable loader.
609    pub fn is_upgradeable_loader_present(&self) -> bool {
610        self.account_keys
611            .iter()
612            .any(|&key| key == bpf_loader_upgradeable::id())
613    }
614}
615
616#[cfg(test)]
617mod tests {
618    #![allow(deprecated)]
619    use {
620        super::*,
621        crate::{hash, instruction::AccountMeta, message::MESSAGE_HEADER_LENGTH},
622        std::collections::HashSet,
623    };
624
625    #[test]
626    fn test_builtin_program_keys() {
627        let keys: HashSet<Pubkey> = BUILTIN_PROGRAMS_KEYS.iter().copied().collect();
628        assert_eq!(keys.len(), 10);
629        for k in keys {
630            let k = format!("{}", k);
631            assert!(k.ends_with("11111111111111111111111"));
632        }
633    }
634
635    #[test]
636    fn test_builtin_program_keys_abi_freeze() {
637        // Once the feature is flipped on, we can't further modify
638        // BUILTIN_PROGRAMS_KEYS without the risk of breaking consensus.
639        let builtins = format!("{:?}", *BUILTIN_PROGRAMS_KEYS);
640        assert_eq!(
641            format!("{}", hash::hash(builtins.as_bytes())),
642            "ACqmMkYbo9eqK6QrRSrB3HLyR6uHhLf31SCfGUAJjiWj"
643        );
644    }
645
646    #[test]
647    // Ensure there's a way to calculate the number of required signatures.
648    fn test_message_signed_keys_len() {
649        let program_id = Pubkey::default();
650        let id0 = Pubkey::default();
651        let ix = Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, false)]);
652        let message = Message::new(&[ix], None);
653        assert_eq!(message.header.num_required_signatures, 0);
654
655        let ix = Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, true)]);
656        let message = Message::new(&[ix], Some(&id0));
657        assert_eq!(message.header.num_required_signatures, 1);
658    }
659
660    #[test]
661    fn test_message_kitchen_sink() {
662        let program_id0 = Pubkey::new_unique();
663        let program_id1 = Pubkey::new_unique();
664        let id0 = Pubkey::default();
665        let id1 = Pubkey::new_unique();
666        let message = Message::new(
667            &[
668                Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id0, false)]),
669                Instruction::new_with_bincode(program_id1, &0, vec![AccountMeta::new(id1, true)]),
670                Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id1, false)]),
671            ],
672            Some(&id1),
673        );
674        assert_eq!(
675            message.instructions[0],
676            CompiledInstruction::new(2, &0, vec![1])
677        );
678        assert_eq!(
679            message.instructions[1],
680            CompiledInstruction::new(3, &0, vec![0])
681        );
682        assert_eq!(
683            message.instructions[2],
684            CompiledInstruction::new(2, &0, vec![0])
685        );
686    }
687
688    #[test]
689    fn test_message_payer_first() {
690        let program_id = Pubkey::default();
691        let payer = Pubkey::new_unique();
692        let id0 = Pubkey::default();
693
694        let ix = Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, false)]);
695        let message = Message::new(&[ix], Some(&payer));
696        assert_eq!(message.header.num_required_signatures, 1);
697
698        let ix = Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, true)]);
699        let message = Message::new(&[ix], Some(&payer));
700        assert_eq!(message.header.num_required_signatures, 2);
701
702        let ix = Instruction::new_with_bincode(
703            program_id,
704            &0,
705            vec![AccountMeta::new(payer, true), AccountMeta::new(id0, true)],
706        );
707        let message = Message::new(&[ix], Some(&payer));
708        assert_eq!(message.header.num_required_signatures, 2);
709    }
710
711    #[test]
712    fn test_program_position() {
713        let program_id0 = Pubkey::default();
714        let program_id1 = Pubkey::new_unique();
715        let id = Pubkey::new_unique();
716        let message = Message::new(
717            &[
718                Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id, false)]),
719                Instruction::new_with_bincode(program_id1, &0, vec![AccountMeta::new(id, true)]),
720            ],
721            Some(&id),
722        );
723        assert_eq!(message.program_position(0), None);
724        assert_eq!(message.program_position(1), Some(0));
725        assert_eq!(message.program_position(2), Some(1));
726    }
727
728    #[test]
729    fn test_is_writable() {
730        let key0 = Pubkey::new_unique();
731        let key1 = Pubkey::new_unique();
732        let key2 = Pubkey::new_unique();
733        let key3 = Pubkey::new_unique();
734        let key4 = Pubkey::new_unique();
735        let key5 = Pubkey::new_unique();
736
737        let message = Message {
738            header: MessageHeader {
739                num_required_signatures: 3,
740                num_readonly_signed_accounts: 2,
741                num_readonly_unsigned_accounts: 1,
742            },
743            account_keys: vec![key0, key1, key2, key3, key4, key5],
744            recent_blockhash: Hash::default(),
745            instructions: vec![],
746        };
747        assert!(message.is_writable(0));
748        assert!(!message.is_writable(1));
749        assert!(!message.is_writable(2));
750        assert!(message.is_writable(3));
751        assert!(message.is_writable(4));
752        assert!(!message.is_writable(5));
753    }
754
755    #[test]
756    fn test_get_account_keys_by_lock_type() {
757        let program_id = Pubkey::default();
758        let id0 = Pubkey::new_unique();
759        let id1 = Pubkey::new_unique();
760        let id2 = Pubkey::new_unique();
761        let id3 = Pubkey::new_unique();
762        let message = Message::new(
763            &[
764                Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, false)]),
765                Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id1, true)]),
766                Instruction::new_with_bincode(
767                    program_id,
768                    &0,
769                    vec![AccountMeta::new_readonly(id2, false)],
770                ),
771                Instruction::new_with_bincode(
772                    program_id,
773                    &0,
774                    vec![AccountMeta::new_readonly(id3, true)],
775                ),
776            ],
777            Some(&id1),
778        );
779        assert_eq!(
780            message.get_account_keys_by_lock_type(),
781            (vec![&id1, &id0], vec![&id3, &program_id, &id2])
782        );
783    }
784
785    #[test]
786    fn test_program_ids() {
787        let key0 = Pubkey::new_unique();
788        let key1 = Pubkey::new_unique();
789        let loader2 = Pubkey::new_unique();
790        let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
791        let message = Message::new_with_compiled_instructions(
792            1,
793            0,
794            2,
795            vec![key0, key1, loader2],
796            Hash::default(),
797            instructions,
798        );
799        assert_eq!(message.program_ids(), vec![&loader2]);
800    }
801
802    #[test]
803    fn test_is_key_passed_to_program() {
804        let key0 = Pubkey::new_unique();
805        let key1 = Pubkey::new_unique();
806        let loader2 = Pubkey::new_unique();
807        let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
808        let message = Message::new_with_compiled_instructions(
809            1,
810            0,
811            2,
812            vec![key0, key1, loader2],
813            Hash::default(),
814            instructions,
815        );
816
817        assert!(message.is_key_passed_to_program(0));
818        assert!(message.is_key_passed_to_program(1));
819        assert!(!message.is_key_passed_to_program(2));
820    }
821
822    #[test]
823    fn test_is_non_loader_key() {
824        let key0 = Pubkey::new_unique();
825        let key1 = Pubkey::new_unique();
826        let loader2 = Pubkey::new_unique();
827        let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
828        let message = Message::new_with_compiled_instructions(
829            1,
830            0,
831            2,
832            vec![key0, key1, loader2],
833            Hash::default(),
834            instructions,
835        );
836        assert!(message.is_non_loader_key(0));
837        assert!(message.is_non_loader_key(1));
838        assert!(!message.is_non_loader_key(2));
839    }
840
841    #[test]
842    fn test_message_header_len_constant() {
843        assert_eq!(
844            bincode::serialized_size(&MessageHeader::default()).unwrap() as usize,
845            MESSAGE_HEADER_LENGTH
846        );
847    }
848
849    #[test]
850    fn test_message_hash() {
851        // when this test fails, it's most likely due to a new serialized format of a message.
852        // in this case, the domain prefix `solana-tx-message-v1` should be updated.
853        let program_id0 = Pubkey::from_str("4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM").unwrap();
854        let program_id1 = Pubkey::from_str("8opHzTAnfzRpPEx21XtnrVTX28YQuCpAjcn1PczScKh").unwrap();
855        let id0 = Pubkey::from_str("CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3").unwrap();
856        let id1 = Pubkey::from_str("GcdayuLaLyrdmUu324nahyv33G5poQdLUEZ1nEytDeP").unwrap();
857        let id2 = Pubkey::from_str("LX3EUdRUBUa3TbsYXLEUdj9J3prXkWXvLYSWyYyc2Jj").unwrap();
858        let id3 = Pubkey::from_str("QRSsyMWN1yHT9ir42bgNZUNZ4PdEhcSWCrL2AryKpy5").unwrap();
859        let instructions = vec![
860            Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id0, false)]),
861            Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id1, true)]),
862            Instruction::new_with_bincode(
863                program_id1,
864                &0,
865                vec![AccountMeta::new_readonly(id2, false)],
866            ),
867            Instruction::new_with_bincode(
868                program_id1,
869                &0,
870                vec![AccountMeta::new_readonly(id3, true)],
871            ),
872        ];
873
874        let message = Message::new(&instructions, Some(&id1));
875        assert_eq!(
876            message.hash(),
877            Hash::from_str("7VWCF4quo2CcWQFNUayZiorxpiR5ix8YzLebrXKf3fMF").unwrap()
878        )
879    }
880}