diff --git a/ledger-tool/src/program.rs b/ledger-tool/src/program.rs index 7e0d14a6a69302..7b0a8f81aa8d83 100644 --- a/ledger-tool/src/program.rs +++ b/ledger-tool/src/program.rs @@ -29,7 +29,9 @@ use { verifier::RequisiteVerifier, }, solana_sdk_ids::{bpf_loader_upgradeable, sysvar}, - solana_transaction_context::{IndexOfAccount, InstructionAccount}, + solana_transaction_context::{ + IndexOfAccount, InstructionAccountView, InstructionAccountViewVector, + }, std::{ collections::HashMap, fmt::{self, Debug, Formatter}, @@ -398,7 +400,7 @@ pub fn program(ledger_path: &Path, matches: &ArgMatches<'_>) { let bank = load_blockstore(&ledger_path, matches); let loader_id = bpf_loader_upgradeable::id(); let mut transaction_accounts = Vec::new(); - let mut instruction_accounts = Vec::new(); + let mut instruction_accounts = InstructionAccountViewVector::new(); let mut program_id = Pubkey::new_unique(); let mut cached_account_keys = vec![]; @@ -409,7 +411,13 @@ pub fn program(ledger_path: &Path, matches: &ArgMatches<'_>) { pubkey, AccountSharedData::new(0, allocation_size, &Pubkey::new_unique()), )); - instruction_accounts.push(InstructionAccount::new(0, 0, 0, false, true)); + instruction_accounts.push(InstructionAccountView { + index_in_transaction: 0, + index_in_caller: 0, + index_in_callee: 0, + is_signer: false, + is_writable: true, + }); vec![] } Err(_) => { @@ -424,71 +432,72 @@ pub fn program(ledger_path: &Path, matches: &ArgMatches<'_>) { // Maps a public key to the transaction account index let mut txn_acct_indices = HashMap::::with_capacity(input.accounts.len()); - instruction_accounts = input - .accounts - .into_iter() - .map(|account_info| { - let pubkey = account_info.key.parse::().unwrap_or_else(|err| { - eprintln!("Invalid key in input {}, error {}", account_info.key, err); - exit(1); - }); - let data = account_info.data.unwrap_or_default(); - let space = data.len(); - let account = if let Some(account) = bank.get_account_with_fixed_root(&pubkey) { - let owner = *account.owner(); - if bpf_loader_upgradeable::check_id(&owner) { - if let Ok(UpgradeableLoaderState::Program { - programdata_address, - }) = account.state() + let mut new_instr_accs = InstructionAccountViewVector::new(); + for account_info in input.accounts.into_iter() { + let pubkey = account_info.key.parse::().unwrap_or_else(|err| { + eprintln!("Invalid key in input {}, error {}", account_info.key, err); + exit(1); + }); + let data = account_info.data.unwrap_or_default(); + let space = data.len(); + let account = if let Some(account) = bank.get_account_with_fixed_root(&pubkey) { + let owner = *account.owner(); + if bpf_loader_upgradeable::check_id(&owner) { + if let Ok(UpgradeableLoaderState::Program { + programdata_address, + }) = account.state() + { + debug!("Program data address {}", programdata_address); + if bank + .get_account_with_fixed_root(&programdata_address) + .is_some() { - debug!("Program data address {programdata_address}"); - if bank - .get_account_with_fixed_root(&programdata_address) - .is_some() - { - cached_account_keys.push(pubkey); - } + cached_account_keys.push(pubkey); } } - // Override account data and lamports from input file if provided - if space > 0 { - let lamports = account_info.lamports.unwrap_or(account.lamports()); - let mut account = AccountSharedData::new(lamports, space, &owner); - account.set_data_from_slice(&data); - account - } else { - account - } - } else { - let owner = account_info - .owner - .unwrap_or(Pubkey::new_unique().to_string()); - let owner = owner.parse::().unwrap_or_else(|err| { - eprintln!("Invalid owner key in input {owner}, error {err}"); - Pubkey::new_unique() - }); - let lamports = account_info.lamports.unwrap_or(0); + } + // Override account data and lamports from input file if provided + if space > 0 { + let lamports = account_info.lamports.unwrap_or(account.lamports()); let mut account = AccountSharedData::new(lamports, space, &owner); account.set_data_from_slice(&data); account - }; - let txn_acct_index = if let Some(idx) = txn_acct_indices.get(&pubkey) { - *idx } else { - let idx = transaction_accounts.len(); - txn_acct_indices.insert(pubkey, idx); - transaction_accounts.push((pubkey, account)); - idx - }; - InstructionAccount::new( - txn_acct_index as IndexOfAccount, - txn_acct_index as IndexOfAccount, - txn_acct_index as IndexOfAccount, - account_info.is_signer.unwrap_or(false), - account_info.is_writable.unwrap_or(false), - ) - }) - .collect(); + account + } + } else { + let owner = account_info + .owner + .unwrap_or(Pubkey::new_unique().to_string()); + let owner = owner.parse::().unwrap_or_else(|err| { + eprintln!("Invalid owner key in input {owner}, error {err}"); + Pubkey::new_unique() + }); + let lamports = account_info.lamports.unwrap_or(0); + let mut account = AccountSharedData::new(lamports, space, &owner); + account.set_data_from_slice(&data); + account + }; + let txn_acct_index = if let Some(idx) = txn_acct_indices.get(&pubkey) { + *idx + } else { + let idx = transaction_accounts.len(); + txn_acct_indices.insert(pubkey, idx); + transaction_accounts.push((pubkey, account)); + idx + }; + + let instr_acc = InstructionAccountView { + index_in_transaction: txn_acct_index as IndexOfAccount, + index_in_caller: txn_acct_index as IndexOfAccount, + index_in_callee: txn_acct_index as IndexOfAccount, + is_signer: account_info.is_signer.unwrap_or(false), + is_writable: account_info.is_writable.unwrap_or(false), + }; + new_instr_accs.push(instr_acc); + } + instruction_accounts = std::mem::take(&mut new_instr_accs); + input.instruction_data } }; @@ -527,7 +536,7 @@ pub fn program(ledger_path: &Path, matches: &ArgMatches<'_>) { .unwrap() .configure( &[program_index, program_index.saturating_add(1)], - &instruction_accounts, + instruction_accounts, &instruction_data, ); invoke_context.push().unwrap(); diff --git a/program-runtime/src/invoke_context.rs b/program-runtime/src/invoke_context.rs index 70a8f3de580225..cca8936653cca8 100644 --- a/program-runtime/src/invoke_context.rs +++ b/program-runtime/src/invoke_context.rs @@ -31,7 +31,8 @@ use { solana_svm_feature_set::SVMFeatureSet, solana_timings::{ExecuteDetailsTimings, ExecuteTimings}, solana_transaction_context::{ - IndexOfAccount, InstructionAccount, TransactionAccount, TransactionContext, + IndexOfAccount, InstructionAccountView, InstructionAccountViewVector, TransactionAccount, + TransactionContext, }, solana_type_overrides::sync::{atomic::Ordering, Arc}, std::{ @@ -312,7 +313,7 @@ impl<'a> InvokeContext<'a> { let mut compute_units_consumed = 0; self.process_instruction( &instruction.data, - &instruction_accounts, + instruction_accounts, &program_indices, &mut compute_units_consumed, &mut ExecuteTimings::default(), @@ -326,13 +327,14 @@ impl<'a> InvokeContext<'a> { &mut self, instruction: &StableInstruction, signers: &[Pubkey], - ) -> Result<(Vec, Vec), InstructionError> { + ) -> Result<(InstructionAccountViewVector, Vec), InstructionError> { // Finds the index of each account in the instruction by its pubkey. // Then normalizes / unifies the privileges of duplicate accounts. // Note: This is an O(n^2) algorithm, // but performed on a very small slice and requires no heap allocations. let instruction_context = self.transaction_context.get_current_instruction_context()?; - let mut deduplicated_instruction_accounts: Vec = Vec::new(); + let mut deduplicated_instruction_accounts: Vec = + Vec::with_capacity(instruction.accounts.len() as usize); let mut duplicate_indicies = Vec::with_capacity(instruction.accounts.len() as usize); for (instruction_account_index, account_meta) in instruction.accounts.iter().enumerate() { let index_in_transaction = self @@ -346,6 +348,7 @@ impl<'a> InvokeContext<'a> { ); InstructionError::MissingAccount })?; + if let Some(duplicate_index) = deduplicated_instruction_accounts .iter() @@ -357,10 +360,10 @@ impl<'a> InvokeContext<'a> { let instruction_account = deduplicated_instruction_accounts .get_mut(duplicate_index) .ok_or(InstructionError::NotEnoughAccountKeys)?; - instruction_account - .set_is_signer(instruction_account.is_signer() || account_meta.is_signer); - instruction_account - .set_is_writable(instruction_account.is_writable() || account_meta.is_writable); + instruction_account.is_signer = + instruction_account.is_signer || account_meta.is_signer; + instruction_account.is_writable = + instruction_account.is_writable || account_meta.is_writable; } else { let index_in_caller = instruction_context .find_index_of_instruction_account( @@ -376,15 +379,16 @@ impl<'a> InvokeContext<'a> { InstructionError::MissingAccount })?; duplicate_indicies.push(deduplicated_instruction_accounts.len()); - deduplicated_instruction_accounts.push(InstructionAccount::new( + deduplicated_instruction_accounts.push(InstructionAccountView { index_in_transaction, index_in_caller, - instruction_account_index as IndexOfAccount, - account_meta.is_signer, - account_meta.is_writable, - )); + index_in_callee: instruction_account_index as IndexOfAccount, + is_signer: account_meta.is_signer, + is_writable: account_meta.is_writable, + }); } } + for instruction_account in deduplicated_instruction_accounts.iter() { let borrowed_account = instruction_context.try_borrow_instruction_account( self.transaction_context, @@ -392,7 +396,7 @@ impl<'a> InvokeContext<'a> { )?; // Readonly in caller cannot become writable in callee - if instruction_account.is_writable() && !borrowed_account.is_writable() { + if instruction_account.is_writable && !borrowed_account.is_writable() { ic_msg!( self, "{}'s writable privilege escalated", @@ -403,7 +407,7 @@ impl<'a> InvokeContext<'a> { // To be signed in the callee, // it must be either signed in the caller or by the program - if instruction_account.is_signer() + if instruction_account.is_signer && !(borrowed_account.is_signer() || signers.contains(borrowed_account.get_key())) { ic_msg!( @@ -414,15 +418,18 @@ impl<'a> InvokeContext<'a> { return Err(InstructionError::PrivilegeEscalation); } } - let instruction_accounts = duplicate_indicies - .into_iter() - .map(|duplicate_index| { + + let mut instruction_accounts = + InstructionAccountViewVector::with_capacity(duplicate_indicies.len()); + + for duplicate_index in duplicate_indicies.into_iter() { + instruction_accounts.push( deduplicated_instruction_accounts .get(duplicate_index) .cloned() - .ok_or(InstructionError::NotEnoughAccountKeys) - }) - .collect::, InstructionError>>()?; + .ok_or(InstructionError::NotEnoughAccountKeys)?, + ); + } // Find and validate executables / program accounts let callee_program_id = instruction.program_id; @@ -452,7 +459,7 @@ impl<'a> InvokeContext<'a> { pub fn process_instruction( &mut self, instruction_data: &[u8], - instruction_accounts: &[InstructionAccount], + instruction_accounts: InstructionAccountViewVector, program_indices: &[IndexOfAccount], compute_units_consumed: &mut u64, timings: &mut ExecuteTimings, @@ -473,7 +480,7 @@ impl<'a> InvokeContext<'a> { &mut self, program_id: &Pubkey, instruction_data: &[u8], - instruction_accounts: &[InstructionAccount], + instruction_accounts: InstructionAccountViewVector, program_indices: &[IndexOfAccount], message_instruction_datas_iter: impl Iterator, ) -> Result<(), InstructionError> { @@ -481,7 +488,6 @@ impl<'a> InvokeContext<'a> { .get_next_instruction_context()? .configure(program_indices, instruction_accounts, instruction_data); self.push()?; - let instruction_datas: Vec<_> = message_instruction_datas_iter.collect(); self.environment_config .epoch_stake_callback @@ -824,8 +830,8 @@ pub fn mock_process_instruction_with_feature_set< mut post_adjustments: G, feature_set: &SVMFeatureSet, ) -> Vec { - let mut instruction_accounts: Vec = - Vec::with_capacity(instruction_account_metas.len()); + let mut instruction_accounts = + InstructionAccountViewVector::with_capacity(instruction_account_metas.len()); for (instruction_account_index, account_meta) in instruction_account_metas.iter().enumerate() { let index_in_transaction = transaction_accounts .iter() @@ -833,20 +839,20 @@ pub fn mock_process_instruction_with_feature_set< .unwrap_or(transaction_accounts.len()) as IndexOfAccount; let index_in_callee = instruction_accounts - .get(0..instruction_account_index) + .get_metadata_for_test(0..instruction_account_index) .unwrap() .iter() .position(|instruction_account| { instruction_account.index_in_transaction == index_in_transaction }) .unwrap_or(instruction_account_index) as IndexOfAccount; - instruction_accounts.push(InstructionAccount::new( - index_in_transaction, + instruction_accounts.push(InstructionAccountView { index_in_transaction, + index_in_caller: index_in_transaction, index_in_callee, - account_meta.is_signer, - account_meta.is_writable, - )); + is_signer: account_meta.is_signer, + is_writable: account_meta.is_writable, + }); } if program_indices.is_empty() { program_indices.insert(0, transaction_accounts.len() as IndexOfAccount); @@ -880,7 +886,7 @@ pub fn mock_process_instruction_with_feature_set< pre_adjustments(&mut invoke_context); let result = invoke_context.process_instruction( instruction_data, - &instruction_accounts, + instruction_accounts, &program_indices, &mut 0, &mut ExecuteTimings::default(), @@ -960,17 +966,16 @@ mod tests { let instruction_context = transaction_context.get_current_instruction_context()?; let instruction_data = instruction_context.get_instruction_data(); let program_id = instruction_context.get_last_program_key(transaction_context)?; - let instruction_accounts = (0..4) - .map(|instruction_account_index| { - InstructionAccount::new( - instruction_account_index, - instruction_account_index, - instruction_account_index, - false, - false, - ) - }) - .collect::>(); + let mut instruction_accounts = InstructionAccountViewVector::new(); + for instruction_account_index in 0..4 { + instruction_accounts.push(InstructionAccountView { + index_in_transaction: instruction_account_index, + index_in_caller: instruction_account_index, + index_in_callee: instruction_account_index, + is_signer: false, + is_writable: false, + }); + } assert_eq!( program_id, instruction_context @@ -1023,7 +1028,7 @@ mod tests { .transaction_context .get_next_instruction_context() .unwrap() - .configure(&[3], &instruction_accounts, &[]); + .configure(&[3], instruction_accounts, &[]); let result = invoke_context.push(); assert_eq!(result, Err(InstructionError::UnbalancedInstruction)); result?; @@ -1061,33 +1066,33 @@ mod tests { .saturating_add(1); let mut invoke_stack = vec![]; let mut transaction_accounts = vec![]; - let mut instruction_accounts = vec![]; + let mut instruction_accounts = InstructionAccountViewVector::new(); for index in 0..one_more_than_max_depth { invoke_stack.push(solana_pubkey::new_rand()); transaction_accounts.push(( solana_pubkey::new_rand(), AccountSharedData::new(index as u64, 1, invoke_stack.get(index).unwrap()), )); - instruction_accounts.push(InstructionAccount::new( - index as IndexOfAccount, - index as IndexOfAccount, - instruction_accounts.len() as IndexOfAccount, - false, - true, - )); + instruction_accounts.push(InstructionAccountView { + index_in_transaction: index as IndexOfAccount, + index_in_caller: index as IndexOfAccount, + index_in_callee: instruction_accounts.len() as IndexOfAccount, + is_signer: false, + is_writable: true, + }); } for (index, program_id) in invoke_stack.iter().enumerate() { transaction_accounts.push(( *program_id, AccountSharedData::new(1, 1, &solana_pubkey::Pubkey::default()), )); - instruction_accounts.push(InstructionAccount::new( - index as IndexOfAccount, - index as IndexOfAccount, - index as IndexOfAccount, - false, - false, - )); + instruction_accounts.push(InstructionAccountView { + index_in_transaction: index as IndexOfAccount, + index_in_caller: index as IndexOfAccount, + index_in_callee: index as IndexOfAccount, + is_signer: false, + is_writable: false, + }); } with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts); @@ -1100,7 +1105,7 @@ mod tests { .unwrap() .configure( &[one_more_than_max_depth.saturating_add(depth_reached) as IndexOfAccount], - &instruction_accounts, + instruction_accounts.clone(), &[], ); if Err(InstructionError::CallDepth) == invoke_context.push() { @@ -1157,17 +1162,16 @@ mod tests { AccountMeta::new(transaction_accounts.get(1).unwrap().0, false), AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false), ]; - let instruction_accounts = (0..4) - .map(|instruction_account_index| { - InstructionAccount::new( - instruction_account_index, - instruction_account_index, - instruction_account_index, - false, - instruction_account_index < 2, - ) - }) - .collect::>(); + let mut instruction_accounts = InstructionAccountViewVector::new(); + for instruction_account_index in 0..4 { + instruction_accounts.push(InstructionAccountView { + index_in_transaction: instruction_account_index, + index_in_caller: instruction_account_index, + index_in_callee: instruction_account_index, + is_signer: false, + is_writable: instruction_account_index < 2, + }); + } with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts); let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default(); program_cache_for_tx_batch.replenish( @@ -1181,7 +1185,7 @@ mod tests { .transaction_context .get_next_instruction_context() .unwrap() - .configure(&[4], &instruction_accounts, &[]); + .configure(&[4], instruction_accounts, &[]); invoke_context.push().unwrap(); let inner_instruction = Instruction::new_with_bincode(callee_program_id, &instruction, metas.clone()); @@ -1215,17 +1219,17 @@ mod tests { AccountMeta::new(transaction_accounts.get(1).unwrap().0, false), AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false), ]; - let instruction_accounts = (0..4) - .map(|instruction_account_index| { - InstructionAccount::new( - instruction_account_index, - instruction_account_index, - instruction_account_index, - false, - instruction_account_index < 2, - ) - }) - .collect::>(); + + let mut instruction_accounts = InstructionAccountViewVector::new(); + for instruction_account_index in 0..4 { + instruction_accounts.push(InstructionAccountView { + index_in_transaction: instruction_account_index, + index_in_caller: instruction_account_index, + index_in_callee: instruction_account_index, + is_signer: false, + is_writable: instruction_account_index < 2, + }); + } with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts); let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default(); program_cache_for_tx_batch.replenish( @@ -1240,7 +1244,7 @@ mod tests { .transaction_context .get_next_instruction_context() .unwrap() - .configure(&[4], &instruction_accounts, &[]); + .configure(&[4], instruction_accounts, &[]); invoke_context.push().unwrap(); let inner_instruction = Instruction::new_with_bincode( callee_program_id, @@ -1258,7 +1262,7 @@ mod tests { let mut compute_units_consumed = 0; let result = invoke_context.process_instruction( &inner_instruction.data, - &inner_instruction_accounts, + inner_instruction_accounts, &program_indices, &mut compute_units_consumed, &mut ExecuteTimings::default(), @@ -1292,7 +1296,7 @@ mod tests { .transaction_context .get_next_instruction_context() .unwrap() - .configure(&[0], &[], &[]); + .configure(&[0], InstructionAccountViewVector::new(), &[]); invoke_context.push().unwrap(); assert_eq!(*invoke_context.get_compute_budget(), execution_budget); invoke_context.pop().unwrap(); @@ -1314,10 +1318,22 @@ mod tests { (Pubkey::new_unique(), dummy_account), (program_key, program_account), ]; - let instruction_accounts = [ - InstructionAccount::new(0, 0, 0, false, true), - InstructionAccount::new(1, 1, 1, false, false), - ]; + let instruction_accounts = InstructionAccountViewVector::from_vector(vec![ + InstructionAccountView { + index_in_transaction: 0, + index_in_caller: 0, + index_in_callee: 0, + is_signer: false, + is_writable: true, + }, + InstructionAccountView { + index_in_transaction: 1, + index_in_caller: 1, + index_in_callee: 1, + is_signer: false, + is_writable: false, + }, + ]); with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts); let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default(); program_cache_for_tx_batch.replenish( @@ -1331,7 +1347,7 @@ mod tests { let result = invoke_context.process_instruction( &instruction_data, - &instruction_accounts, + instruction_accounts, &[2], &mut 0, &mut ExecuteTimings::default(), diff --git a/program-runtime/src/serialization.rs b/program-runtime/src/serialization.rs index 1d8544f84ba0a6..204f4ae48948de 100644 --- a/program-runtime/src/serialization.rs +++ b/program-runtime/src/serialization.rs @@ -605,7 +605,7 @@ mod tests { solana_sbpf::{memory_region::MemoryMapping, program::SBPFVersion, vm::Config}, solana_sdk_ids::bpf_loader, solana_system_interface::MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION, - solana_transaction_context::InstructionAccount, + solana_transaction_context::{InstructionAccountView, InstructionAccountViewVector}, std::{ cell::RefCell, mem::transmute, @@ -617,26 +617,27 @@ mod tests { fn deduplicated_instruction_accounts( transaction_indexes: &[IndexOfAccount], is_writable: fn(usize) -> bool, - ) -> Vec { - transaction_indexes - .iter() - .enumerate() - .map(|(index_in_instruction, index_in_transaction)| { - let index_in_callee = transaction_indexes - .get(0..index_in_instruction) - .unwrap() - .iter() - .position(|account_index| account_index == index_in_transaction) - .unwrap_or(index_in_instruction); - InstructionAccount::new( - *index_in_transaction, - *index_in_transaction, - index_in_callee as IndexOfAccount, - false, - is_writable(index_in_instruction), - ) - }) - .collect() + ) -> InstructionAccountViewVector { + let mut instr_accounts = + InstructionAccountViewVector::with_capacity(transaction_indexes.len()); + + for (index_in_instruction, index_in_transaction) in transaction_indexes.iter().enumerate() { + let index_in_callee = transaction_indexes + .get(0..index_in_instruction) + .unwrap() + .iter() + .position(|account_index| account_index == index_in_transaction) + .unwrap_or(index_in_instruction); + instr_accounts.push(InstructionAccountView { + index_in_transaction: *index_in_transaction, + index_in_caller: *index_in_transaction, + index_in_callee: index_in_callee as IndexOfAccount, + is_signer: false, + is_writable: is_writable(index_in_instruction), + }); + } + + instr_accounts } #[test] @@ -703,7 +704,8 @@ mod tests { let mut instruction_accounts = deduplicated_instruction_accounts(&transaction_accounts_indexes, |_| false); if append_dup_account { - instruction_accounts.push(instruction_accounts.last().cloned().unwrap()); + let last = instruction_accounts.iter().next_back().unwrap(); + instruction_accounts.push(last); } let program_indices = [0]; let instruction_data = vec![]; @@ -717,7 +719,7 @@ mod tests { .transaction_context .get_next_instruction_context() .unwrap() - .configure(&program_indices, &instruction_accounts, &instruction_data); + .configure(&program_indices, instruction_accounts, &instruction_data); invoke_context.push().unwrap(); let instruction_context = invoke_context .transaction_context @@ -860,7 +862,7 @@ mod tests { .transaction_context .get_next_instruction_context() .unwrap() - .configure(&program_indices, &instruction_accounts, &instruction_data); + .configure(&program_indices, instruction_accounts, &instruction_data); invoke_context.push().unwrap(); let instruction_context = invoke_context .transaction_context @@ -1106,7 +1108,7 @@ mod tests { .transaction_context .get_next_instruction_context() .unwrap() - .configure(&program_indices, &instruction_accounts, &instruction_data); + .configure(&program_indices, instruction_accounts, &instruction_data); invoke_context.push().unwrap(); let instruction_context = invoke_context .transaction_context @@ -1375,7 +1377,7 @@ mod tests { transaction_context .get_next_instruction_context() .unwrap() - .configure(&program_indices, &instruction_accounts, &instruction_data); + .configure(&program_indices, instruction_accounts, &instruction_data); transaction_context.push().unwrap(); let instruction_context = transaction_context .get_current_instruction_context() diff --git a/program-test/src/lib.rs b/program-test/src/lib.rs index 9bf8fafaa9f894..0ef00db5292d9f 100644 --- a/program-test/src/lib.rs +++ b/program-test/src/lib.rs @@ -320,7 +320,7 @@ impl solana_sysvar::program_stubs::SyscallStubs for SyscallStubs { .set_owner(account_info.owner.as_ref()) .unwrap(); } - if instruction_account.is_writable() { + if instruction_account.is_writable { account_indices.push((instruction_account.index_in_caller, account_info_index)); } } @@ -329,7 +329,7 @@ impl solana_sysvar::program_stubs::SyscallStubs for SyscallStubs { invoke_context .process_instruction( &instruction.data, - &instruction_accounts, + instruction_accounts, &program_indices, &mut compute_units_consumed, &mut ExecuteTimings::default(), diff --git a/programs/bpf_loader/benches/serialization.rs b/programs/bpf_loader/benches/serialization.rs index 218a4e3512d967..24a193206c6aea 100644 --- a/programs/bpf_loader/benches/serialization.rs +++ b/programs/bpf_loader/benches/serialization.rs @@ -5,7 +5,9 @@ use { solana_pubkey::Pubkey, solana_rent::Rent, solana_sdk_ids::{bpf_loader, bpf_loader_deprecated}, - solana_transaction_context::{IndexOfAccount, InstructionAccount, TransactionContext}, + solana_transaction_context::{ + IndexOfAccount, InstructionAccountView, InstructionAccountViewVector, TransactionContext, + }, }; fn create_inputs(owner: Pubkey, num_instruction_accounts: usize) -> TransactionContext { @@ -82,7 +84,7 @@ fn create_inputs(owner: Pubkey, num_instruction_accounts: usize) -> TransactionC }), ), ]; - let mut instruction_accounts: Vec = Vec::new(); + let mut instruction_accounts = InstructionAccountViewVector::new(); for (instruction_account_index, index_in_transaction) in [1, 1, 2, 3, 4, 4, 5, 6] .into_iter() .cycle() @@ -93,13 +95,13 @@ fn create_inputs(owner: Pubkey, num_instruction_accounts: usize) -> TransactionC .iter() .position(|account| account.index_in_transaction == index_in_transaction) .unwrap_or(instruction_account_index) as IndexOfAccount; - instruction_accounts.push(InstructionAccount::new( - instruction_account_index as IndexOfAccount, + instruction_accounts.push(InstructionAccountView { + index_in_caller: instruction_account_index as IndexOfAccount, index_in_transaction, index_in_callee, - false, - instruction_account_index >= 4, - )); + is_signer: false, + is_writable: instruction_account_index >= 4, + }); } let mut transaction_context = @@ -108,7 +110,7 @@ fn create_inputs(owner: Pubkey, num_instruction_accounts: usize) -> TransactionC transaction_context .get_next_instruction_context() .unwrap() - .configure(&[0], &instruction_accounts, &instruction_data); + .configure(&[0], instruction_accounts, &instruction_data); transaction_context.push().unwrap(); transaction_context } diff --git a/programs/bpf_loader/src/syscalls/cpi.rs b/programs/bpf_loader/src/syscalls/cpi.rs index 3142ab7fc8d4ed..116c4c602b2529 100644 --- a/programs/bpf_loader/src/syscalls/cpi.rs +++ b/programs/bpf_loader/src/syscalls/cpi.rs @@ -8,7 +8,7 @@ use { }, solana_sbpf::ebpf, solana_stable_layout::stable_instruction::StableInstruction, - solana_transaction_context::BorrowedAccount, + solana_transaction_context::{BorrowedAccount, InstructionAccountViewVector}, std::mem, }; @@ -328,7 +328,7 @@ trait SyscallInvokeSigned { invoke_context: &mut InvokeContext, ) -> Result; fn translate_accounts<'a>( - instruction_accounts: &[InstructionAccount], + instruction_accounts: &InstructionAccountViewVector, account_infos_addr: u64, account_infos_len: u64, is_loader_deprecated: bool, @@ -427,7 +427,7 @@ impl SyscallInvokeSigned for SyscallInvokeSignedRust { } fn translate_accounts<'a>( - instruction_accounts: &[InstructionAccount], + instruction_accounts: &InstructionAccountViewVector, account_infos_addr: u64, account_infos_len: u64, is_loader_deprecated: bool, @@ -649,7 +649,7 @@ impl SyscallInvokeSigned for SyscallInvokeSignedC { } fn translate_accounts<'a>( - instruction_accounts: &[InstructionAccount], + instruction_accounts: &InstructionAccountViewVector, account_infos_addr: u64, account_infos_len: u64, is_loader_deprecated: bool, @@ -775,7 +775,7 @@ where // Finish translating accounts, build CallerAccount values and update callee // accounts in preparation of executing the callee. fn translate_and_update_accounts<'a, T, F>( - instruction_accounts: &[InstructionAccount], + instruction_accounts: &InstructionAccountViewVector, account_info_keys: &[&Pubkey], account_infos: &[T], account_infos_addr: u64, @@ -875,8 +875,8 @@ where accounts.push(TranslatedAccount { index_in_caller: instruction_account.index_in_caller, caller_account, - update_caller_account_region: instruction_account.is_writable() || update_caller, - update_caller_account_info: instruction_account.is_writable(), + update_caller_account_region: instruction_account.is_writable || update_caller, + update_caller_account_info: instruction_account.is_writable, }); } else { ic_msg!( @@ -1045,7 +1045,7 @@ fn cpi_common( let mut compute_units_consumed = 0; invoke_context.process_instruction( &instruction.data, - &instruction_accounts, + instruction_accounts, &program_indices, &mut compute_units_consumed, &mut ExecuteTimings::default(), @@ -1302,7 +1302,9 @@ mod tests { ebpf::MM_INPUT_START, memory_region::MemoryRegion, program::SBPFVersion, vm::Config, }, solana_sdk_ids::system_program, - solana_transaction_context::TransactionAccount, + solana_transaction_context::{ + InstructionAccountView, InstructionAccountViewVector, TransactionAccount, + }, std::{ cell::{Cell, RefCell}, mem, ptr, @@ -1321,19 +1323,17 @@ mod tests { $instruction_accounts:expr) => { let program_accounts = $program_accounts; let instruction_data = $instruction_data; - let instruction_accounts = $instruction_accounts - .iter() - .enumerate() - .map(|(index_in_callee, index_in_transaction)| { - InstructionAccount::new( - *index_in_transaction as IndexOfAccount, - *index_in_transaction as IndexOfAccount, - index_in_callee as IndexOfAccount, - false, - $transaction_accounts[*index_in_transaction as usize].2, - ) - }) - .collect::>(); + let mut instruction_accounts = InstructionAccountViewVector::new(); + for (index_in_callee, index_in_transaction) in $instruction_accounts.iter().enumerate() + { + instruction_accounts.push(InstructionAccountView { + index_in_transaction: *index_in_transaction as IndexOfAccount, + index_in_caller: *index_in_transaction as IndexOfAccount, + index_in_callee: index_in_callee as IndexOfAccount, + is_signer: false, + is_writable: $transaction_accounts[*index_in_transaction as usize].2, + }); + } let transaction_accounts = $transaction_accounts .into_iter() .map(|a| (a.0, a.1)) @@ -1351,7 +1351,7 @@ mod tests { .transaction_context .get_next_instruction_context() .unwrap() - .configure(program_accounts, &instruction_accounts, instruction_data); + .configure(program_accounts, instruction_accounts, instruction_data); $invoke_context.push().unwrap(); }; } @@ -1877,11 +1877,24 @@ mod tests { mock_create_vm!(_vm, Vec::new(), vec![account_metadata], &mut invoke_context); + let instruction_accounts = InstructionAccountViewVector::from_vector(vec![ + InstructionAccountView { + index_in_transaction: 1, + index_in_caller: 0, + index_in_callee: 0, + is_signer: false, + is_writable: true, + }, + InstructionAccountView { + index_in_transaction: 1, + index_in_caller: 0, + index_in_callee: 0, + is_signer: false, + is_writable: true, + }, + ]); let accounts = SyscallInvokeSignedRust::translate_accounts( - &[ - InstructionAccount::new(1, 0, 0, false, true), - InstructionAccount::new(1, 0, 0, false, true), - ], + &instruction_accounts, vm_addr, 1, false, diff --git a/programs/bpf_loader/src/syscalls/mod.rs b/programs/bpf_loader/src/syscalls/mod.rs index 32b01375cc0f99..f758da39cecbe8 100644 --- a/programs/bpf_loader/src/syscalls/mod.rs +++ b/programs/bpf_loader/src/syscalls/mod.rs @@ -50,7 +50,7 @@ use { solana_sysvar::Sysvar, solana_sysvar_id::SysvarId, solana_timings::ExecuteTimings, - solana_transaction_context::{IndexOfAccount, InstructionAccount}, + solana_transaction_context::IndexOfAccount, solana_type_overrides::sync::Arc, std::{ alloc::Layout, @@ -2201,6 +2201,7 @@ mod tests { solana_slot_hashes::{self as slot_hashes, SlotHashes}, solana_stable_layout::stable_instruction::StableInstruction, solana_sysvar::stake_history::{self, StakeHistory, StakeHistoryEntry}, + solana_transaction_context::{InstructionAccountView, InstructionAccountViewVector}, std::{ hash::{DefaultHasher, Hash, Hasher}, mem, @@ -2236,7 +2237,7 @@ mod tests { .transaction_context .get_next_instruction_context() .unwrap() - .configure(&[0, 1], &[], &[]); + .configure(&[0, 1], InstructionAccountViewVector::new(), &[]); $invoke_context.push().unwrap(); }; } @@ -4441,18 +4442,19 @@ mod tests { .transaction_context .get_instruction_context_stack_height() { - let instruction_accounts = [InstructionAccount::new( - index_in_trace.saturating_add(1) as IndexOfAccount, - 0, // This is incorrect / inconsistent but not required - 0, - false, - false, - )]; + let instruction_accounts = + InstructionAccountViewVector::from_vector(vec![InstructionAccountView { + index_in_transaction: index_in_trace.saturating_add(1) as IndexOfAccount, + index_in_caller: 0, // This is incorrect / inconsistent but not required + index_in_callee: 0, + is_signer: false, + is_writable: false, + }]); invoke_context .transaction_context .get_next_instruction_context() .unwrap() - .configure(&[0], &instruction_accounts, &[index_in_trace as u8]); + .configure(&[0], instruction_accounts, &[index_in_trace as u8]); invoke_context.transaction_context.push().unwrap(); } } diff --git a/programs/sbf/benches/bpf_loader.rs b/programs/sbf/benches/bpf_loader.rs index 8a11eabe911b8c..e9663e2fec5939 100644 --- a/programs/sbf/benches/bpf_loader.rs +++ b/programs/sbf/benches/bpf_loader.rs @@ -38,7 +38,7 @@ use { solana_sdk_ids::{bpf_loader, native_loader}, solana_signer::Signer, solana_svm_feature_set::SVMFeatureSet, - solana_transaction_context::InstructionAccount, + solana_transaction_context::{InstructionAccountView, InstructionAccountViewVector}, std::{mem, sync::Arc}, test::Bencher, }; @@ -60,7 +60,14 @@ macro_rules! with_mock_invoke_context { AccountSharedData::new(2, $account_size, &program_key), ), ]; - let instruction_accounts = vec![InstructionAccount::new(2, 2, 0, false, true)]; + let instruction_accounts = + InstructionAccountViewVector::from_vector(vec![InstructionAccountView { + index_in_transaction: 2, + index_in_caller: 2, + index_in_callee: 0, + is_signer: false, + is_writable: true, + }]); solana_program_runtime::with_mock_invoke_context!( $invoke_context, transaction_context, @@ -70,7 +77,7 @@ macro_rules! with_mock_invoke_context { .transaction_context .get_next_instruction_context() .unwrap() - .configure(&[0, 1], &instruction_accounts, &[]); + .configure(&[0, 1], instruction_accounts, &[]); $invoke_context.push().unwrap(); }; } diff --git a/programs/system/Cargo.toml b/programs/system/Cargo.toml index efdbb17443c831..1613e964653303 100644 --- a/programs/system/Cargo.toml +++ b/programs/system/Cargo.toml @@ -48,6 +48,7 @@ solana-rent = { workspace = true } solana-sha256-hasher = { workspace = true } solana-svm-callback = { workspace = true } solana-svm-feature-set = { workspace = true } +solana-transaction-context = { workspace = true, features = ["bincode", "dev-context-only-utils"] } [[bench]] name = "system" diff --git a/programs/system/src/system_instruction.rs b/programs/system/src/system_instruction.rs index 3f9753e6fa7063..22666783a90254 100644 --- a/programs/system/src/system_instruction.rs +++ b/programs/system/src/system_instruction.rs @@ -257,7 +257,7 @@ mod test { solana_program_runtime::with_mock_invoke_context, solana_sdk_ids::system_program, solana_sha256_hasher::hash, - solana_transaction_context::InstructionAccount, + solana_transaction_context::{InstructionAccountView, InstructionAccountViewVector}, }; pub const NONCE_ACCOUNT_INDEX: IndexOfAccount = 0; @@ -269,7 +269,7 @@ mod test { .transaction_context .get_next_instruction_context() .unwrap() - .configure(&[2], &$instruction_accounts, &[]); + .configure(&[2], $instruction_accounts, &[]); $invoke_context.push().unwrap(); let $transaction_context = &$invoke_context.transaction_context; let $instruction_context = $transaction_context @@ -293,10 +293,22 @@ mod test { (Pubkey::new_unique(), create_account(42).into_inner()), (system_program::id(), AccountSharedData::default()), ]; - let $instruction_accounts = vec![ - InstructionAccount::new(0, 0, 0, true, true), - InstructionAccount::new(1, 1, 1, false, true), - ]; + let $instruction_accounts = InstructionAccountViewVector::from_vector(vec![ + InstructionAccountView { + index_in_transaction: 0, + index_in_caller: 0, + index_in_callee: 0, + is_signer: true, + is_writable: true, + }, + InstructionAccountView { + index_in_transaction: 1, + index_in_caller: 1, + index_in_callee: 1, + is_signer: false, + is_writable: true, + }, + ]); with_mock_invoke_context!($invoke_context, transaction_context, transaction_accounts); }; } diff --git a/programs/vote/Cargo.toml b/programs/vote/Cargo.toml index cbacb867fc1087..e36a1a673a73a9 100644 --- a/programs/vote/Cargo.toml +++ b/programs/vote/Cargo.toml @@ -73,6 +73,7 @@ solana-pubkey = { workspace = true, features = ["rand"] } solana-rent = { workspace = true } solana-sdk-ids = { workspace = true } solana-sha256-hasher = { workspace = true } +solana-transaction-context = { workspace = true, features = ['bincode', 'dev-context-only-utils'] } test-case = { workspace = true } [[bench]] diff --git a/programs/vote/src/vote_state/mod.rs b/programs/vote/src/vote_state/mod.rs index def8642a1a1934..9a29067c6ca0a2 100644 --- a/programs/vote/src/vote_state/mod.rs +++ b/programs/vote/src/vote_state/mod.rs @@ -1072,7 +1072,7 @@ mod tests { solana_account::{state_traits::StateMut, AccountSharedData}, solana_clock::DEFAULT_SLOTS_PER_EPOCH, solana_sha256_hasher::hash, - solana_transaction_context::InstructionAccount, + solana_transaction_context::{InstructionAccountView, InstructionAccountViewVector}, std::cell::RefCell, test_case::test_case, }; @@ -1167,7 +1167,15 @@ mod tests { 0, ); let mut instruction_context = InstructionContext::default(); - instruction_context.configure(&[0], &[InstructionAccount::new(1, 1, 0, false, true)], &[]); + let instruction_accounts = + InstructionAccountViewVector::from_vector(vec![InstructionAccountView { + index_in_transaction: 1, + index_in_caller: 1, + index_in_callee: 0, + is_signer: false, + is_writable: true, + }]); + instruction_context.configure(&[0], instruction_accounts, &[]); // Get the BorrowedAccount from the InstructionContext which is what is used to manipulate and inspect account // state @@ -1312,7 +1320,15 @@ mod tests { 0, ); let mut instruction_context = InstructionContext::default(); - instruction_context.configure(&[0], &[InstructionAccount::new(1, 1, 0, false, true)], &[]); + let instruction_accounts = + InstructionAccountViewVector::from_vector(vec![InstructionAccountView { + index_in_transaction: 1, + index_in_caller: 1, + index_in_callee: 0, + is_signer: false, + is_writable: true, + }]); + instruction_context.configure(&[0], instruction_accounts, &[]); // Get the BorrowedAccount from the InstructionContext which is what is used to manipulate and inspect account // state diff --git a/svm/src/message_processor.rs b/svm/src/message_processor.rs index 3e8de728a4d74f..93eaf3306f6762 100644 --- a/svm/src/message_processor.rs +++ b/svm/src/message_processor.rs @@ -3,7 +3,9 @@ use { solana_program_runtime::invoke_context::InvokeContext, solana_svm_transaction::svm_message::SVMMessage, solana_timings::{ExecuteDetailsTimings, ExecuteTimings}, - solana_transaction_context::{IndexOfAccount, InstructionAccount}, + solana_transaction_context::{ + IndexOfAccount, InstructionAccountView, InstructionAccountViewVector, + }, solana_transaction_error::TransactionError, }; @@ -25,7 +27,8 @@ pub(crate) fn process_message( .zip(program_indices.iter()) .enumerate() { - let mut instruction_accounts = Vec::with_capacity(instruction.accounts.len()); + let mut instruction_accounts = + InstructionAccountViewVector::with_capacity(instruction.accounts.len()); for (instruction_account_index, index_in_transaction) in instruction.accounts.iter().enumerate() { @@ -38,13 +41,13 @@ pub(crate) fn process_message( .unwrap_or(instruction_account_index) as IndexOfAccount; let index_in_transaction = *index_in_transaction as usize; - instruction_accounts.push(InstructionAccount::new( - index_in_transaction as IndexOfAccount, - index_in_transaction as IndexOfAccount, + instruction_accounts.push(InstructionAccountView { + index_in_transaction: index_in_transaction as IndexOfAccount, + index_in_caller: index_in_transaction as IndexOfAccount, index_in_callee, - message.is_signer(index_in_transaction), - message.is_writable(index_in_transaction), - )); + is_signer: message.is_signer(index_in_transaction), + is_writable: message.is_writable(index_in_transaction), + }); } let mut compute_units_consumed = 0; @@ -53,14 +56,14 @@ pub(crate) fn process_message( invoke_context.process_precompile( program_id, instruction.data, - &instruction_accounts, + instruction_accounts, program_indices, message.instructions_iter().map(|ix| ix.data), ) } else { invoke_context.process_instruction( instruction.data, - &instruction_accounts, + instruction_accounts, program_indices, &mut compute_units_consumed, execute_timings, diff --git a/svm/src/transaction_processor.rs b/svm/src/transaction_processor.rs index d9266d3d5f1cf0..2a8ef8490c9ab4 100644 --- a/svm/src/transaction_processor.rs +++ b/svm/src/transaction_processor.rs @@ -1119,7 +1119,7 @@ mod tests { solana_signature::Signature, solana_svm_callback::{AccountState, InvokeContextCallback}, solana_transaction::{sanitized::SanitizedTransaction, Transaction}, - solana_transaction_context::TransactionContext, + solana_transaction_context::{InstructionAccountViewVector, TransactionContext}, solana_transaction_error::{TransactionError, TransactionError::DuplicateInstruction}, test_case::test_case, }; @@ -1294,7 +1294,11 @@ mod tests { transaction_context .get_next_instruction_context() .unwrap() - .configure(&[], &[], &[index_in_trace as u8]); + .configure( + &[], + InstructionAccountViewVector::new(), + &[index_in_trace as u8], + ); transaction_context.push().unwrap(); } } diff --git a/svm/tests/conformance.rs b/svm/tests/conformance.rs index cf0ae545769569..0c3951cdb1c97f 100644 --- a/svm/tests/conformance.rs +++ b/svm/tests/conformance.rs @@ -28,7 +28,8 @@ use { solana_sysvar_id::SysvarId, solana_timings::ExecuteTimings, solana_transaction_context::{ - ExecutionRecord, IndexOfAccount, InstructionAccount, TransactionAccount, TransactionContext, + ExecutionRecord, IndexOfAccount, InstructionAccountView, InstructionAccountViewVector, + TransactionAccount, TransactionContext, }, std::{ collections::{hash_map::Entry, HashMap}, @@ -367,8 +368,9 @@ fn execute_fixture_as_instr( SVMTransactionExecutionCost::default(), ); - let mut instruction_accounts: Vec = - Vec::with_capacity(sanitized_message.instructions()[0].accounts.len()); + let mut instruction_accounts = InstructionAccountViewVector::with_capacity( + sanitized_message.instructions()[0].accounts.len(), + ); for (instruction_acct_idx, index_txn) in sanitized_message.instructions()[0] .accounts @@ -383,20 +385,20 @@ fn execute_fixture_as_instr( .position(|idx| *idx == *index_txn) .unwrap_or(instruction_acct_idx); - instruction_accounts.push(InstructionAccount::new( - *index_txn as IndexOfAccount, - *index_txn as IndexOfAccount, - index_in_callee as IndexOfAccount, - sanitized_message.is_signer(*index_txn as usize), - sanitized_message.is_writable(*index_txn as usize), - )); + instruction_accounts.push(InstructionAccountView { + index_in_transaction: *index_txn as IndexOfAccount, + index_in_caller: *index_txn as IndexOfAccount, + index_in_callee: index_in_callee as IndexOfAccount, + is_signer: sanitized_message.is_signer(*index_txn as usize), + is_writable: sanitized_message.is_writable(*index_txn as usize), + }); } let mut compute_units_consumed = 0u64; let mut timings = ExecuteTimings::default(); let result = invoke_context.process_instruction( &sanitized_message.instructions()[0].data, - &instruction_accounts, + instruction_accounts, &[program_idx as IndexOfAccount], &mut compute_units_consumed, &mut timings, diff --git a/transaction-context/src/lib.rs b/transaction-context/src/lib.rs index ce475f555ffb19..d0ef67b1d10926 100644 --- a/transaction-context/src/lib.rs +++ b/transaction-context/src/lib.rs @@ -13,8 +13,11 @@ use { std::{ cell::{Ref, RefCell, RefMut}, collections::HashSet, + iter::{Map, Zip}, + ops::Range, pin::Pin, rc::Rc, + slice::Iter, }, }; @@ -49,17 +52,96 @@ static_assertions::const_assert_eq!( solana_account_info::MAX_PERMITTED_DATA_INCREASE ); -/// Index of an account inside of the TransactionContext or an InstructionContext. +/// `InstructionViewAccountVector` manages the creation of vectors for `InstructionAccount` and +/// `InstructionAccountCallIndexes`, by accepting `InstructionAccountView`, and exposing an iterator of it. +#[derive(Default, Clone)] +pub struct InstructionAccountViewVector { + metadata: Vec, + indexes: Vec, +} + +type InstructionViewIterator<'a> = Map< + Zip, Iter<'a, InstructionAccountCallIndexes>>, + fn((&InstructionAccount, &InstructionAccountCallIndexes)) -> InstructionAccountView, +>; + +impl InstructionAccountViewVector { + pub fn new() -> InstructionAccountViewVector { + InstructionAccountViewVector::default() + } + + pub fn with_capacity(capacity: usize) -> InstructionAccountViewVector { + InstructionAccountViewVector { + metadata: Vec::with_capacity(capacity), + indexes: Vec::with_capacity(capacity), + } + } + + pub fn push(&mut self, element: InstructionAccountView) { + self.metadata.push(InstructionAccount::new( + element.index_in_transaction, + element.is_signer, + element.is_writable, + )); + + self.indexes.push(InstructionAccountCallIndexes { + index_in_caller: element.index_in_caller, + index_in_callee: element.index_in_callee, + }); + } + + #[cfg(feature = "dev-context-only-utils")] + pub fn from_vector(vector: Vec) -> InstructionAccountViewVector { + let mut view_owner = Self::with_capacity(vector.len()); + for item in vector { + view_owner.push(item); + } + + view_owner + } + + pub fn iter(&self) -> InstructionViewIterator { + self.metadata + .iter() + .zip(self.indexes.iter()) + .map(|(acc, idx)| InstructionAccountView { + index_in_callee: idx.index_in_callee, + index_in_caller: idx.index_in_caller, + index_in_transaction: acc.index_in_transaction, + is_signer: acc.is_signer(), + is_writable: acc.is_writable(), + }) + } + + /// Retrieve a range of the `InstructionAccount` vector as immutable. + pub fn get_metadata_for_test(&self, range: Range) -> Option<&[InstructionAccount]> { + self.metadata.get(range) + } + + pub fn len(&self) -> usize { + debug_assert_eq!(self.metadata.len(), self.indexes.len()); + self.metadata.len() + } + + pub fn is_empty(&self) -> bool { + debug_assert_eq!(self.metadata.len(), self.indexes.len()); + self.metadata.is_empty() + } +} + +/// Index of an account inside the TransactionContext or an InstructionContext. pub type IndexOfAccount = u16; -/// Contains account meta data which varies between instruction. -/// -/// It also contains indices to other structures for faster lookup. -#[repr(C)] -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct InstructionAccount { +/// `InstructionAccountView` is a view struct that merges `InstructionAccount` and +/// `InstructionAccountCallIndexes` for easier handling outside of runtime. +#[derive(Clone)] +pub struct InstructionAccountView { /// Points to the account and its key in the `TransactionContext` pub index_in_transaction: IndexOfAccount, + /// Is this account supposed to sign + pub is_signer: bool, + /// Is this account allowed to become writable + pub is_writable: bool, /// Points to the first occurrence in the parent `InstructionContext` /// /// This excludes the program accounts. @@ -68,6 +150,32 @@ pub struct InstructionAccount { /// /// This excludes the program accounts. pub index_in_callee: IndexOfAccount, +} + +/// `InstructionAccountCallIndexes` saves indexes of the account relative to the caller and callee. +/// These values are only used by the runtime and are not accessible to programs. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct InstructionAccountCallIndexes { + /// Points to the first occurrence in the parent `InstructionContext` + /// + /// This excludes the program accounts. + pub index_in_caller: IndexOfAccount, + /// Points to the first occurrence in the current `InstructionContext` + /// + /// This excludes the program accounts. + pub index_in_callee: IndexOfAccount, +} + +/// Contains account metadata which varies between instruction. +/// +/// It also contains indices to other structures for faster lookup. +/// This struct is shared between programs and runtime, so it cannot be modified without a SIMD, +/// and a feature gate. +#[repr(C)] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct InstructionAccount { + /// Points to the account and its key in the `TransactionContext` + pub index_in_transaction: IndexOfAccount, /// Is this account supposed to sign is_signer: u8, /// Is this account allowed to become writable @@ -77,15 +185,11 @@ pub struct InstructionAccount { impl InstructionAccount { pub fn new( index_in_transaction: IndexOfAccount, - index_in_caller: IndexOfAccount, - index_in_callee: IndexOfAccount, is_signer: bool, is_writable: bool, ) -> InstructionAccount { InstructionAccount { index_in_transaction, - index_in_caller, - index_in_callee, is_signer: is_signer as u8, is_writable: is_writable as u8, } @@ -98,14 +202,6 @@ impl InstructionAccount { pub fn is_writable(&self) -> bool { self.is_writable != 0 } - - pub fn set_is_signer(&mut self, value: bool) { - self.is_signer = value as u8; - } - - pub fn set_is_writable(&mut self, value: bool) { - self.is_writable = value as u8; - } } /// An account key and the matching account @@ -619,6 +715,7 @@ pub struct InstructionContext { instruction_accounts_lamport_sum: u128, program_accounts: Vec, instruction_accounts: Vec, + instruction_account_call_indexes: Vec, instruction_data: Vec, } @@ -628,11 +725,16 @@ impl InstructionContext { pub fn configure( &mut self, program_accounts: &[IndexOfAccount], - instruction_accounts: &[InstructionAccount], + instruction_accounts: InstructionAccountViewVector, instruction_data: &[u8], ) { + debug_assert_eq!( + instruction_accounts.metadata.len(), + instruction_accounts.indexes.len() + ); self.program_accounts = program_accounts.to_vec(); - self.instruction_accounts = instruction_accounts.to_vec(); + self.instruction_accounts = instruction_accounts.metadata; + self.instruction_account_call_indexes = instruction_accounts.indexes; self.instruction_data = instruction_data.to_vec(); } @@ -734,7 +836,7 @@ impl InstructionContext { instruction_account_index: IndexOfAccount, ) -> Result, InstructionError> { let index_in_callee = self - .instruction_accounts + .instruction_account_call_indexes .get(instruction_account_index as usize) .ok_or(InstructionError::NotEnoughAccountKeys)? .index_in_callee;