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