1#[cfg(feature = "dev-context-only-utils")]
2use {
3 crate::program_cache_entry::ProgramCacheEntry,
4 qualifier_attr::qualifiers,
5 solana_account::{AccountSharedData, WritableAccount, state_traits::StateMutWincode as _},
6 solana_epoch_schedule::EpochSchedule,
7 solana_instruction::AccountMeta,
8 solana_message::{LegacyMessage, Message, SanitizedMessage},
9 solana_sdk_ids::sysvar,
10 solana_transaction_context::transaction_accounts::KeyedAccountSharedData,
11 std::collections::{HashMap, HashSet},
12};
13use {
14 crate::{
15 execution_budget::{SVMTransactionExecutionBudget, SVMTransactionExecutionCost},
16 loaded_programs::{
17 ProgramCacheForTxBatch, ProgramRuntimeEnvironment, ProgramRuntimeEnvironments,
18 },
19 memory_context::{MemoryContext, MemoryContexts},
20 program_cache_entry::ProgramCacheEntryType,
21 stable_log,
22 sysvar_cache::SysvarCache,
23 },
24 solana_hash::Hash,
25 solana_instruction::Instruction,
26 solana_instruction_error::InstructionError,
27 solana_pubkey::Pubkey,
28 solana_sbpf::{
29 ebpf::MM_HEAP_START,
30 elf::{ElfError, Executable as GenericExecutable},
31 error::{EbpfError, ProgramResult},
32 memory_region::MemoryMapping,
33 program::{BuiltinProgram, SBPFVersion},
34 vm::{Config, ContextObject, EbpfVm},
35 },
36 solana_sdk_ids::{
37 bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, loader_v4, native_loader,
38 },
39 solana_svm_callback::InvokeContextCallback,
40 solana_svm_feature_set::SVMFeatureSet,
41 solana_svm_log_collector::{LogCollector, ic_msg},
42 solana_svm_measure::{measure::Measure, measure_us},
43 solana_svm_timings::{ExecuteDetailsTimings, ExecuteTimings},
44 solana_svm_transaction::svm_message::SVMMessage,
45 solana_svm_type_overrides::sync::Arc,
46 solana_transaction_context::{
47 IndexOfAccount, MAX_ACCOUNTS_PER_TRANSACTION, instruction::InstructionContext,
48 instruction_accounts::InstructionAccount, transaction::TransactionContext,
49 },
50 std::{
51 alloc::Layout,
52 borrow::Cow,
53 cell::RefCell,
54 fmt::{self, Debug},
55 ptr,
56 rc::Rc,
57 time::Duration,
58 },
59};
60
61pub type BuiltinFunctionRegisterer =
62 fn(&mut BuiltinProgram<InvokeContext<'static, 'static>>, &str) -> Result<(), ElfError>;
63pub type Executable = GenericExecutable<InvokeContext<'static, 'static>>;
64pub type RegisterTrace<'a> = &'a [[u64; 12]];
65
66#[macro_export]
68macro_rules! declare_process_instruction {
69 ($process_instruction:ident, $cu_to_consume:expr, |$invoke_context:ident| $inner:tt) => {
70 $crate::solana_sbpf::declare_builtin_function!(
71 $process_instruction,
72 fn rust(
73 invoke_context: &mut $crate::invoke_context::InvokeContext<'_, '_>,
74 _arg0: u64,
75 _arg1: u64,
76 _arg2: u64,
77 _arg3: u64,
78 _arg4: u64,
79 ) -> Result<u64, Box<dyn std::error::Error>> {
80 fn process_instruction_inner(
81 $invoke_context: &mut $crate::invoke_context::InvokeContext,
82 ) -> std::result::Result<(), $crate::__private::InstructionError>
83 $inner
84
85 let consumption_result = if $cu_to_consume > 0
86 {
87 invoke_context.consume_checked($cu_to_consume)
88 } else {
89 Ok(())
90 };
91 consumption_result
92 .and_then(|_| {
93 process_instruction_inner(invoke_context)
94 .map(|_| 0)
95 .map_err(|err| Box::new(err) as Box<dyn std::error::Error>)
96 })
97 .into()
98 }
99 );
100 };
101}
102
103impl ContextObject for InvokeContext<'_, '_> {
104 fn consume(&mut self, amount: u64) {
105 self.transaction_context
106 .compute_meter
107 .consume_unchecked(amount);
108 }
109
110 fn get_remaining(&self) -> u64 {
111 self.transaction_context.compute_meter.get_remaining()
112 }
113
114 fn active_mapping_ptr(&mut self) -> ptr::NonNull<MemoryMapping> {
115 let memory = self
116 .memory_contexts
117 .memory_mapping_mut()
118 .expect("The memory context must have been set for the current instruction");
119 ptr::NonNull::from_mut(memory)
120 }
121}
122
123#[derive(Clone, PartialEq, Eq, Debug)]
124pub struct AllocErr;
125impl fmt::Display for AllocErr {
126 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
127 f.write_str("Error: Memory allocation failed")
128 }
129}
130
131pub struct BpfAllocator {
132 len: u64,
133 pos: u64,
134}
135
136impl BpfAllocator {
137 pub fn new(len: u64) -> Self {
138 Self { len, pos: 0 }
139 }
140
141 pub fn alloc(&mut self, layout: Layout) -> Result<u64, AllocErr> {
142 let bytes_to_align = (self.pos as *const u8).align_offset(layout.align()) as u64;
143 if self
144 .pos
145 .saturating_add(bytes_to_align)
146 .saturating_add(layout.size() as u64)
147 <= self.len
148 {
149 self.pos = self.pos.saturating_add(bytes_to_align);
150 let addr = MM_HEAP_START.saturating_add(self.pos);
151 self.pos = self.pos.saturating_add(layout.size() as u64);
152 Ok(addr)
153 } else {
154 Err(AllocErr)
155 }
156 }
157}
158
159pub struct EnvironmentConfig<'a> {
160 pub blockhash: Hash,
161 pub blockhash_lamports_per_signature: u64,
162 alpenglow_migration_succeeded: bool,
163 epoch_stake_callback: &'a dyn InvokeContextCallback,
164 feature_set: &'a SVMFeatureSet,
165 program_runtime_environments: &'a ProgramRuntimeEnvironments,
166 sysvar_cache: &'a SysvarCache,
167}
168impl<'a> EnvironmentConfig<'a> {
169 pub fn new(
170 blockhash: Hash,
171 blockhash_lamports_per_signature: u64,
172 alpenglow_migration_succeeded: bool,
173 epoch_stake_callback: &'a dyn InvokeContextCallback,
174 feature_set: &'a SVMFeatureSet,
175 program_runtime_environments: &'a ProgramRuntimeEnvironments,
176 sysvar_cache: &'a SysvarCache,
177 ) -> Self {
178 Self {
179 blockhash,
180 blockhash_lamports_per_signature,
181 alpenglow_migration_succeeded,
182 epoch_stake_callback,
183 feature_set,
184 program_runtime_environments,
185 sysvar_cache,
186 }
187 }
188
189 pub fn sysvar_cache(&self) -> &SysvarCache {
191 self.sysvar_cache
192 }
193}
194
195pub struct InvokeContext<'a, 'ix_data> {
197 pub transaction_context: &'a mut TransactionContext<'ix_data>,
199 pub program_cache_for_tx_batch: &'a mut ProgramCacheForTxBatch,
201 pub environment_config: EnvironmentConfig<'a>,
203 compute_budget: SVMTransactionExecutionBudget,
205 execution_cost: SVMTransactionExecutionCost,
207 log_collector: Option<Rc<RefCell<LogCollector>>>,
208 pub total_nested_exec_time: Duration,
210 pub timings: ExecuteDetailsTimings,
211 pub memory_contexts: MemoryContexts,
212 register_traces: Vec<(usize, Vec<[u64; 12]>)>,
214 #[cfg(feature = "sbpf-debugger")]
216 pub debug_port: Option<u16>,
217}
218
219impl<'a, 'ix_data> InvokeContext<'a, 'ix_data> {
220 pub fn new(
221 transaction_context: &'a mut TransactionContext<'ix_data>,
222 program_cache_for_tx_batch: &'a mut ProgramCacheForTxBatch,
223 environment_config: EnvironmentConfig<'a>,
224 log_collector: Option<Rc<RefCell<LogCollector>>>,
225 compute_budget: SVMTransactionExecutionBudget,
226 execution_cost: SVMTransactionExecutionCost,
227 ) -> Self {
228 Self {
229 transaction_context,
230 program_cache_for_tx_batch,
231 environment_config,
232 log_collector,
233 compute_budget,
234 execution_cost,
235 total_nested_exec_time: Duration::ZERO,
236 timings: ExecuteDetailsTimings::default(),
237 memory_contexts: MemoryContexts::new(),
238 register_traces: Vec::new(),
239 #[cfg(feature = "sbpf-debugger")]
240 debug_port: None,
241 }
242 }
243
244 #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
246 fn push(&mut self) -> Result<(), InstructionError> {
247 let instruction_context = self.transaction_context.get_next_instruction_context()?;
248 let program_id = instruction_context
249 .get_program_key()
250 .map_err(|_| InstructionError::UnsupportedProgramId)?;
251 if self.transaction_context.get_instruction_stack_height() != 0 {
252 let contains =
253 (0..self.transaction_context.get_instruction_stack_height()).any(|level| {
254 self.transaction_context
255 .get_instruction_context_at_nesting_level(level)
256 .and_then(|instruction_context| instruction_context.get_program_key())
257 .map(|program_key| program_key == program_id)
258 .unwrap_or(false)
259 });
260 let is_last = self
261 .transaction_context
262 .get_current_instruction_context()
263 .and_then(|instruction_context| instruction_context.get_program_key())
264 .map(|program_key| program_key == program_id)
265 .unwrap_or(false);
266 if contains && !is_last {
267 return Err(InstructionError::ReentrancyNotAllowed);
269 }
270 }
271
272 let detect_overflow_early = self
273 .environment_config
274 .feature_set
275 .early_instruction_trace_overflow_detection;
276 self.transaction_context.push(detect_overflow_early)?;
277 self.memory_contexts.push_placeholder();
278 Ok(())
279 }
280
281 #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
283 fn pop(&mut self) -> Result<(), InstructionError> {
284 self.memory_contexts.pop();
285 self.transaction_context.pop()
286 }
287
288 pub fn get_stack_height(&self) -> usize {
291 self.transaction_context.get_instruction_stack_height()
292 }
293
294 pub fn native_invoke_signed(
301 &mut self,
302 instruction: Instruction,
303 signer_seeds: &[&[&[u8]]],
304 ) -> Result<(), InstructionError> {
305 let caller_program_id = *self
306 .transaction_context
307 .get_current_instruction_context()?
308 .get_program_key()?;
309 let signers = signer_seeds
312 .iter()
313 .map(|seeds| Pubkey::create_program_address(seeds, &caller_program_id))
314 .collect::<Result<Vec<Pubkey>, solana_pubkey::PubkeyError>>()
315 .map_err(|e| e as u64)?;
316 self.build_instruction_frame(instruction)?;
317 self.internal_native_invoke(&signers)?;
318 Ok(())
319 }
320
321 pub fn internal_native_invoke(&mut self, signers: &[Pubkey]) -> Result<(), InstructionError> {
324 self.verify_instruction_accounts(signers)?;
325 let mut compute_units_consumed = 0;
326 self.process_instruction(&mut compute_units_consumed, &mut ExecuteTimings::default())?;
327 Ok(())
328 }
329
330 pub(crate) fn verify_instruction_accounts(
333 &mut self,
334 signers: &[Pubkey],
335 ) -> Result<(), InstructionError> {
336 let instruction_context = self.transaction_context.get_current_instruction_context()?;
337 let next_context = self.transaction_context.get_next_instruction_context()?;
338 let callee_instruction_accounts = next_context.instruction_accounts();
339 for (idx, callee_account) in callee_instruction_accounts.iter().enumerate() {
340 if next_context
341 .is_instruction_account_duplicate(idx as u16)?
342 .is_some()
343 {
344 continue;
345 }
346
347 let index_in_caller = instruction_context
349 .get_index_of_account_in_instruction(callee_account.index_in_transaction)?;
350
351 let caller_instruction_account = instruction_context
352 .instruction_accounts()
353 .get(index_in_caller as usize)
354 .expect(
355 "get_index_of_account_in_instruction above has already checked if the index \
356 is valid.",
357 );
358
359 let account_key = self
360 .transaction_context
361 .get_key_of_account_at_index(caller_instruction_account.index_in_transaction)?;
362
363 if callee_account.is_writable() && !caller_instruction_account.is_writable() {
365 ic_msg!(self, "{}'s writable privilege escalated", account_key,);
366 return Err(InstructionError::PrivilegeEscalation);
367 }
368
369 if callee_account.is_signer()
372 && !(caller_instruction_account.is_signer() || signers.contains(account_key))
373 {
374 ic_msg!(self, "{}'s signer privilege escalated", account_key,);
375 return Err(InstructionError::PrivilegeEscalation);
376 }
377 }
378
379 let program_id_tx_idx = next_context.get_index_of_program_account_in_transaction()?;
382 if instruction_context
383 .get_index_of_account_in_instruction(program_id_tx_idx)
384 .is_err()
385 {
386 let callee_program_id = self
387 .transaction_context
388 .get_key_of_account_at_index(program_id_tx_idx)
389 .expect("We should have checked that the program ID is in the transaction");
390 ic_msg!(self, "Unknown program {}", callee_program_id);
391 return Err(InstructionError::MissingAccount);
392 }
393
394 Ok(())
395 }
396
397 pub(crate) fn build_instruction_frame(
399 &mut self,
400 instruction: Instruction,
401 ) -> Result<(), InstructionError> {
402 let transaction_callee_map_len = (self.transaction_context.get_number_of_accounts()
404 as usize)
405 .min(MAX_ACCOUNTS_PER_TRANSACTION);
406 let mut transaction_callee_map: Vec<u8> = vec![u8::MAX; transaction_callee_map_len];
407 let mut instruction_accounts: Vec<InstructionAccount> =
408 Vec::with_capacity(instruction.accounts.len());
409
410 let program_account_index = {
414 for account_meta in instruction.accounts.iter() {
415 let index_in_transaction = self
416 .transaction_context
417 .find_index_of_account(&account_meta.pubkey)
418 .ok_or_else(|| {
419 ic_msg!(
420 self,
421 "Instruction references an unknown account {}",
422 account_meta.pubkey,
423 );
424 InstructionError::MissingAccount
425 })?;
426
427 debug_assert!((index_in_transaction as usize) < transaction_callee_map.len());
428 let index_in_callee = transaction_callee_map
429 .get_mut(index_in_transaction as usize)
430 .unwrap();
431
432 if (*index_in_callee as usize) < instruction_accounts.len() {
433 let cloned_account = {
434 let instruction_account = instruction_accounts
435 .get_mut(*index_in_callee as usize)
436 .ok_or(InstructionError::MissingAccount)?;
437 instruction_account.set_is_signer(
438 instruction_account.is_signer() || account_meta.is_signer,
439 );
440 instruction_account.set_is_writable(
441 instruction_account.is_writable() || account_meta.is_writable,
442 );
443 *instruction_account
444 };
445 instruction_accounts.push(cloned_account);
446 } else {
447 *index_in_callee = instruction_accounts.len() as u8;
448 instruction_accounts.push(InstructionAccount::new(
449 index_in_transaction,
450 account_meta.is_signer,
451 account_meta.is_writable,
452 ));
453 }
454 }
455
456 TransactionContext::replicate_account_flags(
457 &mut instruction_accounts,
458 &transaction_callee_map,
459 );
460
461 let callee_program_id = &instruction.program_id;
463 let program_account_index_in_transaction = self
464 .transaction_context
465 .find_index_of_account(callee_program_id);
466
467 if program_account_index_in_transaction.is_none() {
470 ic_msg!(self, "Unknown program {}", callee_program_id);
471 return Err(InstructionError::MissingAccount);
472 }
473
474 program_account_index_in_transaction.unwrap()
477 };
478
479 let caller_index = self.transaction_context.get_current_instruction_index()?;
482 self.transaction_context.configure_instruction_at_index(
483 self.transaction_context.get_instruction_trace_length(),
484 program_account_index,
485 instruction_accounts,
486 transaction_callee_map,
487 Cow::Owned(instruction.data),
488 Some(caller_index as u16),
489 )?;
490 Ok(())
491 }
492
493 pub fn process_message(
499 &mut self,
500 message: &'ix_data impl SVMMessage,
501 execute_timings: &mut ExecuteTimings,
502 accumulated_consumed_units: &mut u64,
503 ) -> Result<(), (u8, InstructionError)> {
504 self.prepare_top_level_instructions(message)?;
505
506 for (top_level_instruction_index, (program_id, instruction)) in
507 message.program_instructions_iter().enumerate()
508 {
509 let mut compute_units_consumed = 0;
510 let (result, process_instruction_us) = measure_us!({
511 if self.is_precompile(program_id) {
512 self.process_precompile(
513 program_id,
514 instruction.data,
515 message.instructions_iter().map(|ix| ix.data),
516 )
517 } else {
518 self.process_instruction(&mut compute_units_consumed, execute_timings)
519 }
520 });
521
522 *accumulated_consumed_units =
523 accumulated_consumed_units.saturating_add(compute_units_consumed);
524 if log::log_enabled!(log::Level::Trace) {
527 execute_timings.details.accumulate_program(
528 program_id,
529 process_instruction_us,
530 compute_units_consumed,
531 result.is_err(),
532 );
533 }
534 self.timings = {
535 execute_timings.details.accumulate(&self.timings);
536 ExecuteDetailsTimings::default()
537 };
538 execute_timings
539 .execute_accessories
540 .process_instructions
541 .total_us += process_instruction_us;
542
543 result.map_err(|err| (top_level_instruction_index as u8, err))?;
544 }
545
546 Ok(())
547 }
548
549 #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
551 fn prepare_top_level_instructions(
552 &mut self,
553 message: &'ix_data impl SVMMessage,
554 ) -> Result<(), (u8, InstructionError)> {
555 for (top_level_instruction_index, (_, instruction)) in
556 message.program_instructions_iter().enumerate()
557 {
558 let transaction_callee_map_len = message
559 .account_keys()
560 .len()
561 .min(MAX_ACCOUNTS_PER_TRANSACTION);
562 let mut transaction_callee_map: Vec<u8> = vec![u8::MAX; transaction_callee_map_len];
563
564 let mut instruction_accounts: Vec<InstructionAccount> =
565 Vec::with_capacity(instruction.accounts.len());
566 for index_in_transaction in instruction.accounts.iter() {
567 let index_in_callee = transaction_callee_map
568 .get_mut(*index_in_transaction as usize)
569 .expect("Invalid index in transaction");
570
571 if (*index_in_callee as usize) > instruction_accounts.len() {
572 *index_in_callee = instruction_accounts.len() as u8;
573 }
574
575 let index_in_transaction = *index_in_transaction as usize;
576 instruction_accounts.push(InstructionAccount::new(
577 index_in_transaction as IndexOfAccount,
578 message.is_signer(index_in_transaction),
579 message.is_writable(index_in_transaction),
580 ));
581 }
582
583 self.transaction_context
584 .configure_instruction_at_index(
585 top_level_instruction_index,
586 instruction.program_id_index as u16,
587 instruction_accounts,
588 transaction_callee_map,
589 Cow::Borrowed(instruction.data),
590 None,
591 )
592 .map_err(|err| (top_level_instruction_index as u8, err))?;
593 }
594 Ok(())
595 }
596
597 #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
599 pub(crate) fn process_instruction(
600 &mut self,
601 compute_units_consumed: &mut u64,
602 timings: &mut ExecuteTimings,
603 ) -> Result<(), InstructionError> {
604 *compute_units_consumed = 0;
605 self.push()?;
606 self.process_executable_chain(compute_units_consumed, timings)
607 .and(self.pop())
610 }
611
612 #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
614 fn process_precompile(
615 &mut self,
616 program_id: &Pubkey,
617 instruction_data: &[u8],
618 message_instruction_datas_iter: impl Iterator<Item = &'ix_data [u8]>,
619 ) -> Result<(), InstructionError> {
620 self.push()?;
621 let instruction_datas: Vec<_> = message_instruction_datas_iter.collect();
622 self.environment_config
623 .epoch_stake_callback
624 .process_precompile(program_id, instruction_data, instruction_datas)
625 .map_err(InstructionError::from)
626 .and(self.pop())
627 }
628
629 fn process_executable_chain(
631 &mut self,
632 compute_units_consumed: &mut u64,
633 timings: &mut ExecuteTimings,
634 ) -> Result<(), InstructionError> {
635 let instruction_context = self.transaction_context.get_current_instruction_context()?;
636 let process_executable_chain_time = Measure::start("process_executable_chain_time");
637
638 let builtin_id = {
639 let owner_id = instruction_context.get_program_owner()?;
640 if native_loader::check_id(&owner_id) {
641 *instruction_context.get_program_key()?
642 } else if bpf_loader_deprecated::check_id(&owner_id)
643 || bpf_loader::check_id(&owner_id)
644 || bpf_loader_upgradeable::check_id(&owner_id)
645 || loader_v4::check_id(&owner_id)
646 {
647 owner_id
648 } else {
649 return Err(InstructionError::UnsupportedProgramId);
650 }
651 };
652
653 const ENTRYPOINT_KEY: u32 = 0x71E3CF81;
655 let entry = self
656 .program_cache_for_tx_batch
657 .find(&builtin_id)
658 .ok_or(InstructionError::UnsupportedProgramId)?;
659 let function = match &entry.program {
660 ProgramCacheEntryType::Builtin(program) => program
661 .get_function_registry()
662 .lookup_by_key(ENTRYPOINT_KEY)
663 .map(|(_name, (function, _codegen))| function),
664 _ => None,
665 }
666 .ok_or(InstructionError::UnsupportedProgramId)?;
667
668 let program_id = *instruction_context.get_program_key()?;
669 self.transaction_context
670 .set_return_data(program_id, Vec::new())?;
671 let logger = self.get_log_collector();
672 stable_log::program_invoke(&logger, &program_id, self.get_stack_height());
673 let pre_remaining_units = self.get_remaining();
674 self.memory_contexts
676 .set_memory_context_abi_v1(MemoryContext::new(
677 BpfAllocator::new(0),
678 Vec::new(),
679 unsafe {
682 MemoryMapping::new(Vec::new(), &Config::default(), SBPFVersion::Reserved)
683 .unwrap()
684 },
685 ))?;
686 let mut vm = EbpfVm::new(
687 Arc::clone(
688 &**self
689 .environment_config
690 .program_runtime_environments
691 .get_env_for_execution(),
692 ),
693 SBPFVersion::V0,
694 unsafe { std::mem::transmute::<&mut InvokeContext, &mut InvokeContext>(self) },
696 0,
697 );
698 vm.invoke_function(function);
699 let result = match vm.program_result {
700 ProgramResult::Ok(_) => {
701 stable_log::program_success(&logger, &program_id);
702 Ok(())
703 }
704 ProgramResult::Err(ref err) => {
705 if let EbpfError::SyscallError(syscall_error) = err {
706 if let Some(instruction_err) = syscall_error.downcast_ref::<InstructionError>()
707 {
708 stable_log::program_failure(&logger, &program_id, instruction_err);
709 Err(instruction_err.clone())
710 } else {
711 stable_log::program_failure(&logger, &program_id, syscall_error);
712 Err(InstructionError::ProgramFailedToComplete)
713 }
714 } else {
715 stable_log::program_failure(&logger, &program_id, err);
716 Err(InstructionError::ProgramFailedToComplete)
717 }
718 }
719 };
720 let post_remaining_units = self.get_remaining();
721 *compute_units_consumed = pre_remaining_units.saturating_sub(post_remaining_units);
722
723 if builtin_id == program_id && result.is_ok() && *compute_units_consumed == 0 {
724 return Err(InstructionError::BuiltinProgramsMustConsumeComputeUnits);
725 }
726
727 timings
728 .execute_accessories
729 .process_instructions
730 .process_executable_chain_us += process_executable_chain_time.end_as_us();
731 result
732 }
733
734 pub fn consume_checked(&self, amount: u64) -> Result<(), Box<dyn std::error::Error>> {
735 self.transaction_context
736 .compute_meter
737 .consume_checked(amount)
738 }
739
740 #[cfg(feature = "dev-context-only-utils")]
741 pub fn mock_set_remaining(&self, remaining: u64) {
742 self.transaction_context
743 .compute_meter
744 .mock_set_remaining(remaining)
745 }
746
747 pub fn get_log_collector(&self) -> Option<Rc<RefCell<LogCollector>>> {
749 self.log_collector.clone()
750 }
751
752 #[cfg(feature = "dev-context-only-utils")]
753 pub fn set_alpenglow_migration_succeeded_for_tests(&mut self, succeeded: bool) {
754 self.environment_config.alpenglow_migration_succeeded = succeeded;
755 }
756
757 pub fn get_compute_budget(&self) -> &SVMTransactionExecutionBudget {
759 &self.compute_budget
760 }
761
762 pub fn get_execution_cost(&self) -> &SVMTransactionExecutionCost {
764 &self.execution_cost
765 }
766
767 pub fn get_feature_set(&self) -> &SVMFeatureSet {
769 self.environment_config.feature_set
770 }
771
772 pub fn get_program_runtime_environment_for_deployment(&self) -> &ProgramRuntimeEnvironment {
773 self.environment_config
774 .program_runtime_environments
775 .get_env_for_deployment()
776 }
777
778 pub fn is_deprecate_legacy_vote_ixs_active(&self) -> bool {
779 self.environment_config
780 .feature_set
781 .deprecate_legacy_vote_ixs
782 }
783
784 pub fn is_alpenglow_migration_succeeded(&self) -> bool {
785 self.environment_config.alpenglow_migration_succeeded
786 }
787
788 pub fn get_epoch_stake(&self) -> u64 {
790 self.environment_config
791 .epoch_stake_callback
792 .get_epoch_stake()
793 }
794
795 pub fn get_epoch_stake_for_vote_account(&self, pubkey: &'a Pubkey) -> u64 {
797 self.environment_config
798 .epoch_stake_callback
799 .get_epoch_stake_for_vote_account(pubkey)
800 }
801
802 pub fn is_precompile(&self, pubkey: &Pubkey) -> bool {
803 self.environment_config
804 .epoch_stake_callback
805 .is_precompile(pubkey)
806 }
807
808 pub fn get_check_aligned(&self) -> bool {
810 self.transaction_context
811 .get_current_instruction_context()
812 .and_then(|instruction_context| {
813 let owner_id = instruction_context.get_program_owner();
814 debug_assert!(owner_id.is_ok());
815 owner_id
816 })
817 .map(|owner_key| owner_key != bpf_loader_deprecated::id())
818 .unwrap_or(true)
819 }
820
821 pub(crate) fn insert_register_trace(&mut self, register_trace: Vec<[u64; 12]>) {
823 if register_trace.is_empty() {
824 return;
825 }
826 let Ok(instruction_context) = self.transaction_context.get_current_instruction_context()
827 else {
828 return;
829 };
830 self.register_traces
831 .push((instruction_context.get_index_in_trace(), register_trace));
832 }
833
834 pub fn iterate_vm_traces(
836 &self,
837 callback: &dyn Fn(InstructionContext, &Executable, RegisterTrace),
838 ) {
839 for (index_in_trace, register_trace) in &self.register_traces {
840 let Ok(instruction_context) = self
841 .transaction_context
842 .get_instruction_context_at_index_in_trace(*index_in_trace)
843 else {
844 continue;
845 };
846 let Ok(program_id) = instruction_context.get_program_key() else {
847 continue;
848 };
849 let Some(entry) = self.program_cache_for_tx_batch.find(program_id) else {
850 continue;
851 };
852 let ProgramCacheEntryType::Loaded(ref executable) = entry.program else {
853 continue;
854 };
855 callback(instruction_context, executable, register_trace.as_slice());
856 }
857 }
858}
859
860#[cfg(feature = "dev-context-only-utils")]
861#[macro_export]
862macro_rules! with_mock_invoke_context_with_feature_set {
863 (
864 $invoke_context:ident,
865 $transaction_context:ident,
866 $feature_set:ident,
867 $top_level_instructions:literal,
868 $transaction_accounts:expr,
869 $all_accounts:expr $(,)?
870 ) => {
871 use {
872 solana_svm_callback::InvokeContextCallback,
873 solana_svm_log_collector::LogCollector,
874 $crate::{
875 __private::{
876 ComputeMeter, DropOnBailOut, Hash, ReadableAccount, Rent, TransactionContext,
877 },
878 execution_budget::{SVMTransactionExecutionBudget, SVMTransactionExecutionCost},
879 invoke_context::{EnvironmentConfig, InvokeContext},
880 loaded_programs::{ProgramCacheForTxBatch, ProgramRuntimeEnvironments},
881 sysvar_cache::SysvarCache,
882 },
883 };
884
885 struct MockInvokeContextCallback {}
886 impl InvokeContextCallback for MockInvokeContextCallback {}
887
888 let compute_budget = SVMTransactionExecutionBudget::new_with_defaults(
889 $feature_set.raise_cpi_nesting_limit_to_8,
890 );
891 let mut sysvar_cache = SysvarCache::default();
892 sysvar_cache.fill_missing_entries(|pubkey, callback| {
893 for (key, account) in $all_accounts.iter() {
894 if key == pubkey {
895 callback(account.data());
896 }
897 }
898 });
899 let mut $transaction_context = TransactionContext::new_with_feature_flags(
900 $transaction_accounts,
901 Rent::default(),
902 compute_budget.max_instruction_stack_depth,
903 compute_budget.max_instruction_trace_length,
904 $top_level_instructions,
905 ComputeMeter::new(compute_budget.compute_unit_limit),
906 DropOnBailOut::Disabled,
907 );
908 let program_runtime_environments = ProgramRuntimeEnvironments::mock();
909 let environment_config = EnvironmentConfig::new(
910 Hash::default(),
911 0,
912 false,
913 &MockInvokeContextCallback {},
914 $feature_set,
915 &program_runtime_environments,
916 &sysvar_cache,
917 );
918 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
919 let mut $invoke_context = InvokeContext::new(
920 &mut $transaction_context,
921 &mut program_cache_for_tx_batch,
922 environment_config,
923 Some(LogCollector::new_ref()),
924 compute_budget,
925 SVMTransactionExecutionCost::default(),
926 );
927 };
928 (
929 $invoke_context:ident,
930 $transaction_context:ident,
931 $feature_set:ident,
932 $top_level_instructions:literal,
933 $transaction_accounts:expr $(,)?
934 ) => {
935 let transaction_accounts: Vec<(solana_pubkey::Pubkey, solana_account::AccountSharedData)> =
936 $transaction_accounts;
937 $crate::with_mock_invoke_context_with_feature_set!(
938 $invoke_context,
939 $transaction_context,
940 $feature_set,
941 $top_level_instructions,
942 transaction_accounts,
943 &transaction_accounts
944 );
945 };
946 (
947 $invoke_context:ident,
948 $transaction_context:ident,
949 $feature_set:ident,
950 $transaction_accounts:expr $(,)?
951 ) => {
952 $crate::with_mock_invoke_context_with_feature_set!(
953 $invoke_context,
954 $transaction_context,
955 $feature_set,
956 1,
957 $transaction_accounts
958 );
959 };
960}
961
962#[cfg(feature = "dev-context-only-utils")]
963#[macro_export]
964macro_rules! with_mock_invoke_context {
965 (
966 $invoke_context:ident,
967 $transaction_context:ident,
968 $top_level_instructions:literal,
969 $transaction_accounts:expr $(,)?
970 ) => {
971 let feature_set = &solana_svm_feature_set::SVMFeatureSet::default();
972 $crate::with_mock_invoke_context_with_feature_set!(
973 $invoke_context,
974 $transaction_context,
975 feature_set,
976 $top_level_instructions,
977 $transaction_accounts
978 )
979 };
980 (
981 $invoke_context:ident,
982 $transaction_context:ident,
983 $transaction_accounts:expr $(,)?
984 ) => {
985 with_mock_invoke_context!(
986 $invoke_context,
987 $transaction_context,
988 1,
989 $transaction_accounts
990 );
991 };
992}
993
994#[cfg(feature = "dev-context-only-utils")]
995pub fn mock_compile_message<A>(
996 instruction: &Instruction,
997 accounts: &[(Pubkey, A)],
998 program_id: &Pubkey,
999 loader_key: &Pubkey,
1000) -> (SanitizedMessage, Vec<(Pubkey, AccountSharedData)>)
1001where
1002 AccountSharedData: From<A>,
1003 A: Clone,
1004{
1005 let message = Message::new(std::slice::from_ref(instruction), None);
1006 let transaction_accounts: Vec<_> = message
1007 .account_keys
1008 .iter()
1009 .map(|key| {
1010 let account = accounts
1011 .iter()
1012 .find(|(k, _)| k == key)
1013 .map(|(_, a)| AccountSharedData::from(a.clone()))
1014 .unwrap_or_else(|| {
1015 if key == program_id {
1016 let mut account = AccountSharedData::new(0, 0, loader_key);
1017 account.set_executable(true);
1018 account
1019 } else {
1020 AccountSharedData::default()
1021 }
1022 });
1023 (*key, account)
1024 })
1025 .collect();
1026
1027 let sanitized_message = SanitizedMessage::Legacy(LegacyMessage::new(message, &HashSet::new()));
1028
1029 (sanitized_message, transaction_accounts)
1030}
1031
1032#[cfg(feature = "dev-context-only-utils")]
1033pub fn mock_process_instruction_with_feature_set<
1034 F: FnMut(&mut InvokeContext),
1035 G: FnMut(&mut InvokeContext),
1036>(
1037 program_id: &Pubkey,
1038 instruction_data: &[u8],
1039 mut accounts: Vec<KeyedAccountSharedData>,
1040 instruction_account_metas: Vec<AccountMeta>,
1041 expected_result: Result<(), InstructionError>,
1042 builtin: BuiltinFunctionRegisterer,
1043 mut pre_adjustments: F,
1044 mut post_adjustments: G,
1045 feature_set: &SVMFeatureSet,
1046) -> Vec<AccountSharedData> {
1047 let original_len = accounts.len();
1048 if !accounts
1049 .iter()
1050 .any(|(key, _)| *key == sysvar::epoch_schedule::id())
1051 {
1052 let account = AccountSharedData::new_data_with_space(
1053 1,
1054 &EpochSchedule::default(),
1055 solana_epoch_schedule::SIZE,
1056 &sysvar::id(),
1057 )
1058 .unwrap();
1059 accounts.push((sysvar::epoch_schedule::id(), account));
1060 }
1061
1062 let instruction =
1063 Instruction::new_with_bytes(*program_id, instruction_data, instruction_account_metas);
1064 let (sanitized_message, transaction_accounts) =
1065 mock_compile_message(&instruction, &accounts, program_id, &native_loader::id());
1066
1067 let program_owner = accounts
1068 .iter()
1069 .find(|(key, _)| key == program_id)
1070 .map(|(_, acct)| *acct.owner())
1071 .unwrap_or_else(native_loader::id);
1072 let is_builtin = native_loader::check_id(&program_owner);
1073
1074 with_mock_invoke_context_with_feature_set!(
1075 invoke_context,
1076 transaction_context,
1077 feature_set,
1078 1,
1079 transaction_accounts,
1080 &accounts
1081 );
1082
1083 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1084 program_cache_for_tx_batch.replenish(
1085 if is_builtin {
1086 *program_id
1087 } else {
1088 program_owner
1089 },
1090 Arc::new(ProgramCacheEntry::new_builtin(builtin)),
1091 );
1092 program_cache_for_tx_batch.set_slot_for_tests(
1093 invoke_context
1094 .environment_config
1095 .sysvar_cache()
1096 .get_clock()
1097 .map(|clock| clock.slot)
1098 .unwrap_or(1),
1099 );
1100 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1101
1102 pre_adjustments(&mut invoke_context);
1103
1104 invoke_context
1105 .prepare_top_level_instructions(&sanitized_message)
1106 .unwrap();
1107
1108 let result = invoke_context.process_instruction(&mut 0, &mut ExecuteTimings::default());
1109 assert_eq!(result, expected_result);
1110 post_adjustments(&mut invoke_context);
1111
1112 let txn_result_keys: Vec<_> = (0..transaction_context.get_number_of_accounts())
1113 .map(|i| *transaction_context.get_key_of_account_at_index(i).unwrap())
1114 .collect();
1115 let txn_result_accounts = transaction_context.deconstruct_without_keys().unwrap();
1116 let txn_result_map = txn_result_keys
1117 .into_iter()
1118 .zip(txn_result_accounts)
1119 .collect::<HashMap<Pubkey, AccountSharedData>>();
1120
1121 accounts
1122 .into_iter()
1123 .take(original_len)
1124 .map(|(key, original)| txn_result_map.get(&key).cloned().unwrap_or(original))
1125 .collect()
1126}
1127
1128#[cfg(feature = "dev-context-only-utils")]
1129pub fn mock_process_instruction<F: FnMut(&mut InvokeContext), G: FnMut(&mut InvokeContext)>(
1130 program_id: &Pubkey,
1131 instruction_data: &[u8],
1132 accounts: Vec<KeyedAccountSharedData>,
1133 instruction_account_metas: Vec<AccountMeta>,
1134 expected_result: Result<(), InstructionError>,
1135 builtin: BuiltinFunctionRegisterer,
1136 pre_adjustments: F,
1137 post_adjustments: G,
1138) -> Vec<AccountSharedData> {
1139 mock_process_instruction_with_feature_set(
1140 program_id,
1141 instruction_data,
1142 accounts,
1143 instruction_account_metas,
1144 expected_result,
1145 builtin,
1146 pre_adjustments,
1147 post_adjustments,
1148 &SVMFeatureSet::all_enabled(),
1149 )
1150}
1151
1152#[cfg(test)]
1153mod tests {
1154 use {
1155 super::*,
1156 crate::execution_budget::{
1157 DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, MAX_INSTRUCTION_STACK_DEPTH,
1158 MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268,
1159 },
1160 openssl::{
1161 ec::{EcGroup, EcKey},
1162 nid::Nid,
1163 },
1164 solana_account::{Account, DUMMY_INHERITABLE_ACCOUNT_FIELDS, ReadableAccount},
1165 solana_ed25519_program::new_ed25519_instruction_with_signature,
1166 solana_keypair::{Address, Keypair},
1167 solana_message::AccountKeys,
1168 solana_precompile_error::PrecompileError,
1169 solana_rent::Rent,
1170 solana_sbpf::program::BuiltinFunctionDefinition,
1171 solana_sdk_ids::{ed25519_program, secp256k1_program, system_program},
1172 solana_secp256k1_program::{
1173 eth_address_from_pubkey, new_secp256k1_instruction_with_signature,
1174 },
1175 solana_secp256r1_program::{new_secp256r1_instruction_with_signature, sign_message},
1176 solana_signer::Signer,
1177 solana_svm_feature_set::SVMFeatureSet,
1178 solana_transaction::{Transaction, sanitized::SanitizedTransaction},
1179 solana_transaction_context::{MAX_ACCOUNTS_PER_INSTRUCTION, MAX_ACCOUNTS_PER_TRANSACTION},
1180 test_case::test_case,
1181 };
1182
1183 #[derive(Debug, wincode::SchemaRead, wincode::SchemaWrite)]
1184 enum MockInstruction {
1185 NoopSuccess,
1186 NoopFail,
1187 ModifyOwned,
1188 ModifyNotOwned,
1189 ModifyReadonly,
1190 UnbalancedPush,
1191 UnbalancedPop,
1192 ConsumeComputeUnits {
1193 compute_units_to_consume: u64,
1194 desired_result: Result<(), InstructionError>,
1195 },
1196 Resize {
1197 new_len: u64,
1198 },
1199 }
1200
1201 const MOCK_BUILTIN_COMPUTE_UNIT_COST: u64 = 1;
1202
1203 declare_process_instruction!(
1204 MockBuiltin,
1205 MOCK_BUILTIN_COMPUTE_UNIT_COST,
1206 |invoke_context| {
1207 let transaction_context = &invoke_context.transaction_context;
1208 let instruction_context = transaction_context.get_current_instruction_context()?;
1209 let instruction_data = instruction_context.get_instruction_data();
1210 let program_id = instruction_context.get_program_key()?;
1211 let instruction_accounts = (0..4)
1212 .map(|instruction_account_index| {
1213 InstructionAccount::new(instruction_account_index, false, false)
1214 })
1215 .collect::<Vec<_>>();
1216 assert_eq!(
1217 program_id,
1218 instruction_context
1219 .try_borrow_instruction_account(0)?
1220 .get_owner()
1221 );
1222 assert_ne!(
1223 instruction_context
1224 .try_borrow_instruction_account(1)?
1225 .get_owner(),
1226 instruction_context.get_key_of_instruction_account(0)?
1227 );
1228
1229 if let Ok(instruction) = wincode::deserialize(instruction_data) {
1230 match instruction {
1231 MockInstruction::NoopSuccess => (),
1232 MockInstruction::NoopFail => return Err(InstructionError::GenericError),
1233 MockInstruction::ModifyOwned => instruction_context
1234 .try_borrow_instruction_account(0)?
1235 .set_data_from_slice(&[1])?,
1236 MockInstruction::ModifyNotOwned => instruction_context
1237 .try_borrow_instruction_account(1)?
1238 .set_data_from_slice(&[1])?,
1239 MockInstruction::ModifyReadonly => instruction_context
1240 .try_borrow_instruction_account(2)?
1241 .set_data_from_slice(&[1])?,
1242 MockInstruction::UnbalancedPush => {
1243 instruction_context
1244 .try_borrow_instruction_account(0)?
1245 .checked_add_lamports(1)?;
1246 let program_id = *transaction_context.get_key_of_account_at_index(3)?;
1247 let metas = vec![
1248 AccountMeta::new_readonly(
1249 *transaction_context.get_key_of_account_at_index(0)?,
1250 false,
1251 ),
1252 AccountMeta::new_readonly(
1253 *transaction_context.get_key_of_account_at_index(1)?,
1254 false,
1255 ),
1256 ];
1257 let inner_instruction = Instruction::new_with_wincode(
1258 program_id,
1259 &MockInstruction::NoopSuccess,
1260 metas,
1261 );
1262 invoke_context
1263 .transaction_context
1264 .configure_top_level_instruction_for_tests(
1265 3,
1266 instruction_accounts,
1267 vec![],
1268 )
1269 .unwrap();
1270 let result = invoke_context.push();
1271 assert_eq!(result, Err(InstructionError::UnbalancedInstruction));
1272 result?;
1273 invoke_context
1274 .native_invoke_signed(inner_instruction, &[])
1275 .and(invoke_context.pop())?;
1276 }
1277 MockInstruction::UnbalancedPop => instruction_context
1278 .try_borrow_instruction_account(0)?
1279 .checked_add_lamports(1)?,
1280 MockInstruction::ConsumeComputeUnits {
1281 compute_units_to_consume,
1282 desired_result,
1283 } => {
1284 invoke_context
1285 .consume_checked(compute_units_to_consume)
1286 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
1287 return desired_result;
1288 }
1289 MockInstruction::Resize { new_len } => instruction_context
1290 .try_borrow_instruction_account(0)?
1291 .set_data_from_slice(&vec![0; new_len as usize])?,
1292 }
1293 } else {
1294 return Err(InstructionError::InvalidInstructionData);
1295 }
1296 Ok(())
1297 }
1298 );
1299
1300 #[test_case(false; "SIMD-0268 disabled")]
1301 #[test_case(true; "SIMD-0268 enabled")]
1302 fn test_instruction_stack_height(simd_0268_active: bool) {
1303 let feature_set = &SVMFeatureSet {
1304 raise_cpi_nesting_limit_to_8: simd_0268_active,
1305 ..SVMFeatureSet::all_enabled()
1306 };
1307 let max_depth = SVMTransactionExecutionBudget::new_with_defaults(simd_0268_active)
1308 .max_instruction_stack_depth;
1309 assert_eq!(
1310 max_depth,
1311 if simd_0268_active {
1312 MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268
1313 } else {
1314 MAX_INSTRUCTION_STACK_DEPTH
1315 },
1316 );
1317
1318 let mut invoke_stack = vec![];
1321 let mut transaction_accounts = vec![];
1322 let mut instruction_accounts = vec![];
1323 for index in 0..max_depth.saturating_add(1) {
1324 let program_id = solana_pubkey::new_rand();
1325 invoke_stack.push(program_id);
1326 transaction_accounts.push((
1327 solana_pubkey::new_rand(),
1328 AccountSharedData::new(1, 1, &program_id),
1329 ));
1330 instruction_accounts.push(InstructionAccount::new(
1331 index as IndexOfAccount,
1332 false,
1333 true,
1334 ));
1335 }
1336
1337 let first_program_account = transaction_accounts.len();
1340 for (index, program_id) in invoke_stack.iter().enumerate() {
1341 transaction_accounts.push((
1342 *program_id,
1343 AccountSharedData::new(1, 1, &solana_pubkey::Pubkey::default()),
1344 ));
1345 instruction_accounts.push(InstructionAccount::new(
1346 index as IndexOfAccount,
1347 false,
1348 false,
1349 ));
1350 }
1351 with_mock_invoke_context_with_feature_set!(
1352 invoke_context,
1353 transaction_context,
1354 feature_set,
1355 transaction_accounts,
1356 );
1357
1358 for depth in 0..max_depth {
1360 assert_eq!(invoke_context.get_stack_height(), depth);
1361 invoke_context
1362 .transaction_context
1363 .configure_top_level_instruction_for_tests(
1364 (first_program_account.saturating_add(depth)) as IndexOfAccount,
1365 instruction_accounts.clone(),
1366 vec![],
1367 )
1368 .unwrap();
1369 assert!(
1370 invoke_context.push().is_ok(),
1371 "push at depth {depth} should succeed (max_depth={max_depth})",
1372 );
1373 }
1374
1375 assert_eq!(invoke_context.get_stack_height(), max_depth);
1377 invoke_context
1378 .transaction_context
1379 .configure_top_level_instruction_for_tests(
1380 (first_program_account.saturating_add(max_depth)) as IndexOfAccount,
1381 instruction_accounts.clone(),
1382 vec![],
1383 )
1384 .unwrap();
1385 assert_eq!(invoke_context.push(), Err(InstructionError::CallDepth),);
1386
1387 assert_eq!(invoke_context.get_stack_height(), max_depth);
1389 }
1390
1391 #[test_case(false; "simd_582_disabled")]
1392 #[test_case(true; "simd_582_enabled")]
1393 fn test_max_instruction_trace_length_top_level(fail_early: bool) {
1394 const MAX_INSTRUCTIONS: usize = 8;
1395 let mut transaction_context = TransactionContext::new(
1396 vec![(
1397 Pubkey::new_unique(),
1398 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1399 )],
1400 Rent::default(),
1401 1,
1402 MAX_INSTRUCTIONS,
1403 MAX_INSTRUCTIONS,
1404 );
1405 for _ in 0..MAX_INSTRUCTIONS {
1406 transaction_context.push(fail_early).unwrap();
1407 transaction_context
1408 .configure_top_level_instruction_for_tests(
1409 0,
1410 vec![InstructionAccount::new(0, false, false)],
1411 vec![],
1412 )
1413 .unwrap();
1414 transaction_context.pop().unwrap();
1415 }
1416
1417 assert_eq!(
1418 transaction_context.push(fail_early),
1419 Err(InstructionError::MaxInstructionTraceLengthExceeded)
1420 );
1421 }
1422
1423 #[test_case(false; "simd_582_disabled")]
1424 #[test_case(true; "simd_582_enabled")]
1425 fn test_max_instruction_trace_length_cpi(fail_early: bool) {
1426 const MAX_INSTRUCTIONS: usize = 8;
1428 let mut transaction_context = TransactionContext::new(
1429 vec![(
1430 Pubkey::new_unique(),
1431 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1432 )],
1433 Rent::default(),
1434 256,
1435 MAX_INSTRUCTIONS,
1436 2,
1437 );
1438 let num_transaction_accounts = usize::from(transaction_context.get_number_of_accounts());
1439
1440 transaction_context
1441 .configure_instruction_at_index(
1442 0,
1443 0,
1444 vec![InstructionAccount::new(0, false, false)],
1445 vec![u8::MAX; num_transaction_accounts],
1446 Cow::Owned(Vec::new()),
1447 None,
1448 )
1449 .unwrap();
1450
1451 transaction_context
1452 .configure_instruction_at_index(
1453 1,
1454 0,
1455 vec![InstructionAccount::new(0, false, false)],
1456 vec![u8::MAX; num_transaction_accounts],
1457 Cow::Owned(Vec::new()),
1458 None,
1459 )
1460 .unwrap();
1461
1462 for ix_in_trace in (2..).take(MAX_INSTRUCTIONS) {
1463 let result = transaction_context.push(fail_early);
1464 if ix_in_trace > MAX_INSTRUCTIONS && fail_early {
1465 assert_eq!(
1466 result,
1467 Err(InstructionError::MaxInstructionTraceLengthExceeded)
1468 );
1469 } else {
1470 assert!(result.is_ok());
1471 }
1472
1473 transaction_context
1474 .configure_next_cpi_for_tests(
1475 0,
1476 vec![InstructionAccount::new(0, false, false)],
1477 Vec::new(),
1478 )
1479 .unwrap();
1480 }
1481
1482 if !fail_early {
1483 assert_eq!(
1484 transaction_context.push(false),
1485 Err(InstructionError::MaxInstructionTraceLengthExceeded)
1486 );
1487 }
1488 }
1489
1490 #[test_case(MockInstruction::NoopSuccess, Ok(()); "NoopSuccess")]
1491 #[test_case(MockInstruction::NoopFail, Err(InstructionError::GenericError); "NoopFail")]
1492 #[test_case(MockInstruction::ModifyOwned, Ok(()); "ModifyOwned")]
1493 #[test_case(MockInstruction::ModifyNotOwned, Err(InstructionError::ExternalAccountDataModified); "ModifyNotOwned")]
1494 #[test_case(MockInstruction::ModifyReadonly, Err(InstructionError::ReadonlyDataModified); "ModifyReadonly")]
1495 #[test_case(MockInstruction::UnbalancedPush, Err(InstructionError::UnbalancedInstruction); "UnbalancedPush")]
1496 #[test_case(MockInstruction::UnbalancedPop, Err(InstructionError::UnbalancedInstruction); "UnbalancedPop")]
1497 fn test_process_instruction_account_modifications(
1498 instruction: MockInstruction,
1499 expected_result: Result<(), InstructionError>,
1500 ) {
1501 let callee_program_id = solana_pubkey::new_rand();
1502 let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1503 let not_owned_account = AccountSharedData::new(84, 1, &solana_pubkey::new_rand());
1504 let readonly_account = AccountSharedData::new(168, 1, &solana_pubkey::new_rand());
1505 let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1506 let mut program_account = AccountSharedData::new(1, 1, &native_loader::id());
1507 program_account.set_executable(true);
1508 let transaction_accounts = vec![
1509 (solana_pubkey::new_rand(), owned_account),
1510 (solana_pubkey::new_rand(), not_owned_account),
1511 (solana_pubkey::new_rand(), readonly_account),
1512 (callee_program_id, program_account),
1513 (solana_pubkey::new_rand(), loader_account),
1514 ];
1515 let metas = vec![
1516 AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1517 AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1518 AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1519 ];
1520 let instruction_accounts = (0..4)
1521 .map(|instruction_account_index| {
1522 InstructionAccount::new(
1523 instruction_account_index,
1524 false,
1525 instruction_account_index < 2,
1526 )
1527 })
1528 .collect::<Vec<_>>();
1529 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1530 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1531 program_cache_for_tx_batch.replenish(
1532 callee_program_id,
1533 Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
1534 );
1535 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1536
1537 invoke_context
1539 .transaction_context
1540 .configure_top_level_instruction_for_tests(4, instruction_accounts, vec![])
1541 .unwrap();
1542 invoke_context.push().unwrap();
1543 let inner_instruction =
1544 Instruction::new_with_wincode(callee_program_id, &instruction, metas);
1545 let result = invoke_context
1546 .native_invoke_signed(inner_instruction, &[])
1547 .and(invoke_context.pop());
1548 assert_eq!(result, expected_result);
1549 }
1550
1551 #[test_case(Ok(()); "Ok")]
1552 #[test_case(Err(InstructionError::GenericError); "GenericError")]
1553 fn test_process_instruction_compute_unit_consumption(
1554 expected_result: Result<(), InstructionError>,
1555 ) {
1556 let callee_program_id = solana_pubkey::new_rand();
1557 let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1558 let not_owned_account = AccountSharedData::new(84, 1, &solana_pubkey::new_rand());
1559 let readonly_account = AccountSharedData::new(168, 1, &solana_pubkey::new_rand());
1560 let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1561 let mut program_account = AccountSharedData::new(1, 1, &native_loader::id());
1562 program_account.set_executable(true);
1563 let transaction_accounts = vec![
1564 (solana_pubkey::new_rand(), owned_account),
1565 (solana_pubkey::new_rand(), not_owned_account),
1566 (solana_pubkey::new_rand(), readonly_account),
1567 (callee_program_id, program_account),
1568 (solana_pubkey::new_rand(), loader_account),
1569 ];
1570 let metas = vec![
1571 AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1572 AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1573 AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1574 ];
1575 let instruction_accounts = (0..4)
1576 .map(|instruction_account_index| {
1577 InstructionAccount::new(
1578 instruction_account_index,
1579 false,
1580 instruction_account_index < 2,
1581 )
1582 })
1583 .collect::<Vec<_>>();
1584 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1585 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1586 program_cache_for_tx_batch.replenish(
1587 callee_program_id,
1588 Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
1589 );
1590 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1591
1592 let compute_units_to_consume = 10;
1594 invoke_context
1595 .transaction_context
1596 .configure_top_level_instruction_for_tests(4, instruction_accounts, vec![])
1597 .unwrap();
1598 invoke_context.push().unwrap();
1599 let inner_instruction = Instruction::new_with_wincode(
1600 callee_program_id,
1601 &MockInstruction::ConsumeComputeUnits {
1602 compute_units_to_consume,
1603 desired_result: expected_result.clone(),
1604 },
1605 metas,
1606 );
1607
1608 invoke_context
1609 .build_instruction_frame(inner_instruction)
1610 .unwrap();
1611 invoke_context.verify_instruction_accounts(&[]).unwrap();
1612
1613 let mut compute_units_consumed = 0;
1614 let result = invoke_context
1615 .process_instruction(&mut compute_units_consumed, &mut ExecuteTimings::default());
1616
1617 assert!(compute_units_consumed > 0);
1621 assert_eq!(
1622 compute_units_consumed,
1623 compute_units_to_consume.saturating_add(MOCK_BUILTIN_COMPUTE_UNIT_COST),
1624 );
1625 assert_eq!(result, expected_result);
1626
1627 invoke_context.pop().unwrap();
1628 }
1629
1630 #[test]
1631 fn test_invoke_context_compute_budget() {
1632 let transaction_accounts = vec![(solana_pubkey::new_rand(), AccountSharedData::default())];
1633 let execution_budget = SVMTransactionExecutionBudget {
1634 compute_unit_limit: u64::from(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT),
1635 ..SVMTransactionExecutionBudget::default()
1636 };
1637
1638 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1639 invoke_context.compute_budget = execution_budget;
1640
1641 invoke_context
1642 .transaction_context
1643 .configure_top_level_instruction_for_tests(0, vec![], vec![])
1644 .unwrap();
1645 invoke_context.push().unwrap();
1646 assert_eq!(*invoke_context.get_compute_budget(), execution_budget);
1647 invoke_context.pop().unwrap();
1648 }
1649
1650 #[test_case(0; "Resize the account to *the same size*, so not consuming any additional size")]
1651 #[test_case(1; "Resize the account larger")]
1652 #[test_case(-1; "Resize the account smaller")]
1653 fn test_process_instruction_accounts_resize_delta(resize_delta: i64) {
1654 let program_key = Pubkey::new_unique();
1655 let user_account_data_len = 123u64;
1656 let user_account =
1657 AccountSharedData::new(100, user_account_data_len as usize, &program_key);
1658 let dummy_account = AccountSharedData::new(10, 0, &program_key);
1659 let mut program_account = AccountSharedData::new(500, 500, &native_loader::id());
1660 program_account.set_executable(true);
1661 let transaction_accounts = vec![
1662 (Pubkey::new_unique(), user_account),
1663 (Pubkey::new_unique(), dummy_account),
1664 (program_key, program_account),
1665 ];
1666 let instruction_accounts = vec![
1667 InstructionAccount::new(0, false, true),
1668 InstructionAccount::new(1, false, false),
1669 ];
1670 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1671 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1672 program_cache_for_tx_batch.replenish(
1673 program_key,
1674 Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
1675 );
1676 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1677
1678 let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
1679 let instruction_data = wincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1680
1681 invoke_context
1682 .transaction_context
1683 .configure_top_level_instruction_for_tests(2, instruction_accounts, instruction_data)
1684 .unwrap();
1685 let result = invoke_context.process_instruction(&mut 0, &mut ExecuteTimings::default());
1686
1687 assert!(result.is_ok());
1688 assert_eq!(
1689 invoke_context.transaction_context.accounts().resize_delta(),
1690 resize_delta
1691 );
1692 }
1693
1694 #[test]
1695 fn test_prepare_instruction_maximum_accounts() {
1696 const MAX_ACCOUNTS_REFERENCED: usize = MAX_ACCOUNTS_PER_INSTRUCTION;
1697 let mut transaction_accounts: Vec<KeyedAccountSharedData> =
1698 Vec::with_capacity(MAX_ACCOUNTS_PER_TRANSACTION);
1699 let mut account_metas: Vec<AccountMeta> = Vec::with_capacity(MAX_ACCOUNTS_REFERENCED);
1700
1701 let fee_payer = Keypair::new();
1703 transaction_accounts.push((
1704 fee_payer.pubkey(),
1705 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1706 ));
1707 account_metas.push(AccountMeta::new(fee_payer.pubkey(), true));
1708
1709 let program_id = Pubkey::new_unique();
1710 let mut program_account = AccountSharedData::new(1, 1, &Pubkey::new_unique());
1711 program_account.set_executable(true);
1712 transaction_accounts.push((program_id, program_account));
1713 account_metas.push(AccountMeta::new_readonly(program_id, false));
1714
1715 for _ in 2..MAX_ACCOUNTS_REFERENCED {
1717 let key = Pubkey::new_unique();
1718 transaction_accounts.push((key, AccountSharedData::new(1, 1, &Pubkey::new_unique())));
1719 account_metas.push(AccountMeta::new_readonly(key, false));
1720 }
1721
1722 with_mock_invoke_context!(invoke_context, transaction_context, 2, transaction_accounts);
1723
1724 let instruction_1 = Instruction::new_with_bytes(program_id, &[20], account_metas.clone());
1725
1726 let instruction_2 = Instruction::new_with_bytes(
1727 program_id,
1728 &[20],
1729 account_metas.iter().rev().cloned().collect(),
1730 );
1731
1732 let transaction = Transaction::new_with_payer(
1733 &[instruction_1.clone(), instruction_2.clone()],
1734 Some(&fee_payer.pubkey()),
1735 );
1736
1737 let sanitized =
1738 SanitizedTransaction::try_from_legacy_transaction(transaction, &HashSet::new())
1739 .unwrap();
1740
1741 fn test_case_1(invoke_context: &InvokeContext) {
1742 let instruction_context = invoke_context
1743 .transaction_context
1744 .get_next_instruction_context()
1745 .unwrap();
1746 for index_in_instruction in 0..MAX_ACCOUNTS_REFERENCED as IndexOfAccount {
1747 let index_in_transaction = instruction_context
1748 .get_index_of_instruction_account_in_transaction(index_in_instruction)
1749 .unwrap();
1750 let other_ix_index = instruction_context
1751 .get_index_of_account_in_instruction(index_in_transaction)
1752 .unwrap();
1753 assert_eq!(index_in_instruction, index_in_transaction);
1754 assert_eq!(index_in_instruction, other_ix_index);
1755 }
1756 }
1757
1758 fn test_case_2(invoke_context: &InvokeContext) {
1759 let instruction_context = invoke_context
1760 .transaction_context
1761 .get_next_instruction_context()
1762 .unwrap();
1763 for index_in_instruction in 0..MAX_ACCOUNTS_REFERENCED as IndexOfAccount {
1764 let index_in_transaction = instruction_context
1765 .get_index_of_instruction_account_in_transaction(index_in_instruction)
1766 .unwrap();
1767 let other_ix_index = instruction_context
1768 .get_index_of_account_in_instruction(index_in_transaction)
1769 .unwrap();
1770 assert_eq!(
1771 index_in_transaction,
1772 (MAX_ACCOUNTS_REFERENCED as u16)
1773 .saturating_sub(index_in_instruction)
1774 .saturating_sub(1)
1775 );
1776 assert_eq!(index_in_instruction, other_ix_index);
1777 }
1778 }
1779
1780 invoke_context
1781 .prepare_top_level_instructions(&sanitized)
1782 .unwrap();
1783
1784 test_case_1(&invoke_context);
1785
1786 invoke_context.transaction_context.push(true).unwrap();
1787 invoke_context.transaction_context.pop().unwrap();
1788
1789 test_case_2(&invoke_context);
1790
1791 invoke_context.transaction_context.push(true).unwrap();
1792 invoke_context
1793 .build_instruction_frame(instruction_1)
1794 .unwrap();
1795 invoke_context
1796 .verify_instruction_accounts(&[fee_payer.pubkey()])
1797 .unwrap();
1798 test_case_1(&invoke_context);
1799
1800 invoke_context.transaction_context.push(true).unwrap();
1801 invoke_context
1802 .build_instruction_frame(instruction_2)
1803 .unwrap();
1804 invoke_context
1805 .verify_instruction_accounts(&[fee_payer.pubkey()])
1806 .unwrap();
1807 test_case_2(&invoke_context);
1808 }
1809
1810 #[test]
1811 fn test_duplicated_accounts() {
1812 let mut transaction_accounts: Vec<KeyedAccountSharedData> =
1813 Vec::with_capacity(MAX_ACCOUNTS_PER_TRANSACTION);
1814 let mut account_metas: Vec<AccountMeta> =
1815 Vec::with_capacity(MAX_ACCOUNTS_PER_INSTRUCTION.saturating_sub(1));
1816
1817 let fee_payer = Keypair::new();
1819 transaction_accounts.push((
1820 fee_payer.pubkey(),
1821 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1822 ));
1823 account_metas.push(AccountMeta::new(fee_payer.pubkey(), true));
1824
1825 let program_id = Pubkey::new_unique();
1826 let mut program_account = AccountSharedData::new(1, 1, &Pubkey::new_unique());
1827 program_account.set_executable(true);
1828 transaction_accounts.push((program_id, program_account));
1829 account_metas.push(AccountMeta::new_readonly(program_id, false));
1830
1831 for i in 2..account_metas.capacity() {
1832 if i % 2 == 0 {
1833 let key = Pubkey::new_unique();
1834 transaction_accounts
1835 .push((key, AccountSharedData::new(1, 1, &Pubkey::new_unique())));
1836 account_metas.push(AccountMeta::new_readonly(key, false));
1837 } else {
1838 let last_key = transaction_accounts.last().unwrap().0;
1839 account_metas.push(AccountMeta::new_readonly(last_key, false));
1840 }
1841 }
1842
1843 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1844
1845 let instruction = Instruction::new_with_bytes(program_id, &[20], account_metas.clone());
1846
1847 let transaction = Transaction::new_with_payer(&[instruction], Some(&fee_payer.pubkey()));
1848
1849 let sanitized =
1850 SanitizedTransaction::try_from_legacy_transaction(transaction, &HashSet::new())
1851 .unwrap();
1852
1853 invoke_context
1854 .prepare_top_level_instructions(&sanitized)
1855 .unwrap();
1856
1857 {
1858 let instruction_context = invoke_context
1859 .transaction_context
1860 .get_next_instruction_context()
1861 .unwrap();
1862 for index_in_instruction in 2..account_metas.len() as IndexOfAccount {
1863 let is_duplicate = instruction_context
1864 .is_instruction_account_duplicate(index_in_instruction)
1865 .unwrap();
1866 if index_in_instruction % 2 == 0 {
1867 assert!(is_duplicate.is_none());
1868 } else {
1869 assert_eq!(is_duplicate, Some(index_in_instruction.saturating_sub(1)));
1870 }
1871 }
1872 }
1873
1874 invoke_context.transaction_context.push(true).unwrap();
1875
1876 let instruction = Instruction::new_with_bytes(
1877 program_id,
1878 &[20],
1879 account_metas.iter().cloned().rev().collect(),
1880 );
1881
1882 invoke_context.build_instruction_frame(instruction).unwrap();
1883 invoke_context
1884 .verify_instruction_accounts(&[fee_payer.pubkey()])
1885 .unwrap();
1886 let instruction_context = invoke_context
1887 .transaction_context
1888 .get_next_instruction_context()
1889 .unwrap();
1890 for index_in_instruction in 2..account_metas.len().saturating_sub(1) as u16 {
1891 let is_duplicate = instruction_context
1892 .is_instruction_account_duplicate(index_in_instruction)
1893 .unwrap();
1894 if index_in_instruction % 2 == 0 {
1895 assert!(is_duplicate.is_none());
1896 } else {
1897 assert_eq!(is_duplicate, Some(index_in_instruction.saturating_sub(1)));
1898 }
1899 }
1900 }
1901
1902 const TEST_CALLER_PROGRAM_ID: Pubkey = Pubkey::new_from_array([1u8; 32]);
1904 const TEST_CALLEE_PROGRAM_ID: Pubkey = Pubkey::new_from_array([2u8; 32]);
1905 const TEST_WRONG_PROGRAM_ID: Pubkey = Pubkey::new_from_array([3u8; 32]);
1906 const TEST_MOCK_EXTRA_KEY: Pubkey = Pubkey::new_from_array([4u8; 32]);
1907 const TEST_ACCOUNT_KEY: Pubkey = Pubkey::new_from_array([5u8; 32]);
1908
1909 fn run_native_invoke_signed_test(
1918 target_key: Pubkey,
1919 target_is_signer: bool,
1920 inner_instruction: Instruction,
1921 signer_seeds: &[&[&[u8]]],
1922 ) -> Result<(), InstructionError> {
1923 let target_account = AccountSharedData::new(100, 0, &TEST_CALLEE_PROGRAM_ID);
1924 let mock_extra_account = AccountSharedData::new(0, 1, &system_program::id());
1925 let mut caller_program_account = AccountSharedData::new(1, 1, &native_loader::id());
1926 caller_program_account.set_executable(true);
1927 let mut callee_program_account = AccountSharedData::new(1, 1, &native_loader::id());
1928 callee_program_account.set_executable(true);
1929 let transaction_accounts = vec![
1930 (target_key, target_account),
1931 (TEST_CALLER_PROGRAM_ID, caller_program_account),
1932 (TEST_MOCK_EXTRA_KEY, mock_extra_account),
1933 (TEST_CALLEE_PROGRAM_ID, callee_program_account),
1934 ];
1935
1936 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1937 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1938 program_cache_for_tx_batch.replenish(
1939 TEST_CALLEE_PROGRAM_ID,
1940 Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
1941 );
1942 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1943
1944 let instruction_accounts = (0..4)
1945 .map(|i| InstructionAccount::new(i, i == 0 && target_is_signer, i < 2))
1946 .collect::<Vec<_>>();
1947 invoke_context
1948 .transaction_context
1949 .configure_top_level_instruction_for_tests(1, instruction_accounts, vec![])
1950 .unwrap();
1951 invoke_context.push().unwrap();
1952
1953 let result = invoke_context.native_invoke_signed(inner_instruction, signer_seeds);
1954 invoke_context.pop().unwrap();
1955 result
1956 }
1957
1958 #[test]
1959 fn test_native_invoke_signed_instruction_account_limit() {
1960 let instruction = Instruction::new_with_wincode(
1961 TEST_CALLEE_PROGRAM_ID,
1962 &MockInstruction::NoopSuccess,
1963 vec![
1964 AccountMeta::new_readonly(TEST_ACCOUNT_KEY, false);
1965 MAX_ACCOUNTS_PER_INSTRUCTION + 1
1966 ],
1967 );
1968 assert_eq!(
1969 run_native_invoke_signed_test(TEST_ACCOUNT_KEY, false, instruction, &[]),
1970 Err(InstructionError::MaxAccountsExceeded),
1971 );
1972 }
1973
1974 #[test]
1976 fn test_native_invoke_signed_with_valid_pda_signer() {
1977 let (pda_key, bump_seed) =
1978 Pubkey::find_program_address(&[b"seed"], &TEST_CALLER_PROGRAM_ID);
1979 let instruction = Instruction::new_with_wincode(
1980 TEST_CALLEE_PROGRAM_ID,
1981 &MockInstruction::NoopSuccess,
1982 vec![
1983 AccountMeta::new(pda_key, true),
1984 AccountMeta::new_readonly(TEST_MOCK_EXTRA_KEY, false),
1985 ],
1986 );
1987 let result =
1988 run_native_invoke_signed_test(pda_key, false, instruction, &[&[b"seed", &[bump_seed]]]);
1989 assert!(
1990 result.is_ok(),
1991 "valid PDA signer should succeed: {result:?}"
1992 );
1993 }
1994
1995 #[test]
1999 fn test_native_invoke_signed_with_invalid_seeds() {
2000 let instruction = Instruction::new_with_wincode(
2001 TEST_CALLEE_PROGRAM_ID,
2002 &MockInstruction::NoopSuccess,
2003 vec![AccountMeta::new(TEST_ACCOUNT_KEY, true)],
2004 );
2005 let oversized_seed = [0u8; 33];
2006 let result = run_native_invoke_signed_test(
2007 TEST_ACCOUNT_KEY,
2008 false,
2009 instruction,
2010 &[&[&oversized_seed]],
2011 );
2012 assert_eq!(result, Err(InstructionError::Custom(0)));
2013 }
2014
2015 #[test]
2018 fn test_native_invoke_signed_pda_privilege_escalation_without_seeds() {
2019 let (pda_key, _bump_seed) =
2020 Pubkey::find_program_address(&[b"seed"], &TEST_CALLER_PROGRAM_ID);
2021 let instruction = Instruction::new_with_wincode(
2022 TEST_CALLEE_PROGRAM_ID,
2023 &MockInstruction::NoopSuccess,
2024 vec![AccountMeta::new(pda_key, true)],
2025 );
2026 let result = run_native_invoke_signed_test(pda_key, false, instruction, &[]);
2027 assert_eq!(result, Err(InstructionError::PrivilegeEscalation));
2028 }
2029
2030 #[test]
2033 fn test_native_invoke_signed_uses_caller_program_id_for_pda() {
2034 let (pda_key, bump_seed) = Pubkey::find_program_address(&[b"seed"], &TEST_WRONG_PROGRAM_ID);
2035 let instruction = Instruction::new_with_wincode(
2036 TEST_CALLEE_PROGRAM_ID,
2037 &MockInstruction::NoopSuccess,
2038 vec![AccountMeta::new(pda_key, true)],
2039 );
2040 let result =
2041 run_native_invoke_signed_test(pda_key, false, instruction, &[&[b"seed", &[bump_seed]]]);
2042 assert_eq!(result, Err(InstructionError::PrivilegeEscalation));
2043 }
2044
2045 #[test]
2047 fn test_native_invoke_signed_top_level_signer_needs_no_seeds() {
2048 let (pda_key, _bump_seed) =
2049 Pubkey::find_program_address(&[b"seed"], &TEST_CALLER_PROGRAM_ID);
2050 let instruction = Instruction::new_with_wincode(
2051 TEST_CALLEE_PROGRAM_ID,
2052 &MockInstruction::NoopSuccess,
2053 vec![
2054 AccountMeta::new(pda_key, true),
2055 AccountMeta::new_readonly(TEST_MOCK_EXTRA_KEY, false),
2056 ],
2057 );
2058 let result = run_native_invoke_signed_test(pda_key, true, instruction, &[]);
2059 assert!(
2060 result.is_ok(),
2061 "top-level signer should not need seeds: {result:?}"
2062 );
2063 }
2064
2065 #[test]
2066 fn test_compile_message() {
2067 let program_id = Pubkey::new_from_array([1u8; 32]);
2068 let writable = Pubkey::new_from_array([2u8; 32]);
2069 let loader_key = Pubkey::new_from_array([3u8; 32]);
2070
2071 let instruction = Instruction {
2072 program_id,
2073 accounts: vec![AccountMeta::new(writable, false)],
2074 data: vec![1, 2, 3],
2075 };
2076
2077 let accounts = vec![(
2078 writable,
2079 Account {
2080 lamports: 100,
2081 ..Account::default()
2082 },
2083 )];
2084
2085 let (message, tx_accounts) =
2086 mock_compile_message(&instruction, &accounts, &program_id, &loader_key);
2087
2088 assert_eq!(message.instructions().len(), 1);
2089 assert_eq!(tx_accounts.len(), 2);
2090 assert_eq!(tx_accounts.first().unwrap().0, writable);
2091 assert_eq!(tx_accounts.get(1).unwrap().0, program_id);
2092
2093 assert!(!message.is_signer(0));
2095 }
2096
2097 struct MockCallback {}
2098 impl InvokeContextCallback for MockCallback {}
2099
2100 fn create_loadable_account_for_test(name: &str) -> AccountSharedData {
2101 let (lamports, rent_epoch) = DUMMY_INHERITABLE_ACCOUNT_FIELDS;
2102 AccountSharedData::from(Account {
2103 lamports,
2104 owner: native_loader::id(),
2105 data: name.as_bytes().to_vec(),
2106 executable: true,
2107 rent_epoch,
2108 })
2109 }
2110
2111 fn new_sanitized_message(message: Message) -> SanitizedMessage {
2112 SanitizedMessage::try_from_legacy_message(message, &HashSet::new()).unwrap()
2113 }
2114
2115 #[test]
2116 fn test_reject_instruction_with_more_than_255_accounts() {
2117 let payer = Pubkey::new_unique();
2118 let other_account = Pubkey::new_unique();
2119 let program_id = Pubkey::new_unique();
2120 let mut instruction_accounts = vec![0; 256];
2121 instruction_accounts.push(1);
2122 let message = new_sanitized_message(Message::new_with_compiled_instructions(
2123 1,
2124 0,
2125 2,
2126 vec![payer, other_account, program_id],
2127 Hash::default(),
2128 vec![solana_message::compiled_instruction::CompiledInstruction {
2129 program_id_index: 2,
2130 accounts: instruction_accounts,
2131 data: vec![],
2132 }],
2133 ));
2134 let accounts = vec![
2135 (payer, AccountSharedData::default()),
2136 (other_account, AccountSharedData::default()),
2137 (program_id, create_loadable_account_for_test("noop")),
2138 ];
2139 with_mock_invoke_context!(invoke_context, transaction_context, accounts);
2140 assert_eq!(
2141 invoke_context.prepare_top_level_instructions(&message),
2142 Err((0, InstructionError::MaxAccountsExceeded)),
2143 );
2144 }
2145
2146 #[test]
2147 fn test_process_message_readonly_handling() {
2148 #[derive(wincode::SchemaRead, wincode::SchemaWrite)]
2149 enum MockSystemInstruction {
2150 Correct,
2151 TransferLamports { lamports: u64 },
2152 ChangeData { data: u8 },
2153 }
2154
2155 declare_process_instruction!(MockBuiltin, 1, |invoke_context| {
2156 let transaction_context = &invoke_context.transaction_context;
2157 let instruction_context = transaction_context.get_current_instruction_context()?;
2158 let instruction_data = instruction_context.get_instruction_data();
2159 if let Ok(instruction) = wincode::deserialize(instruction_data) {
2160 match instruction {
2161 MockSystemInstruction::Correct => Ok(()),
2162 MockSystemInstruction::TransferLamports { lamports } => {
2163 instruction_context
2164 .try_borrow_instruction_account(0)?
2165 .checked_sub_lamports(lamports)?;
2166 instruction_context
2167 .try_borrow_instruction_account(1)?
2168 .checked_add_lamports(lamports)?;
2169 Ok(())
2170 }
2171 MockSystemInstruction::ChangeData { data } => {
2172 instruction_context
2173 .try_borrow_instruction_account(1)?
2174 .set_data_from_slice(&[data])?;
2175 Ok(())
2176 }
2177 }
2178 } else {
2179 Err(InstructionError::InvalidInstructionData)
2180 }
2181 });
2182
2183 let writable_pubkey = Pubkey::new_unique();
2184 let readonly_pubkey = Pubkey::new_unique();
2185 let mock_system_program_id = Pubkey::new_unique();
2186
2187 let accounts = vec![
2188 (
2189 writable_pubkey,
2190 AccountSharedData::new(100, 1, &mock_system_program_id),
2191 ),
2192 (
2193 readonly_pubkey,
2194 AccountSharedData::new(0, 1, &mock_system_program_id),
2195 ),
2196 (
2197 mock_system_program_id,
2198 create_loadable_account_for_test("mock_system_program"),
2199 ),
2200 ];
2201 let mut transaction_context =
2202 TransactionContext::new(accounts.clone(), Rent::default(), 1, 3, 1);
2203 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
2204 program_cache_for_tx_batch.replenish(
2205 mock_system_program_id,
2206 Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
2207 );
2208 let account_keys = (0..transaction_context.get_number_of_accounts())
2209 .map(|index| {
2210 *transaction_context
2211 .get_key_of_account_at_index(index)
2212 .unwrap()
2213 })
2214 .collect::<Vec<_>>();
2215 let account_metas = vec![
2216 AccountMeta::new(writable_pubkey, true),
2217 AccountMeta::new_readonly(readonly_pubkey, false),
2218 ];
2219
2220 let message = new_sanitized_message(Message::new_with_compiled_instructions(
2221 1,
2222 0,
2223 2,
2224 account_keys.clone(),
2225 Hash::default(),
2226 AccountKeys::new(&account_keys, None).compile_instructions(&[
2227 Instruction::new_with_wincode(
2228 mock_system_program_id,
2229 &MockSystemInstruction::Correct,
2230 account_metas.clone(),
2231 ),
2232 ]),
2233 ));
2234 let sysvar_cache = SysvarCache::default();
2235 let feature_set = SVMFeatureSet::all_enabled();
2236 let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2237 let environment_config = EnvironmentConfig::new(
2238 Hash::default(),
2239 0,
2240 false,
2241 &MockCallback {},
2242 &feature_set,
2243 &program_runtime_environments,
2244 &sysvar_cache,
2245 );
2246 let mut invoke_context = InvokeContext::new(
2247 &mut transaction_context,
2248 &mut program_cache_for_tx_batch,
2249 environment_config,
2250 None,
2251 SVMTransactionExecutionBudget::default(),
2252 SVMTransactionExecutionCost::default(),
2253 );
2254 let result =
2255 invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2256 assert!(result.is_ok());
2257 assert_eq!(
2258 transaction_context
2259 .accounts()
2260 .try_borrow(0)
2261 .unwrap()
2262 .lamports(),
2263 100
2264 );
2265 assert_eq!(
2266 transaction_context
2267 .accounts()
2268 .try_borrow(1)
2269 .unwrap()
2270 .lamports(),
2271 0
2272 );
2273
2274 let message = new_sanitized_message(Message::new_with_compiled_instructions(
2275 1,
2276 0,
2277 2,
2278 account_keys.clone(),
2279 Hash::default(),
2280 AccountKeys::new(&account_keys, None).compile_instructions(&[
2281 Instruction::new_with_wincode(
2282 mock_system_program_id,
2283 &MockSystemInstruction::TransferLamports { lamports: 50 },
2284 account_metas.clone(),
2285 ),
2286 ]),
2287 ));
2288 let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2289 let environment_config = EnvironmentConfig::new(
2290 Hash::default(),
2291 0,
2292 false,
2293 &MockCallback {},
2294 &feature_set,
2295 &program_runtime_environments,
2296 &sysvar_cache,
2297 );
2298 let mut transaction_context =
2299 TransactionContext::new(accounts.clone(), Rent::default(), 1, 3, 1);
2300 let mut invoke_context = InvokeContext::new(
2301 &mut transaction_context,
2302 &mut program_cache_for_tx_batch,
2303 environment_config,
2304 None,
2305 SVMTransactionExecutionBudget::default(),
2306 SVMTransactionExecutionCost::default(),
2307 );
2308 let result =
2309 invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2310 assert_eq!(result, Err((0, InstructionError::ReadonlyLamportChange)));
2311
2312 let message = new_sanitized_message(Message::new_with_compiled_instructions(
2313 1,
2314 0,
2315 2,
2316 account_keys.clone(),
2317 Hash::default(),
2318 AccountKeys::new(&account_keys, None).compile_instructions(&[
2319 Instruction::new_with_wincode(
2320 mock_system_program_id,
2321 &MockSystemInstruction::ChangeData { data: 50 },
2322 account_metas,
2323 ),
2324 ]),
2325 ));
2326 let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2327 let environment_config = EnvironmentConfig::new(
2328 Hash::default(),
2329 0,
2330 false,
2331 &MockCallback {},
2332 &feature_set,
2333 &program_runtime_environments,
2334 &sysvar_cache,
2335 );
2336 let mut transaction_context = TransactionContext::new(accounts, Rent::default(), 1, 3, 1);
2337 let mut invoke_context = InvokeContext::new(
2338 &mut transaction_context,
2339 &mut program_cache_for_tx_batch,
2340 environment_config,
2341 None,
2342 SVMTransactionExecutionBudget::default(),
2343 SVMTransactionExecutionCost::default(),
2344 );
2345 let result =
2346 invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2347 assert_eq!(result, Err((0, InstructionError::ReadonlyDataModified)));
2348 }
2349
2350 #[test]
2351 fn test_process_message_duplicate_accounts() {
2352 #[derive(wincode::SchemaRead, wincode::SchemaWrite)]
2353 enum MockSystemInstruction {
2354 BorrowFail,
2355 MultiBorrowMut,
2356 DoWork { lamports: u64, data: u8 },
2357 }
2358
2359 declare_process_instruction!(MockBuiltin, 1, |invoke_context| {
2360 let transaction_context = &invoke_context.transaction_context;
2361 let instruction_context = transaction_context.get_current_instruction_context()?;
2362 let instruction_data = instruction_context.get_instruction_data();
2363 let mut to_account = instruction_context.try_borrow_instruction_account(1)?;
2364 if let Ok(instruction) = wincode::deserialize(instruction_data) {
2365 match instruction {
2366 MockSystemInstruction::BorrowFail => {
2367 let from_account = instruction_context.try_borrow_instruction_account(0)?;
2368 let dup_account = instruction_context.try_borrow_instruction_account(2)?;
2369 if from_account.get_lamports() != dup_account.get_lamports() {
2370 return Err(InstructionError::InvalidArgument);
2371 }
2372 Ok(())
2373 }
2374 MockSystemInstruction::MultiBorrowMut => {
2375 let lamports_a = instruction_context
2376 .try_borrow_instruction_account(0)?
2377 .get_lamports();
2378 let lamports_b = instruction_context
2379 .try_borrow_instruction_account(2)?
2380 .get_lamports();
2381 if lamports_a != lamports_b {
2382 return Err(InstructionError::InvalidArgument);
2383 }
2384 Ok(())
2385 }
2386 MockSystemInstruction::DoWork { lamports, data } => {
2387 let mut dup_account =
2388 instruction_context.try_borrow_instruction_account(2)?;
2389 dup_account.checked_sub_lamports(lamports)?;
2390 to_account.checked_add_lamports(lamports)?;
2391 dup_account.set_data_from_slice(&[data])?;
2392 drop(dup_account);
2393 let mut from_account =
2394 instruction_context.try_borrow_instruction_account(0)?;
2395 from_account.checked_sub_lamports(lamports)?;
2396 to_account.checked_add_lamports(lamports)?;
2397 Ok(())
2398 }
2399 }
2400 } else {
2401 Err(InstructionError::InvalidInstructionData)
2402 }
2403 });
2404 let mock_program_id = Pubkey::from([2u8; 32]);
2405 let accounts = vec![
2406 (
2407 solana_pubkey::new_rand(),
2408 AccountSharedData::new(100, 1, &mock_program_id),
2409 ),
2410 (
2411 solana_pubkey::new_rand(),
2412 AccountSharedData::new(0, 1, &mock_program_id),
2413 ),
2414 (
2415 mock_program_id,
2416 create_loadable_account_for_test("mock_system_program"),
2417 ),
2418 ];
2419 let mut transaction_context =
2420 TransactionContext::new(accounts.clone(), Rent::default(), 1, 3, 1);
2421 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
2422 program_cache_for_tx_batch.replenish(
2423 mock_program_id,
2424 Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
2425 );
2426 let account_metas = vec![
2427 AccountMeta::new(
2428 *transaction_context.get_key_of_account_at_index(0).unwrap(),
2429 true,
2430 ),
2431 AccountMeta::new(
2432 *transaction_context.get_key_of_account_at_index(1).unwrap(),
2433 false,
2434 ),
2435 AccountMeta::new(
2436 *transaction_context.get_key_of_account_at_index(0).unwrap(),
2437 false,
2438 ),
2439 ];
2440
2441 let message = new_sanitized_message(Message::new(
2443 &[Instruction::new_with_wincode(
2444 mock_program_id,
2445 &MockSystemInstruction::BorrowFail,
2446 account_metas.clone(),
2447 )],
2448 Some(transaction_context.get_key_of_account_at_index(0).unwrap()),
2449 ));
2450 let sysvar_cache = SysvarCache::default();
2451 let feature_set = SVMFeatureSet::all_enabled();
2452 let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2453 let environment_config = EnvironmentConfig::new(
2454 Hash::default(),
2455 0,
2456 false,
2457 &MockCallback {},
2458 &feature_set,
2459 &program_runtime_environments,
2460 &sysvar_cache,
2461 );
2462 let mut invoke_context = InvokeContext::new(
2463 &mut transaction_context,
2464 &mut program_cache_for_tx_batch,
2465 environment_config,
2466 None,
2467 SVMTransactionExecutionBudget::default(),
2468 SVMTransactionExecutionCost::default(),
2469 );
2470 let result =
2471 invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2472 assert_eq!(result, Err((0, InstructionError::AccountBorrowFailed)));
2473
2474 let message = new_sanitized_message(Message::new(
2476 &[Instruction::new_with_wincode(
2477 mock_program_id,
2478 &MockSystemInstruction::MultiBorrowMut,
2479 account_metas.clone(),
2480 )],
2481 Some(transaction_context.get_key_of_account_at_index(0).unwrap()),
2482 ));
2483 let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2484 let environment_config = EnvironmentConfig::new(
2485 Hash::default(),
2486 0,
2487 false,
2488 &MockCallback {},
2489 &feature_set,
2490 &program_runtime_environments,
2491 &sysvar_cache,
2492 );
2493 let mut transaction_context =
2494 TransactionContext::new(accounts.clone(), Rent::default(), 1, 3, 1);
2495 let mut invoke_context = InvokeContext::new(
2496 &mut transaction_context,
2497 &mut program_cache_for_tx_batch,
2498 environment_config,
2499 None,
2500 SVMTransactionExecutionBudget::default(),
2501 SVMTransactionExecutionCost::default(),
2502 );
2503 let result =
2504 invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2505 assert!(result.is_ok());
2506
2507 let message = new_sanitized_message(Message::new(
2509 &[Instruction::new_with_wincode(
2510 mock_program_id,
2511 &MockSystemInstruction::DoWork {
2512 lamports: 10,
2513 data: 42,
2514 },
2515 account_metas,
2516 )],
2517 Some(transaction_context.get_key_of_account_at_index(0).unwrap()),
2518 ));
2519 let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2520 let environment_config = EnvironmentConfig::new(
2521 Hash::default(),
2522 0,
2523 false,
2524 &MockCallback {},
2525 &feature_set,
2526 &program_runtime_environments,
2527 &sysvar_cache,
2528 );
2529 let mut transaction_context = TransactionContext::new(accounts, Rent::default(), 1, 3, 1);
2530 let mut invoke_context = InvokeContext::new(
2531 &mut transaction_context,
2532 &mut program_cache_for_tx_batch,
2533 environment_config,
2534 None,
2535 SVMTransactionExecutionBudget::default(),
2536 SVMTransactionExecutionCost::default(),
2537 );
2538 let result =
2539 invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2540 assert!(result.is_ok());
2541 assert_eq!(
2542 transaction_context
2543 .accounts()
2544 .try_borrow(0)
2545 .unwrap()
2546 .lamports(),
2547 80
2548 );
2549 assert_eq!(
2550 transaction_context
2551 .accounts()
2552 .try_borrow(1)
2553 .unwrap()
2554 .lamports(),
2555 20
2556 );
2557 assert_eq!(
2558 transaction_context.accounts().try_borrow(0).unwrap().data(),
2559 &vec![42]
2560 );
2561 }
2562
2563 fn secp256k1_instruction_for_test() -> Instruction {
2564 let message = b"hello";
2565 let bytes: [u8; 32] = rand::random();
2566 let secret_key = libsecp256k1::SecretKey::parse(&bytes).unwrap();
2567 let pubkey = libsecp256k1::PublicKey::from_secret_key(&secret_key);
2568 let eth_address = eth_address_from_pubkey(&pubkey.serialize()[1..].try_into().unwrap());
2569 let (signature, recovery_id) =
2570 solana_secp256k1_program::sign_message(&secret_key.serialize(), &message[..]).unwrap();
2571 new_secp256k1_instruction_with_signature(
2572 &message[..],
2573 &signature,
2574 recovery_id,
2575 ð_address,
2576 )
2577 }
2578
2579 fn ed25519_instruction_for_test() -> Instruction {
2580 let keypair = Keypair::new();
2581 let signature = keypair.sign_message(b"hello");
2582 let pubkey = keypair.pubkey().to_bytes();
2583 new_ed25519_instruction_with_signature(b"hello", signature.as_array(), &pubkey)
2584 }
2585
2586 fn secp256r1_instruction_for_test() -> Instruction {
2587 let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap();
2588 let secret_key = EcKey::generate(&group).unwrap();
2589 let signature = sign_message(b"hello", &secret_key.private_key_to_der().unwrap()).unwrap();
2590 let mut ctx = openssl::bn::BigNumContext::new().unwrap();
2591 let pubkey = secret_key
2592 .public_key()
2593 .to_bytes(
2594 &group,
2595 openssl::ec::PointConversionForm::COMPRESSED,
2596 &mut ctx,
2597 )
2598 .unwrap();
2599 new_secp256r1_instruction_with_signature(b"hello", &signature, &pubkey.try_into().unwrap())
2600 }
2601
2602 #[test]
2603 fn test_precompile() {
2604 let mock_program_id = Pubkey::new_unique();
2605 declare_process_instruction!(MockBuiltin, 1, |_invoke_context| {
2606 Err(InstructionError::Custom(0xbabb1e))
2607 });
2608
2609 let mut secp256k1_account = AccountSharedData::new(1, 0, &native_loader::id());
2610 secp256k1_account.set_executable(true);
2611 let mut ed25519_account = AccountSharedData::new(1, 0, &native_loader::id());
2612 ed25519_account.set_executable(true);
2613 let mut secp256r1_account = AccountSharedData::new(1, 0, &native_loader::id());
2614 secp256r1_account.set_executable(true);
2615 let mut mock_program_account = AccountSharedData::new(1, 0, &native_loader::id());
2616 mock_program_account.set_executable(true);
2617
2618 let fee_payer = Pubkey::new_unique();
2619 let accounts_map: HashMap<Address, AccountSharedData> = HashMap::from([
2620 (
2621 fee_payer,
2622 AccountSharedData::new(1, 0, &system_program::id()),
2623 ),
2624 (secp256k1_program::id(), secp256k1_account),
2625 (ed25519_program::id(), ed25519_account),
2626 (solana_secp256r1_program::id(), secp256r1_account),
2627 (mock_program_id, mock_program_account),
2628 ]);
2629
2630 let message = new_sanitized_message(Message::new(
2631 &[
2632 secp256k1_instruction_for_test(),
2633 ed25519_instruction_for_test(),
2634 secp256r1_instruction_for_test(),
2635 Instruction::new_with_bytes(mock_program_id, &[], vec![]),
2636 ],
2637 Some(&fee_payer),
2638 ));
2639
2640 let accounts = message
2641 .account_keys()
2642 .iter()
2643 .map(|key| (*key, accounts_map.get(key).unwrap().clone()))
2644 .collect();
2645 let mut transaction_context = TransactionContext::new(accounts, Rent::default(), 1, 4, 4);
2646
2647 let sysvar_cache = SysvarCache::default();
2648 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
2649 program_cache_for_tx_batch.replenish(
2650 mock_program_id,
2651 Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
2652 );
2653
2654 struct MockCallback {}
2655 impl InvokeContextCallback for MockCallback {
2656 fn is_precompile(&self, program_id: &Pubkey) -> bool {
2657 program_id == &secp256k1_program::id()
2658 || program_id == &ed25519_program::id()
2659 || program_id == &solana_secp256r1_program::id()
2660 }
2661
2662 fn process_precompile(
2663 &self,
2664 program_id: &Pubkey,
2665 _data: &[u8],
2666 _instruction_datas: Vec<&[u8]>,
2667 ) -> std::result::Result<(), PrecompileError> {
2668 if self.is_precompile(program_id) {
2669 Ok(())
2670 } else {
2671 Err(PrecompileError::InvalidPublicKey)
2672 }
2673 }
2674 }
2675 let feature_set = SVMFeatureSet::all_enabled();
2676 let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2677 let environment_config = EnvironmentConfig::new(
2678 Hash::default(),
2679 0,
2680 false,
2681 &MockCallback {},
2682 &feature_set,
2683 &program_runtime_environments,
2684 &sysvar_cache,
2685 );
2686 let mut invoke_context = InvokeContext::new(
2687 &mut transaction_context,
2688 &mut program_cache_for_tx_batch,
2689 environment_config,
2690 None,
2691 SVMTransactionExecutionBudget::default(),
2692 SVMTransactionExecutionCost::default(),
2693 );
2694 let result =
2695 invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2696
2697 assert_eq!(result, Err((3, InstructionError::Custom(0xbabb1e))));
2698 assert_eq!(
2699 transaction_context.number_of_called_instructions_in_trace(),
2700 4
2701 );
2702 }
2703}