//! The original and current Solana message format. //! //! This crate defines two versions of `Message` in their own modules: //! [`legacy`] and [`v0`]. `legacy` is the current version as of Solana 1.10.0. //! `v0` is a [future message format] that encodes more account keys into a //! transaction than the legacy format. //! //! [`legacy`]: crate::message::legacy //! [`v0`]: crate::message::v0 //! [future message format]: https://docs.solana.com/proposals/transactions-v2 #![allow(clippy::integer_arithmetic)] use { crate::{ bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, hash::Hash, instruction::{CompiledInstruction, Instruction}, message::{CompiledKeys, MessageHeader}, pubkey::Pubkey, sanitize::{Sanitize, SanitizeError}, short_vec, system_instruction, system_program, sysvar, wasm_bindgen, }, lazy_static::lazy_static, std::{convert::TryFrom, str::FromStr}, }; lazy_static! { // Copied keys over since direct references create cyclical dependency. pub static ref BUILTIN_PROGRAMS_KEYS: [Pubkey; 10] = { let parse = |s| Pubkey::from_str(s).unwrap(); [ parse("Config1111111111111111111111111111111111111"), parse("Feature111111111111111111111111111111111111"), parse("NativeLoader1111111111111111111111111111111"), parse("Stake11111111111111111111111111111111111111"), parse("StakeConfig11111111111111111111111111111111"), parse("Vote111111111111111111111111111111111111111"), system_program::id(), bpf_loader::id(), bpf_loader_deprecated::id(), bpf_loader_upgradeable::id(), ] }; } fn position(keys: &[Pubkey], key: &Pubkey) -> u8 { keys.iter().position(|k| k == key).unwrap() as u8 } fn compile_instruction(ix: &Instruction, keys: &[Pubkey]) -> CompiledInstruction { let accounts: Vec<_> = ix .accounts .iter() .map(|account_meta| position(keys, &account_meta.pubkey)) .collect(); CompiledInstruction { program_id_index: position(keys, &ix.program_id), data: ix.data.clone(), accounts, } } fn compile_instructions(ixs: &[Instruction], keys: &[Pubkey]) -> Vec { ixs.iter().map(|ix| compile_instruction(ix, keys)).collect() } /// A Solana transaction message (legacy). /// /// See the [`message`] module documentation for further description. /// /// [`message`]: crate::message /// /// Some constructors accept an optional `payer`, the account responsible for /// paying the cost of executing a transaction. In most cases, callers should /// specify the payer explicitly in these constructors. In some cases though, /// the caller is not _required_ to specify the payer, but is still allowed to: /// in the `Message` structure, the first account is always the fee-payer, so if /// the caller has knowledge that the first account of the constructed /// transaction's `Message` is both a signer and the expected fee-payer, then /// redundantly specifying the fee-payer is not strictly required. // NOTE: Serialization-related changes must be paired with the custom serialization // for versioned messages in the `RemainingLegacyMessage` struct. #[wasm_bindgen] #[frozen_abi(digest = "2KnLEqfLcTBQqitE22Pp8JYkaqVVbAkGbCfdeHoyxcAU")] #[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)] #[serde(rename_all = "camelCase")] pub struct Message { /// The message header, identifying signed and read-only `account_keys`. // NOTE: Serialization-related changes must be paired with the direct read at sigverify. #[wasm_bindgen(skip)] pub header: MessageHeader, /// All the account keys used by this transaction. #[wasm_bindgen(skip)] #[serde(with = "short_vec")] pub account_keys: Vec, /// The id of a recent ledger entry. pub recent_blockhash: Hash, /// Programs that will be executed in sequence and committed in one atomic transaction if all /// succeed. #[wasm_bindgen(skip)] #[serde(with = "short_vec")] pub instructions: Vec, } impl Sanitize for Message { fn sanitize(&self) -> std::result::Result<(), SanitizeError> { // signing area and read-only non-signing area should not overlap if self.header.num_required_signatures as usize + self.header.num_readonly_unsigned_accounts as usize > self.account_keys.len() { return Err(SanitizeError::IndexOutOfBounds); } // there should be at least 1 RW fee-payer account. if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures { return Err(SanitizeError::IndexOutOfBounds); } for ci in &self.instructions { if ci.program_id_index as usize >= self.account_keys.len() { return Err(SanitizeError::IndexOutOfBounds); } // A program cannot be a payer. if ci.program_id_index == 0 { return Err(SanitizeError::IndexOutOfBounds); } for ai in &ci.accounts { if *ai as usize >= self.account_keys.len() { return Err(SanitizeError::IndexOutOfBounds); } } } self.account_keys.sanitize()?; self.recent_blockhash.sanitize()?; self.instructions.sanitize()?; Ok(()) } } impl Message { /// Create a new `Message`. /// /// # Examples /// /// This example uses the [`solana_sdk`], [`solana_client`] and [`anyhow`] crates. /// /// [`solana_sdk`]: https://docs.rs/solana-sdk /// [`solana_client`]: https://docs.rs/solana-client /// [`anyhow`]: https://docs.rs/anyhow /// /// ``` /// # use solana_program::example_mocks::solana_sdk; /// # use solana_program::example_mocks::solana_client; /// use anyhow::Result; /// use borsh::{BorshSerialize, BorshDeserialize}; /// use solana_client::rpc_client::RpcClient; /// use solana_sdk::{ /// instruction::Instruction, /// message::Message, /// pubkey::Pubkey, /// signature::Keypair, /// transaction::Transaction, /// }; /// /// // A custom program instruction. This would typically be defined in /// // another crate so it can be shared between the on-chain program and /// // the client. /// #[derive(BorshSerialize, BorshDeserialize)] /// enum BankInstruction { /// Initialize, /// Deposit { lamports: u64 }, /// Withdraw { lamports: u64 }, /// } /// /// fn send_initialize_tx( /// client: &RpcClient, /// program_id: Pubkey, /// payer: &Keypair /// ) -> Result<()> { /// /// let bank_instruction = BankInstruction::Initialize; /// /// let instruction = Instruction::new_with_borsh( /// program_id, /// &bank_instruction, /// vec![], /// ); /// /// let message = Message::new( /// &[instruction], /// Some(&payer.pubkey()), /// ); /// /// let blockhash = client.get_latest_blockhash()?; /// let mut tx = Transaction::new(&[payer], message, blockhash); /// client.send_and_confirm_transaction(&tx)?; /// /// Ok(()) /// } /// # /// # let client = RpcClient::new(String::new()); /// # let program_id = Pubkey::new_unique(); /// # let payer = Keypair::new(); /// # send_initialize_tx(&client, program_id, &payer)?; /// # /// # Ok::<(), anyhow::Error>(()) /// ``` pub fn new(instructions: &[Instruction], payer: Option<&Pubkey>) -> Self { Self::new_with_blockhash(instructions, payer, &Hash::default()) } /// Create a new message while setting the blockhash. /// /// # Examples /// /// This example uses the [`solana_sdk`], [`solana_client`] and [`anyhow`] crates. /// /// [`solana_sdk`]: https://docs.rs/solana-sdk /// [`solana_client`]: https://docs.rs/solana-client /// [`anyhow`]: https://docs.rs/anyhow /// /// ``` /// # use solana_program::example_mocks::solana_sdk; /// # use solana_program::example_mocks::solana_client; /// use anyhow::Result; /// use borsh::{BorshSerialize, BorshDeserialize}; /// use solana_client::rpc_client::RpcClient; /// use solana_sdk::{ /// instruction::Instruction, /// message::Message, /// pubkey::Pubkey, /// signature::Keypair, /// transaction::Transaction, /// }; /// /// // A custom program instruction. This would typically be defined in /// // another crate so it can be shared between the on-chain program and /// // the client. /// #[derive(BorshSerialize, BorshDeserialize)] /// enum BankInstruction { /// Initialize, /// Deposit { lamports: u64 }, /// Withdraw { lamports: u64 }, /// } /// /// fn send_initialize_tx( /// client: &RpcClient, /// program_id: Pubkey, /// payer: &Keypair /// ) -> Result<()> { /// /// let bank_instruction = BankInstruction::Initialize; /// /// let instruction = Instruction::new_with_borsh( /// program_id, /// &bank_instruction, /// vec![], /// ); /// /// let blockhash = client.get_latest_blockhash()?; /// /// let message = Message::new_with_blockhash( /// &[instruction], /// Some(&payer.pubkey()), /// &blockhash, /// ); /// /// let mut tx = Transaction::new_unsigned(message); /// tx.sign(&[payer], tx.message.recent_blockhash); /// client.send_and_confirm_transaction(&tx)?; /// /// Ok(()) /// } /// # /// # let client = RpcClient::new(String::new()); /// # let program_id = Pubkey::new_unique(); /// # let payer = Keypair::new(); /// # send_initialize_tx(&client, program_id, &payer)?; /// # /// # Ok::<(), anyhow::Error>(()) /// ``` pub fn new_with_blockhash( instructions: &[Instruction], payer: Option<&Pubkey>, blockhash: &Hash, ) -> Self { let compiled_keys = CompiledKeys::compile(instructions, payer.cloned()); let (header, account_keys) = compiled_keys .try_into_message_components() .expect("overflow when compiling message keys"); let instructions = compile_instructions(instructions, &account_keys); Self::new_with_compiled_instructions( header.num_required_signatures, header.num_readonly_signed_accounts, header.num_readonly_unsigned_accounts, account_keys, *blockhash, instructions, ) } /// Create a new message for a [nonced transaction]. /// /// [nonced transaction]: https://docs.solana.com/implemented-proposals/durable-tx-nonces /// /// In this type of transaction, the blockhash is replaced with a _durable /// transaction nonce_, allowing for extended time to pass between the /// transaction's signing and submission to the blockchain. /// /// # Examples /// /// This example uses the [`solana_sdk`], [`solana_client`] and [`anyhow`] crates. /// /// [`solana_sdk`]: https://docs.rs/solana-sdk /// [`solana_client`]: https://docs.rs/solana-client /// [`anyhow`]: https://docs.rs/anyhow /// /// ``` /// # use solana_program::example_mocks::solana_sdk; /// # use solana_program::example_mocks::solana_client; /// use anyhow::Result; /// use borsh::{BorshSerialize, BorshDeserialize}; /// use solana_client::rpc_client::RpcClient; /// use solana_sdk::{ /// hash::Hash, /// instruction::Instruction, /// message::Message, /// nonce, /// pubkey::Pubkey, /// signature::Keypair, /// system_instruction, /// transaction::Transaction, /// }; /// /// // A custom program instruction. This would typically be defined in /// // another crate so it can be shared between the on-chain program and /// // the client. /// #[derive(BorshSerialize, BorshDeserialize)] /// enum BankInstruction { /// Initialize, /// Deposit { lamports: u64 }, /// Withdraw { lamports: u64 }, /// } /// /// // Create a nonced transaction for later signing and submission, /// // returning it and the nonce account's pubkey. /// fn create_offline_initialize_tx( /// client: &RpcClient, /// program_id: Pubkey, /// payer: &Keypair /// ) -> Result<(Transaction, Pubkey)> { /// /// let bank_instruction = BankInstruction::Initialize; /// let bank_instruction = Instruction::new_with_borsh( /// program_id, /// &bank_instruction, /// vec![], /// ); /// /// // This will create a nonce account and assign authority to the /// // payer so they can sign to advance the nonce and withdraw its rent. /// let nonce_account = make_nonce_account(client, payer)?; /// /// let mut message = Message::new_with_nonce( /// vec![bank_instruction], /// Some(&payer.pubkey()), /// &nonce_account, /// &payer.pubkey() /// ); /// /// // This transaction will need to be signed later, using the blockhash /// // stored in the nonce account. /// let tx = Transaction::new_unsigned(message); /// /// Ok((tx, nonce_account)) /// } /// /// fn make_nonce_account(client: &RpcClient, payer: &Keypair) /// -> Result /// { /// let nonce_account_address = Keypair::new(); /// let nonce_account_size = nonce::State::size(); /// let nonce_rent = client.get_minimum_balance_for_rent_exemption(nonce_account_size)?; /// /// // Assigning the nonce authority to the payer so they can sign for the withdrawal, /// // and we can throw away the nonce address secret key. /// let create_nonce_instr = system_instruction::create_nonce_account( /// &payer.pubkey(), /// &nonce_account_address.pubkey(), /// &payer.pubkey(), /// nonce_rent, /// ); /// /// let mut nonce_tx = Transaction::new_with_payer(&create_nonce_instr, Some(&payer.pubkey())); /// let blockhash = client.get_latest_blockhash()?; /// nonce_tx.sign(&[&payer, &nonce_account_address], blockhash); /// client.send_and_confirm_transaction(&nonce_tx)?; /// /// Ok(nonce_account_address.pubkey()) /// } /// # /// # let client = RpcClient::new(String::new()); /// # let program_id = Pubkey::new_unique(); /// # let payer = Keypair::new(); /// # create_offline_initialize_tx(&client, program_id, &payer)?; /// # Ok::<(), anyhow::Error>(()) /// ``` pub fn new_with_nonce( mut instructions: Vec, payer: Option<&Pubkey>, nonce_account_pubkey: &Pubkey, nonce_authority_pubkey: &Pubkey, ) -> Self { let nonce_ix = system_instruction::advance_nonce_account(nonce_account_pubkey, nonce_authority_pubkey); instructions.insert(0, nonce_ix); Self::new(&instructions, payer) } pub fn new_with_compiled_instructions( num_required_signatures: u8, num_readonly_signed_accounts: u8, num_readonly_unsigned_accounts: u8, account_keys: Vec, recent_blockhash: Hash, instructions: Vec, ) -> Self { Self { header: MessageHeader { num_required_signatures, num_readonly_signed_accounts, num_readonly_unsigned_accounts, }, account_keys, recent_blockhash, instructions, } } /// Compute the blake3 hash of this transaction's message. #[cfg(not(target_arch = "bpf"))] pub fn hash(&self) -> Hash { let message_bytes = self.serialize(); Self::hash_raw_message(&message_bytes) } /// Compute the blake3 hash of a raw transaction message. #[cfg(not(target_arch = "bpf"))] pub fn hash_raw_message(message_bytes: &[u8]) -> Hash { use blake3::traits::digest::Digest; let mut hasher = blake3::Hasher::new(); hasher.update(b"solana-tx-message-v1"); hasher.update(message_bytes); Hash(<[u8; crate::hash::HASH_BYTES]>::try_from(hasher.finalize().as_slice()).unwrap()) } pub fn compile_instruction(&self, ix: &Instruction) -> CompiledInstruction { compile_instruction(ix, &self.account_keys) } pub fn serialize(&self) -> Vec { bincode::serialize(self).unwrap() } pub fn program_id(&self, instruction_index: usize) -> Option<&Pubkey> { Some( &self.account_keys[self.instructions.get(instruction_index)?.program_id_index as usize], ) } pub fn program_index(&self, instruction_index: usize) -> Option { Some(self.instructions.get(instruction_index)?.program_id_index as usize) } pub fn program_ids(&self) -> Vec<&Pubkey> { self.instructions .iter() .map(|ix| &self.account_keys[ix.program_id_index as usize]) .collect() } pub fn is_key_passed_to_program(&self, key_index: usize) -> bool { if let Ok(key_index) = u8::try_from(key_index) { self.instructions .iter() .any(|ix| ix.accounts.contains(&key_index)) } else { false } } pub fn is_key_called_as_program(&self, key_index: usize) -> bool { if let Ok(key_index) = u8::try_from(key_index) { self.instructions .iter() .any(|ix| ix.program_id_index == key_index) } else { false } } pub fn is_non_loader_key(&self, key_index: usize) -> bool { !self.is_key_called_as_program(key_index) || self.is_key_passed_to_program(key_index) } pub fn program_position(&self, index: usize) -> Option { let program_ids = self.program_ids(); program_ids .iter() .position(|&&pubkey| pubkey == self.account_keys[index]) } pub fn maybe_executable(&self, i: usize) -> bool { self.program_position(i).is_some() } pub fn is_writable(&self, i: usize) -> bool { let demote_program_id = self.is_key_called_as_program(i) && !self.is_upgradeable_loader_present(); (i < (self.header.num_required_signatures - self.header.num_readonly_signed_accounts) as usize || (i >= self.header.num_required_signatures as usize && i < self.account_keys.len() - self.header.num_readonly_unsigned_accounts as usize)) && !{ let key = self.account_keys[i]; sysvar::is_sysvar_id(&key) || BUILTIN_PROGRAMS_KEYS.contains(&key) } && !demote_program_id } pub fn is_signer(&self, i: usize) -> bool { i < self.header.num_required_signatures as usize } #[deprecated] pub fn get_account_keys_by_lock_type(&self) -> (Vec<&Pubkey>, Vec<&Pubkey>) { let mut writable_keys = vec![]; let mut readonly_keys = vec![]; for (i, key) in self.account_keys.iter().enumerate() { if self.is_writable(i) { writable_keys.push(key); } else { readonly_keys.push(key); } } (writable_keys, readonly_keys) } #[deprecated] pub fn deserialize_instruction( index: usize, data: &[u8], ) -> Result { #[allow(deprecated)] sysvar::instructions::load_instruction_at(index, data) } pub fn signer_keys(&self) -> Vec<&Pubkey> { // Clamp in case we're working on un-`sanitize()`ed input let last_key = self .account_keys .len() .min(self.header.num_required_signatures as usize); self.account_keys[..last_key].iter().collect() } /// Returns `true` if `account_keys` has any duplicate keys. pub fn has_duplicates(&self) -> bool { // Note: This is an O(n^2) algorithm, but requires no heap allocations. The benchmark // `bench_has_duplicates` in benches/message_processor.rs shows that this implementation is // ~50 times faster than using HashSet for very short slices. for i in 1..self.account_keys.len() { #[allow(clippy::integer_arithmetic)] if self.account_keys[i..].contains(&self.account_keys[i - 1]) { return true; } } false } /// Returns `true` if any account is the BPF upgradeable loader. pub fn is_upgradeable_loader_present(&self) -> bool { self.account_keys .iter() .any(|&key| key == bpf_loader_upgradeable::id()) } } #[cfg(test)] mod tests { #![allow(deprecated)] use { super::*, crate::{hash, instruction::AccountMeta, message::MESSAGE_HEADER_LENGTH}, std::collections::HashSet, }; #[test] fn test_builtin_program_keys() { let keys: HashSet = BUILTIN_PROGRAMS_KEYS.iter().copied().collect(); assert_eq!(keys.len(), 10); for k in keys { let k = format!("{}", k); assert!(k.ends_with("11111111111111111111111")); } } #[test] fn test_builtin_program_keys_abi_freeze() { // Once the feature is flipped on, we can't further modify // BUILTIN_PROGRAMS_KEYS without the risk of breaking consensus. let builtins = format!("{:?}", *BUILTIN_PROGRAMS_KEYS); assert_eq!( format!("{}", hash::hash(builtins.as_bytes())), "ACqmMkYbo9eqK6QrRSrB3HLyR6uHhLf31SCfGUAJjiWj" ); } #[test] // Ensure there's a way to calculate the number of required signatures. fn test_message_signed_keys_len() { let program_id = Pubkey::default(); let id0 = Pubkey::default(); let ix = Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, false)]); let message = Message::new(&[ix], None); assert_eq!(message.header.num_required_signatures, 0); let ix = Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, true)]); let message = Message::new(&[ix], Some(&id0)); assert_eq!(message.header.num_required_signatures, 1); } #[test] fn test_message_kitchen_sink() { let program_id0 = Pubkey::new_unique(); let program_id1 = Pubkey::new_unique(); let id0 = Pubkey::default(); let id1 = Pubkey::new_unique(); let message = Message::new( &[ Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id0, false)]), Instruction::new_with_bincode(program_id1, &0, vec![AccountMeta::new(id1, true)]), Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id1, false)]), ], Some(&id1), ); assert_eq!( message.instructions[0], CompiledInstruction::new(2, &0, vec![1]) ); assert_eq!( message.instructions[1], CompiledInstruction::new(3, &0, vec![0]) ); assert_eq!( message.instructions[2], CompiledInstruction::new(2, &0, vec![0]) ); } #[test] fn test_message_payer_first() { let program_id = Pubkey::default(); let payer = Pubkey::new_unique(); let id0 = Pubkey::default(); let ix = Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, false)]); let message = Message::new(&[ix], Some(&payer)); assert_eq!(message.header.num_required_signatures, 1); let ix = Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, true)]); let message = Message::new(&[ix], Some(&payer)); assert_eq!(message.header.num_required_signatures, 2); let ix = Instruction::new_with_bincode( program_id, &0, vec![AccountMeta::new(payer, true), AccountMeta::new(id0, true)], ); let message = Message::new(&[ix], Some(&payer)); assert_eq!(message.header.num_required_signatures, 2); } #[test] fn test_program_position() { let program_id0 = Pubkey::default(); let program_id1 = Pubkey::new_unique(); let id = Pubkey::new_unique(); let message = Message::new( &[ Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id, false)]), Instruction::new_with_bincode(program_id1, &0, vec![AccountMeta::new(id, true)]), ], Some(&id), ); assert_eq!(message.program_position(0), None); assert_eq!(message.program_position(1), Some(0)); assert_eq!(message.program_position(2), Some(1)); } #[test] fn test_is_writable() { let key0 = Pubkey::new_unique(); let key1 = Pubkey::new_unique(); let key2 = Pubkey::new_unique(); let key3 = Pubkey::new_unique(); let key4 = Pubkey::new_unique(); let key5 = Pubkey::new_unique(); let message = Message { header: MessageHeader { num_required_signatures: 3, num_readonly_signed_accounts: 2, num_readonly_unsigned_accounts: 1, }, account_keys: vec![key0, key1, key2, key3, key4, key5], recent_blockhash: Hash::default(), instructions: vec![], }; assert!(message.is_writable(0)); assert!(!message.is_writable(1)); assert!(!message.is_writable(2)); assert!(message.is_writable(3)); assert!(message.is_writable(4)); assert!(!message.is_writable(5)); } #[test] fn test_get_account_keys_by_lock_type() { let program_id = Pubkey::default(); let id0 = Pubkey::new_unique(); let id1 = Pubkey::new_unique(); let id2 = Pubkey::new_unique(); let id3 = Pubkey::new_unique(); let message = Message::new( &[ Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, false)]), Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id1, true)]), Instruction::new_with_bincode( program_id, &0, vec![AccountMeta::new_readonly(id2, false)], ), Instruction::new_with_bincode( program_id, &0, vec![AccountMeta::new_readonly(id3, true)], ), ], Some(&id1), ); assert_eq!( message.get_account_keys_by_lock_type(), (vec![&id1, &id0], vec![&id3, &program_id, &id2]) ); } #[test] fn test_program_ids() { let key0 = Pubkey::new_unique(); let key1 = Pubkey::new_unique(); let loader2 = Pubkey::new_unique(); let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])]; let message = Message::new_with_compiled_instructions( 1, 0, 2, vec![key0, key1, loader2], Hash::default(), instructions, ); assert_eq!(message.program_ids(), vec![&loader2]); } #[test] fn test_is_key_passed_to_program() { let key0 = Pubkey::new_unique(); let key1 = Pubkey::new_unique(); let loader2 = Pubkey::new_unique(); let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])]; let message = Message::new_with_compiled_instructions( 1, 0, 2, vec![key0, key1, loader2], Hash::default(), instructions, ); assert!(message.is_key_passed_to_program(0)); assert!(message.is_key_passed_to_program(1)); assert!(!message.is_key_passed_to_program(2)); } #[test] fn test_is_non_loader_key() { let key0 = Pubkey::new_unique(); let key1 = Pubkey::new_unique(); let loader2 = Pubkey::new_unique(); let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])]; let message = Message::new_with_compiled_instructions( 1, 0, 2, vec![key0, key1, loader2], Hash::default(), instructions, ); assert!(message.is_non_loader_key(0)); assert!(message.is_non_loader_key(1)); assert!(!message.is_non_loader_key(2)); } #[test] fn test_message_header_len_constant() { assert_eq!( bincode::serialized_size(&MessageHeader::default()).unwrap() as usize, MESSAGE_HEADER_LENGTH ); } #[test] fn test_message_hash() { // when this test fails, it's most likely due to a new serialized format of a message. // in this case, the domain prefix `solana-tx-message-v1` should be updated. let program_id0 = Pubkey::from_str("4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM").unwrap(); let program_id1 = Pubkey::from_str("8opHzTAnfzRpPEx21XtnrVTX28YQuCpAjcn1PczScKh").unwrap(); let id0 = Pubkey::from_str("CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3").unwrap(); let id1 = Pubkey::from_str("GcdayuLaLyrdmUu324nahyv33G5poQdLUEZ1nEytDeP").unwrap(); let id2 = Pubkey::from_str("LX3EUdRUBUa3TbsYXLEUdj9J3prXkWXvLYSWyYyc2Jj").unwrap(); let id3 = Pubkey::from_str("QRSsyMWN1yHT9ir42bgNZUNZ4PdEhcSWCrL2AryKpy5").unwrap(); let instructions = vec![ Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id0, false)]), Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id1, true)]), Instruction::new_with_bincode( program_id1, &0, vec![AccountMeta::new_readonly(id2, false)], ), Instruction::new_with_bincode( program_id1, &0, vec![AccountMeta::new_readonly(id3, true)], ), ]; let message = Message::new(&instructions, Some(&id1)); assert_eq!( message.hash(), Hash::from_str("7VWCF4quo2CcWQFNUayZiorxpiR5ix8YzLebrXKf3fMF").unwrap() ) } }