From f561eb917fd9dec8c6739418216ed048775d9521 Mon Sep 17 00:00:00 2001 From: sakridge Date: Sat, 19 Sep 2020 12:17:46 -0700 Subject: [PATCH] Add way to look at tx instructions (#11943) --- programs/bpf/Cargo.lock | 7 + programs/bpf/Cargo.toml | 1 + programs/bpf/build.rs | 1 + .../rust/instruction_introspection/Cargo.toml | 26 ++++ .../rust/instruction_introspection/Xargo.toml | 2 + .../rust/instruction_introspection/src/lib.rs | 45 +++++++ programs/bpf/tests/programs.rs | 70 +++++++++- runtime/src/accounts.rs | 88 +++++++++++-- runtime/src/message_processor.rs | 15 +++ sdk/benches/serialize_instructions.rs | 63 +++++++++ sdk/src/instruction.rs | 2 +- sdk/src/lib.rs | 1 + sdk/src/message.rs | 121 ++++++++++++++++++ sdk/src/serialize_utils.rs | 65 ++++++++++ sdk/src/sysvar/instructions.rs | 51 ++++++++ sdk/src/sysvar/mod.rs | 2 + 16 files changed, 545 insertions(+), 15 deletions(-) create mode 100644 programs/bpf/rust/instruction_introspection/Cargo.toml create mode 100644 programs/bpf/rust/instruction_introspection/Xargo.toml create mode 100644 programs/bpf/rust/instruction_introspection/src/lib.rs create mode 100644 sdk/benches/serialize_instructions.rs create mode 100644 sdk/src/serialize_utils.rs create mode 100644 sdk/src/sysvar/instructions.rs diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index b1a522bc25..11080e5cce 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -1822,6 +1822,13 @@ dependencies = [ "solana-sdk", ] +[[package]] +name = "solana-bpf-rust-instruction-introspection" +version = "1.4.0" +dependencies = [ + "solana-sdk", +] + [[package]] name = "solana-bpf-rust-invoke" version = "1.4.0" diff --git a/programs/bpf/Cargo.toml b/programs/bpf/Cargo.toml index 4363ce4b0c..db0d03641a 100644 --- a/programs/bpf/Cargo.toml +++ b/programs/bpf/Cargo.toml @@ -41,6 +41,7 @@ members = [ "rust/dup_accounts", "rust/error_handling", "rust/external_spend", + "rust/instruction_introspection", "rust/invoke", "rust/invoked", "rust/iter", diff --git a/programs/bpf/build.rs b/programs/bpf/build.rs index ac7d51c1ad..2926062117 100644 --- a/programs/bpf/build.rs +++ b/programs/bpf/build.rs @@ -72,6 +72,7 @@ fn main() { "dup_accounts", "error_handling", "external_spend", + "instruction_introspection", "invoke", "invoked", "iter", diff --git a/programs/bpf/rust/instruction_introspection/Cargo.toml b/programs/bpf/rust/instruction_introspection/Cargo.toml new file mode 100644 index 0000000000..8aff32872a --- /dev/null +++ b/programs/bpf/rust/instruction_introspection/Cargo.toml @@ -0,0 +1,26 @@ + +# Note: This crate must be built using do.sh + +[package] +name = "solana-bpf-rust-instruction-introspection" +version = "1.4.0" +description = "Solana BPF test program written in Rust" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana" +license = "Apache-2.0" +homepage = "https://solana.com/" +edition = "2018" + +[dependencies] +solana-sdk = { path = "../../../../sdk/", version = "1.4.0", default-features = false } + +[features] +program = ["solana-sdk/program"] +default = ["program", "solana-sdk/default"] + +[lib] +name = "solana_bpf_rust_instruction_introspection" +crate-type = ["cdylib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/programs/bpf/rust/instruction_introspection/Xargo.toml b/programs/bpf/rust/instruction_introspection/Xargo.toml new file mode 100644 index 0000000000..1744f098ae --- /dev/null +++ b/programs/bpf/rust/instruction_introspection/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] \ No newline at end of file diff --git a/programs/bpf/rust/instruction_introspection/src/lib.rs b/programs/bpf/rust/instruction_introspection/src/lib.rs new file mode 100644 index 0000000000..c82efbdf2c --- /dev/null +++ b/programs/bpf/rust/instruction_introspection/src/lib.rs @@ -0,0 +1,45 @@ +//! @brief Example Rust-based BPF program that exercises instruction introspection + +extern crate solana_sdk; +use solana_sdk::{ + account_info::next_account_info, account_info::AccountInfo, entrypoint, + entrypoint::ProgramResult, info, program_error::ProgramError, pubkey::Pubkey, + sysvar::instructions, +}; + +entrypoint!(process_instruction); +fn process_instruction( + _program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + if instruction_data.is_empty() { + return Err(ProgramError::InvalidAccountData); + } + + let secp_instruction_index = instruction_data[0]; + let account_info_iter = &mut accounts.iter(); + let instruction_accounts = next_account_info(account_info_iter)?; + assert_eq!(*instruction_accounts.key, instructions::id()); + let data_len = instruction_accounts.try_borrow_data()?.len(); + if data_len < 2 { + return Err(ProgramError::InvalidAccountData); + } + + let instruction = instructions::get_instruction( + secp_instruction_index as usize, + &instruction_accounts.try_borrow_data()?, + ) + .map_err(|_| ProgramError::InvalidAccountData)?; + + let current_instruction = + instructions::get_current_instruction(&instruction_accounts.try_borrow_data()?); + let my_index = instruction_data[1] as u16; + assert_eq!(current_instruction, my_index); + + info!(&format!("id: {}", instruction.program_id)); + + info!(&format!("data[0]: {}", instruction.data[0])); + info!(&format!("index: {}", current_instruction)); + Ok(()) +} diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index 5bf1547d63..f9d8fd5a8c 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -576,7 +576,7 @@ fn assert_instruction_count() { ("solana_bpf_rust_128bit", 543), ("solana_bpf_rust_alloc", 19082), ("solana_bpf_rust_dep_crate", 2), - ("solana_bpf_rust_external_spend", 473), + ("solana_bpf_rust_external_spend", 485), ("solana_bpf_rust_iter", 723), ("solana_bpf_rust_many_args", 231), ("solana_bpf_rust_noop", 2217), @@ -669,3 +669,71 @@ impl InstructionMeter for TestInstructionMeter { u64::MAX } } + +#[cfg(any(feature = "bpf_rust"))] +#[test] +fn test_program_bpf_instruction_introspection() { + solana_logger::setup(); + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(50_000); + let mut bank = Bank::new(&genesis_config); + + let (name, id, entrypoint) = solana_bpf_loader_program!(); + bank.add_builtin_loader(&name, id, entrypoint); + let bank = Arc::new(bank); + let bank_client = BankClient::new_shared(&bank); + + let program_id = load_bpf_program( + &bank_client, + &bpf_loader::id(), + &mint_keypair, + "solana_bpf_rust_instruction_introspection", + ); + + // Passing transaction + let account_metas = vec![AccountMeta::new_readonly( + solana_sdk::sysvar::instructions::id(), + false, + )]; + let instruction0 = Instruction::new(program_id, &[0u8, 0u8], account_metas.clone()); + let instruction1 = Instruction::new(program_id, &[0u8, 1u8], account_metas.clone()); + let instruction2 = Instruction::new(program_id, &[0u8, 2u8], account_metas); + let message = Message::new( + &[instruction0, instruction1, instruction2], + Some(&mint_keypair.pubkey()), + ); + let result = bank_client.send_and_confirm_message(&[&mint_keypair], message); + println!("result: {:?}", result); + assert!(result.is_ok()); + + // writable special instructions11111 key, should not be allowed + let account_metas = vec![AccountMeta::new( + solana_sdk::sysvar::instructions::id(), + false, + )]; + let instruction = Instruction::new(program_id, &0u8, account_metas); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InvalidAccountIndex + ); + + // No accounts, should error + let instruction = Instruction::new(program_id, &0u8, vec![]); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError( + 0, + solana_sdk::instruction::InstructionError::NotEnoughAccountKeys + ) + ); + assert!(bank + .get_account(&solana_sdk::sysvar::instructions::id()) + .is_none()); +} diff --git a/runtime/src/accounts.rs b/runtime/src/accounts.rs index 99d9dda903..2e74af0aa4 100644 --- a/runtime/src/accounts.rs +++ b/runtime/src/accounts.rs @@ -115,6 +115,15 @@ impl Accounts { false } + fn construct_instructions_account(message: &Message) -> Account { + let mut account = Account::default(); + account.data = message.serialize_instructions(); + + // add room for current instruction index. + account.data.resize(account.data.len() + 2, 0); + account + } + fn load_tx_accounts( &self, storage: &AccountStorage, @@ -134,15 +143,23 @@ impl Accounts { // If a fee can pay for execution then the program will be scheduled let mut payer_index = None; let mut tx_rent: TransactionRent = 0; - let mut accounts: Vec<_> = message - .account_keys - .iter() - .enumerate() - .map(|(i, key)| { - if Self::is_non_loader_key(message, key, i) { - if payer_index.is_none() { - payer_index = Some(i); + let mut accounts = Vec::with_capacity(message.account_keys.len()); + for (i, key) in message.account_keys.iter().enumerate() { + let account = if Self::is_non_loader_key(message, key, i) { + if payer_index.is_none() { + payer_index = Some(i); + } + + if solana_sdk::sysvar::instructions::is_enabled( + self.epoch, + self.accounts_db.cluster_type.unwrap(), + ) && solana_sdk::sysvar::instructions::check_id(key) + { + if message.is_writable(i) { + return Err(TransactionError::InvalidAccountIndex); } + Self::construct_instructions_account(message) + } else { let (account, rent) = AccountsDB::load(storage, ancestors, accounts_index, key) .map(|(mut account, _)| { @@ -158,12 +175,14 @@ impl Accounts { tx_rent += rent; account - } else { - // Fill in an empty account for the program slots. - Account::default() } - }) - .collect(); + } else { + // Fill in an empty account for the program slots. + Account::default() + }; + accounts.push(account); + } + debug_assert_eq!(accounts.len(), message.account_keys.len()); if let Some(payer_index) = payer_index { if payer_index != 0 { @@ -1793,4 +1812,47 @@ mod tests { info!("done..cleaning.."); accounts.accounts_db.clean_accounts(); } + + fn load_accounts_no_store( + accounts: &Accounts, + tx: Transaction, + ) -> Vec<(Result, Option)> { + let rent_collector = RentCollector::default(); + let fee_calculator = FeeCalculator::new(10); + let mut hash_queue = BlockhashQueue::new(100); + hash_queue.register_hash(&tx.message().recent_blockhash, &fee_calculator); + + let ancestors = vec![(0, 0)].into_iter().collect(); + let mut error_counters = ErrorCounters::default(); + accounts.load_accounts( + &ancestors, + &[tx], + None, + vec![(Ok(()), Some(HashAgeKind::Extant))], + &hash_queue, + &mut error_counters, + &rent_collector, + ) + } + + #[test] + fn test_instructions() { + solana_logger::setup(); + let accounts = Accounts::new(Vec::new(), &ClusterType::Development); + + let instructions_key = solana_sdk::sysvar::instructions::id(); + let keypair = Keypair::new(); + let instructions = vec![CompiledInstruction::new(1, &(), vec![0, 1])]; + let tx = Transaction::new_with_compiled_instructions( + &[&keypair], + &[Pubkey::new_rand(), instructions_key], + Hash::default(), + vec![native_loader::id()], + instructions, + ); + + let loaded_accounts = load_accounts_no_store(&accounts, tx); + assert_eq!(loaded_accounts.len(), 1); + assert!(loaded_accounts[0].0.is_err()); + } } diff --git a/runtime/src/message_processor.rs b/runtime/src/message_processor.rs index 38add45126..c6a88af7dd 100644 --- a/runtime/src/message_processor.rs +++ b/runtime/src/message_processor.rs @@ -665,7 +665,21 @@ impl MessageProcessor { rent_collector: &RentCollector, log_collector: Option>, executors: Rc>, + instruction_index: usize, ) -> Result<(), InstructionError> { + // Fixup the special instructions key if present + // before the account pre-values are taken care of + for (i, key) in message.account_keys.iter().enumerate() { + if solana_sdk::sysvar::instructions::check_id(key) { + let mut mut_account_ref = accounts[i].borrow_mut(); + solana_sdk::sysvar::instructions::store_current_instruction( + &mut mut_account_ref.data, + instruction_index as u16, + ); + break; + } + } + let pre_accounts = Self::create_pre_accounts(message, instruction, accounts); let mut invoke_context = ThisInvokeContext::new( instruction.program_id(&message.account_keys), @@ -712,6 +726,7 @@ impl MessageProcessor { rent_collector, log_collector.clone(), executors.clone(), + instruction_index, ) .map_err(|err| TransactionError::InstructionError(instruction_index as u8, err))?; } diff --git a/sdk/benches/serialize_instructions.rs b/sdk/benches/serialize_instructions.rs new file mode 100644 index 0000000000..dc80829ed5 --- /dev/null +++ b/sdk/benches/serialize_instructions.rs @@ -0,0 +1,63 @@ +#![feature(test)] + +extern crate test; +use bincode::{deserialize, serialize}; +use solana_sdk::instruction::{AccountMeta, Instruction}; +use solana_sdk::message::Message; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::sysvar::instructions; +use test::Bencher; + +fn make_instructions() -> Vec { + let meta = AccountMeta::new(Pubkey::new_rand(), false); + let inst = Instruction::new(Pubkey::new_rand(), &[0; 10], vec![meta; 4]); + vec![inst; 4] +} + +#[bench] +fn bench_bincode_instruction_serialize(b: &mut Bencher) { + let instructions = make_instructions(); + b.iter(|| { + test::black_box(serialize(&instructions).unwrap()); + }); +} + +#[bench] +fn bench_manual_instruction_serialize(b: &mut Bencher) { + let instructions = make_instructions(); + let message = Message::new(&instructions, None); + b.iter(|| { + test::black_box(message.serialize_instructions()); + }); +} + +#[bench] +fn bench_bincode_instruction_deserialize(b: &mut Bencher) { + let instructions = make_instructions(); + let serialized = serialize(&instructions).unwrap(); + b.iter(|| { + test::black_box(deserialize::>(&serialized).unwrap()); + }); +} + +#[bench] +fn bench_manual_instruction_deserialize(b: &mut Bencher) { + let instructions = make_instructions(); + let message = Message::new(&instructions, None); + let serialized = message.serialize_instructions(); + b.iter(|| { + for i in 0..instructions.len() { + test::black_box(instructions::get_instruction(i, &serialized).unwrap()); + } + }); +} + +#[bench] +fn bench_manual_instruction_deserialize_single(b: &mut Bencher) { + let instructions = make_instructions(); + let message = Message::new(&instructions, None); + let serialized = message.serialize_instructions(); + b.iter(|| { + test::black_box(instructions::get_instruction(3, &serialized).unwrap()); + }); +} diff --git a/sdk/src/instruction.rs b/sdk/src/instruction.rs index ccd35d60e5..e4125b35ba 100644 --- a/sdk/src/instruction.rs +++ b/sdk/src/instruction.rs @@ -169,7 +169,7 @@ pub enum InstructionError { ComputationalBudgetExceeded, } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct Instruction { /// Pubkey of the instruction processor that executes this instruction pub program_id: Pubkey, diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 8c13482f80..1752185d52 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -83,6 +83,7 @@ pub mod log; pub mod program; pub mod program_error; pub mod program_stubs; +pub mod serialize_utils; // Modules not usable by on-chain programs #[cfg(not(feature = "program"))] diff --git a/sdk/src/message.rs b/sdk/src/message.rs index 4814fdb1d8..fe3be66b2f 100644 --- a/sdk/src/message.rs +++ b/sdk/src/message.rs @@ -1,6 +1,9 @@ //! A library for generating a message from a sequence of instructions use crate::sanitize::{Sanitize, SanitizeError}; +use crate::serialize_utils::{ + append_slice, append_u16, append_u8, read_pubkey, read_slice, read_u16, read_u8, +}; use crate::{ hash::Hash, instruction::{AccountMeta, CompiledInstruction, Instruction}, @@ -322,6 +325,98 @@ impl Message { } (writable_keys, readonly_keys) } + + // First encode the number of instructions: + // [0..2 - num_instructions + // + // Then a table of offsets of where to find them in the data + // 3..2*num_instructions table of instruction offsets + // + // Each instruction is then encoded as: + // 0..2 - num_accounts + // 3 - meta_byte -> (bit 0 signer, bit 1 is_writable) + // 4..36 - pubkey - 32 bytes + // 36..64 - program_id + // 33..34 - data len - u16 + // 35..data_len - data + pub fn serialize_instructions(&self) -> Vec { + // 64 bytes is a reasonable guess, calculating exactly is slower in benchmarks + let mut data = Vec::with_capacity(self.instructions.len() * (32 * 2)); + append_u16(&mut data, self.instructions.len() as u16); + for _ in 0..self.instructions.len() { + append_u16(&mut data, 0); + } + for (i, instruction) in self.instructions.iter().enumerate() { + let start_instruction_offset = data.len() as u16; + let start = 2 + (2 * i); + data[start..start + 2].copy_from_slice(&start_instruction_offset.to_le_bytes()); + append_u16(&mut data, instruction.accounts.len() as u16); + for account_index in &instruction.accounts { + let account_index = *account_index as usize; + let is_signer = self.is_signer(account_index); + let is_writable = self.is_writable(account_index); + let mut meta_byte = 0; + if is_signer { + meta_byte |= 1 << Self::IS_SIGNER_BIT; + } + if is_writable { + meta_byte |= 1 << Self::IS_WRITABLE_BIT; + } + append_u8(&mut data, meta_byte); + append_slice(&mut data, self.account_keys[account_index].as_ref()); + } + + let program_id = &self.account_keys[instruction.program_id_index as usize]; + append_slice(&mut data, program_id.as_ref()); + append_u16(&mut data, instruction.data.len() as u16); + append_slice(&mut data, &instruction.data); + } + data + } + + const IS_SIGNER_BIT: usize = 0; + const IS_WRITABLE_BIT: usize = 1; + + pub fn deserialize_instruction( + index: usize, + data: &[u8], + ) -> Result { + let mut current = 0; + let _num_instructions = read_u16(&mut current, &data)?; + + // index into the instruction byte-offset table. + current += index * 2; + let start = read_u16(&mut current, &data)?; + + current = start as usize; + let num_accounts = read_u16(&mut current, &data)?; + let mut accounts = Vec::with_capacity(num_accounts as usize); + for _ in 0..num_accounts { + let meta_byte = read_u8(&mut current, &data)?; + let mut is_signer = false; + let mut is_writable = false; + if meta_byte & (1 << Self::IS_SIGNER_BIT) != 0 { + is_signer = true; + } + if meta_byte & (1 << Self::IS_WRITABLE_BIT) != 0 { + is_writable = true; + } + let pubkey = read_pubkey(&mut current, &data)?; + accounts.push(AccountMeta { + pubkey, + is_signer, + is_writable, + }); + } + let program_id = read_pubkey(&mut current, &data)?; + let data_len = read_u16(&mut current, &data)?; + let data = read_slice(&mut current, &data, data_len as usize)?; + Ok(Instruction { + program_id, + data, + accounts, + }) + } } #[cfg(test)] @@ -673,4 +768,30 @@ mod tests { (vec![&id1, &id0], vec![&id3, &id2, &program_id]) ); } + + #[test] + fn test_decompile_instructions() { + solana_logger::setup(); + let program_id0 = Pubkey::new_rand(); + let program_id1 = Pubkey::new_rand(); + let id0 = Pubkey::new_rand(); + let id1 = Pubkey::new_rand(); + let id2 = Pubkey::new_rand(); + let id3 = Pubkey::new_rand(); + let instructions = vec![ + Instruction::new(program_id0, &0, vec![AccountMeta::new(id0, false)]), + Instruction::new(program_id0, &0, vec![AccountMeta::new(id1, true)]), + Instruction::new(program_id1, &0, vec![AccountMeta::new_readonly(id2, false)]), + Instruction::new(program_id1, &0, vec![AccountMeta::new_readonly(id3, true)]), + ]; + + let message = Message::new(&instructions, Some(&id1)); + let serialized = message.serialize_instructions(); + for (i, instruction) in instructions.iter().enumerate() { + assert_eq!( + Message::deserialize_instruction(i, &serialized).unwrap(), + *instruction + ); + } + } } diff --git a/sdk/src/serialize_utils.rs b/sdk/src/serialize_utils.rs new file mode 100644 index 0000000000..e15aaa76dd --- /dev/null +++ b/sdk/src/serialize_utils.rs @@ -0,0 +1,65 @@ +use crate::pubkey::Pubkey; +use crate::sanitize::SanitizeError; + +pub fn append_u16(buf: &mut Vec, data: u16) { + let start = buf.len(); + buf.resize(buf.len() + 2, 0); + let end = buf.len(); + buf[start..end].copy_from_slice(&data.to_le_bytes()); +} + +pub fn append_u8(buf: &mut Vec, data: u8) { + let start = buf.len(); + buf.resize(buf.len() + 1, 0); + buf[start] = data; +} + +pub fn append_slice(buf: &mut Vec, data: &[u8]) { + let start = buf.len(); + buf.resize(buf.len() + data.len(), 0); + let end = buf.len(); + buf[start..end].copy_from_slice(data); +} + +pub fn read_u8(current: &mut usize, data: &[u8]) -> Result { + if data.len() < *current + 1 { + return Err(SanitizeError::IndexOutOfBounds); + } + let e = data[*current]; + *current += 1; + Ok(e) +} + +pub fn read_pubkey(current: &mut usize, data: &[u8]) -> Result { + let len = std::mem::size_of::(); + if data.len() < *current + len { + return Err(SanitizeError::IndexOutOfBounds); + } + let e = Pubkey::new(&data[*current..*current + len]); + *current += len; + Ok(e) +} + +pub fn read_u16(current: &mut usize, data: &[u8]) -> Result { + if data.len() < *current + 2 { + return Err(SanitizeError::IndexOutOfBounds); + } + let mut fixed_data = [0u8; 2]; + fixed_data.copy_from_slice(&data[*current..*current + 2]); + let e = u16::from_le_bytes(fixed_data); + *current += 2; + Ok(e) +} + +pub fn read_slice( + current: &mut usize, + data: &[u8], + data_len: usize, +) -> Result, SanitizeError> { + if data.len() < *current + data_len { + return Err(SanitizeError::IndexOutOfBounds); + } + let e = data[*current..*current + data_len].to_vec(); + *current += data_len; + Ok(e) +} diff --git a/sdk/src/sysvar/instructions.rs b/sdk/src/sysvar/instructions.rs new file mode 100644 index 0000000000..68425d7da3 --- /dev/null +++ b/sdk/src/sysvar/instructions.rs @@ -0,0 +1,51 @@ +//! This account contains the serialized transaction instructions +//! + +use crate::instruction::Instruction; +use crate::sanitize::SanitizeError; +use crate::sysvar::Sysvar; + +pub type Instructions = Vec; + +crate::declare_sysvar_id!("instructions1111111111111111111111111111111", Instructions); + +impl Sysvar for Instructions {} + +#[cfg(not(feature = "program"))] +use crate::clock::Epoch; +#[cfg(not(feature = "program"))] +use crate::genesis_config::ClusterType; + +#[cfg(not(feature = "program"))] +pub fn is_enabled(_epoch: Epoch, cluster_type: ClusterType) -> bool { + cluster_type == ClusterType::Development +} + +pub fn get_current_instruction(data: &[u8]) -> u16 { + let mut instr_fixed_data = [0u8; 2]; + let len = data.len(); + instr_fixed_data.copy_from_slice(&data[len - 2..len]); + u16::from_le_bytes(instr_fixed_data) +} + +pub fn store_current_instruction(data: &mut [u8], instruction_index: u16) { + let last_index = data.len() - 2; + data[last_index..last_index + 2].copy_from_slice(&instruction_index.to_le_bytes()); +} + +pub fn get_instruction(index: usize, data: &[u8]) -> Result { + solana_sdk::message::Message::deserialize_instruction(index, data) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_store_instruction() { + let mut data = [4u8; 10]; + store_current_instruction(&mut data, 3); + assert_eq!(get_current_instruction(&data), 3); + assert_eq!([4u8; 8], data[0..8]); + } +} diff --git a/sdk/src/sysvar/mod.rs b/sdk/src/sysvar/mod.rs index 75a1177c36..79d169513a 100644 --- a/sdk/src/sysvar/mod.rs +++ b/sdk/src/sysvar/mod.rs @@ -11,6 +11,7 @@ use crate::{ pub mod clock; pub mod epoch_schedule; pub mod fees; +pub mod instructions; pub mod recent_blockhashes; pub mod rent; pub mod rewards; @@ -28,6 +29,7 @@ pub fn is_sysvar_id(id: &Pubkey) -> bool { || slot_hashes::check_id(id) || slot_history::check_id(id) || stake_history::check_id(id) + || instructions::check_id(id) } #[macro_export]