1use {
2 crate::{
3 execution_budget::{SVMTransactionExecutionBudget, SVMTransactionExecutionCost},
4 loaded_programs::{
5 ProgramCacheEntry, ProgramCacheEntryType, ProgramCacheForTxBatch,
6 ProgramRuntimeEnvironments,
7 },
8 stable_log,
9 sysvar_cache::SysvarCache,
10 },
11 solana_account::{create_account_shared_data_for_test, AccountSharedData},
12 solana_clock::Slot,
13 solana_epoch_schedule::EpochSchedule,
14 solana_hash::Hash,
15 solana_instruction::{error::InstructionError, AccountMeta},
16 solana_log_collector::{ic_msg, LogCollector},
17 solana_measure::measure::Measure,
18 solana_pubkey::Pubkey,
19 solana_sbpf::{
20 ebpf::MM_HEAP_START,
21 error::{EbpfError, ProgramResult},
22 memory_region::MemoryMapping,
23 program::{BuiltinFunction, SBPFVersion},
24 vm::{Config, ContextObject, EbpfVm},
25 },
26 solana_sdk_ids::{
27 bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, loader_v4, native_loader, sysvar,
28 },
29 solana_stable_layout::stable_instruction::StableInstruction,
30 solana_svm_callback::InvokeContextCallback,
31 solana_svm_feature_set::SVMFeatureSet,
32 solana_timings::{ExecuteDetailsTimings, ExecuteTimings},
33 solana_transaction_context::{
34 IndexOfAccount, InstructionAccount, TransactionAccount, TransactionContext,
35 },
36 solana_type_overrides::sync::{atomic::Ordering, Arc},
37 std::{
38 alloc::Layout,
39 cell::RefCell,
40 fmt::{self, Debug},
41 rc::Rc,
42 },
43};
44
45pub type BuiltinFunctionWithContext = BuiltinFunction<InvokeContext<'static>>;
46
47#[macro_export]
49macro_rules! declare_process_instruction {
50 ($process_instruction:ident, $cu_to_consume:expr, |$invoke_context:ident| $inner:tt) => {
51 $crate::solana_sbpf::declare_builtin_function!(
52 $process_instruction,
53 fn rust(
54 invoke_context: &mut $crate::invoke_context::InvokeContext,
55 _arg0: u64,
56 _arg1: u64,
57 _arg2: u64,
58 _arg3: u64,
59 _arg4: u64,
60 _memory_mapping: &mut $crate::solana_sbpf::memory_region::MemoryMapping,
61 ) -> std::result::Result<u64, Box<dyn std::error::Error>> {
62 fn process_instruction_inner(
63 $invoke_context: &mut $crate::invoke_context::InvokeContext,
64 ) -> std::result::Result<(), $crate::__private::InstructionError>
65 $inner
66
67 let consumption_result = if $cu_to_consume > 0
68 {
69 invoke_context.consume_checked($cu_to_consume)
70 } else {
71 Ok(())
72 };
73 consumption_result
74 .and_then(|_| {
75 process_instruction_inner(invoke_context)
76 .map(|_| 0)
77 .map_err(|err| Box::new(err) as Box<dyn std::error::Error>)
78 })
79 .into()
80 }
81 );
82 };
83}
84
85impl ContextObject for InvokeContext<'_> {
86 fn trace(&mut self, state: [u64; 12]) {
87 self.syscall_context
88 .last_mut()
89 .unwrap()
90 .as_mut()
91 .unwrap()
92 .trace_log
93 .push(state);
94 }
95
96 fn consume(&mut self, amount: u64) {
97 let mut compute_meter = self.compute_meter.borrow_mut();
100 *compute_meter = compute_meter.saturating_sub(amount);
101 }
102
103 fn get_remaining(&self) -> u64 {
104 *self.compute_meter.borrow()
105 }
106}
107
108#[derive(Clone, PartialEq, Eq, Debug)]
109pub struct AllocErr;
110impl fmt::Display for AllocErr {
111 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
112 f.write_str("Error: Memory allocation failed")
113 }
114}
115
116pub struct BpfAllocator {
117 len: u64,
118 pos: u64,
119}
120
121impl BpfAllocator {
122 pub fn new(len: u64) -> Self {
123 Self { len, pos: 0 }
124 }
125
126 pub fn alloc(&mut self, layout: Layout) -> Result<u64, AllocErr> {
127 let bytes_to_align = (self.pos as *const u8).align_offset(layout.align()) as u64;
128 if self
129 .pos
130 .saturating_add(bytes_to_align)
131 .saturating_add(layout.size() as u64)
132 <= self.len
133 {
134 self.pos = self.pos.saturating_add(bytes_to_align);
135 let addr = MM_HEAP_START.saturating_add(self.pos);
136 self.pos = self.pos.saturating_add(layout.size() as u64);
137 Ok(addr)
138 } else {
139 Err(AllocErr)
140 }
141 }
142}
143
144pub struct EnvironmentConfig<'a> {
145 pub blockhash: Hash,
146 pub blockhash_lamports_per_signature: u64,
147 epoch_stake_callback: &'a dyn InvokeContextCallback,
148 feature_set: &'a SVMFeatureSet,
149 sysvar_cache: &'a SysvarCache,
150}
151impl<'a> EnvironmentConfig<'a> {
152 pub fn new(
153 blockhash: Hash,
154 blockhash_lamports_per_signature: u64,
155 epoch_stake_callback: &'a dyn InvokeContextCallback,
156 feature_set: &'a SVMFeatureSet,
157 sysvar_cache: &'a SysvarCache,
158 ) -> Self {
159 Self {
160 blockhash,
161 blockhash_lamports_per_signature,
162 epoch_stake_callback,
163 feature_set,
164 sysvar_cache,
165 }
166 }
167}
168
169pub struct SyscallContext {
170 pub allocator: BpfAllocator,
171 pub accounts_metadata: Vec<SerializedAccountMetadata>,
172 pub trace_log: Vec<[u64; 12]>,
173}
174
175#[derive(Debug, Clone)]
176pub struct SerializedAccountMetadata {
177 pub original_data_len: usize,
178 pub vm_data_addr: u64,
179 pub vm_key_addr: u64,
180 pub vm_lamports_addr: u64,
181 pub vm_owner_addr: u64,
182}
183
184pub struct InvokeContext<'a> {
186 pub transaction_context: &'a mut TransactionContext,
188 pub program_cache_for_tx_batch: &'a mut ProgramCacheForTxBatch,
190 pub environment_config: EnvironmentConfig<'a>,
192 compute_budget: SVMTransactionExecutionBudget,
194 execution_cost: SVMTransactionExecutionCost,
196 compute_meter: RefCell<u64>,
199 log_collector: Option<Rc<RefCell<LogCollector>>>,
200 pub execute_time: Option<Measure>,
202 pub timings: ExecuteDetailsTimings,
203 pub syscall_context: Vec<Option<SyscallContext>>,
204 traces: Vec<Vec<[u64; 12]>>,
205}
206
207impl<'a> InvokeContext<'a> {
208 #[allow(clippy::too_many_arguments)]
209 pub fn new(
210 transaction_context: &'a mut TransactionContext,
211 program_cache_for_tx_batch: &'a mut ProgramCacheForTxBatch,
212 environment_config: EnvironmentConfig<'a>,
213 log_collector: Option<Rc<RefCell<LogCollector>>>,
214 compute_budget: SVMTransactionExecutionBudget,
215 execution_cost: SVMTransactionExecutionCost,
216 ) -> Self {
217 Self {
218 transaction_context,
219 program_cache_for_tx_batch,
220 environment_config,
221 log_collector,
222 compute_budget,
223 execution_cost,
224 compute_meter: RefCell::new(compute_budget.compute_unit_limit),
225 execute_time: None,
226 timings: ExecuteDetailsTimings::default(),
227 syscall_context: Vec::new(),
228 traces: Vec::new(),
229 }
230 }
231
232 pub fn get_environments_for_slot(
233 &self,
234 effective_slot: Slot,
235 ) -> Result<&ProgramRuntimeEnvironments, InstructionError> {
236 let epoch_schedule = self.environment_config.sysvar_cache.get_epoch_schedule()?;
237 let epoch = epoch_schedule.get_epoch(effective_slot);
238 Ok(self
239 .program_cache_for_tx_batch
240 .get_environments_for_epoch(epoch))
241 }
242
243 pub fn push(&mut self) -> Result<(), InstructionError> {
245 let instruction_context = self
246 .transaction_context
247 .get_instruction_context_at_index_in_trace(
248 self.transaction_context.get_instruction_trace_length(),
249 )?;
250 let program_id = instruction_context
251 .get_last_program_key(self.transaction_context)
252 .map_err(|_| InstructionError::UnsupportedProgramId)?;
253 if self
254 .transaction_context
255 .get_instruction_context_stack_height()
256 != 0
257 {
258 let contains = (0..self
259 .transaction_context
260 .get_instruction_context_stack_height())
261 .any(|level| {
262 self.transaction_context
263 .get_instruction_context_at_nesting_level(level)
264 .and_then(|instruction_context| {
265 instruction_context
266 .try_borrow_last_program_account(self.transaction_context)
267 })
268 .map(|program_account| program_account.get_key() == program_id)
269 .unwrap_or(false)
270 });
271 let is_last = self
272 .transaction_context
273 .get_current_instruction_context()
274 .and_then(|instruction_context| {
275 instruction_context.try_borrow_last_program_account(self.transaction_context)
276 })
277 .map(|program_account| program_account.get_key() == program_id)
278 .unwrap_or(false);
279 if contains && !is_last {
280 return Err(InstructionError::ReentrancyNotAllowed);
282 }
283 }
284
285 self.syscall_context.push(None);
286 self.transaction_context.push()
287 }
288
289 fn pop(&mut self) -> Result<(), InstructionError> {
291 if let Some(Some(syscall_context)) = self.syscall_context.pop() {
292 self.traces.push(syscall_context.trace_log);
293 }
294 self.transaction_context.pop()
295 }
296
297 pub fn get_stack_height(&self) -> usize {
300 self.transaction_context
301 .get_instruction_context_stack_height()
302 }
303
304 pub fn native_invoke(
306 &mut self,
307 instruction: StableInstruction,
308 signers: &[Pubkey],
309 ) -> Result<(), InstructionError> {
310 let (instruction_accounts, program_indices) =
311 self.prepare_instruction(&instruction, signers)?;
312 let mut compute_units_consumed = 0;
313 self.process_instruction(
314 &instruction.data,
315 &instruction_accounts,
316 &program_indices,
317 &mut compute_units_consumed,
318 &mut ExecuteTimings::default(),
319 )?;
320 Ok(())
321 }
322
323 #[allow(clippy::type_complexity)]
325 pub fn prepare_instruction(
326 &mut self,
327 instruction: &StableInstruction,
328 signers: &[Pubkey],
329 ) -> Result<(Vec<InstructionAccount>, Vec<IndexOfAccount>), InstructionError> {
330 let instruction_context = self.transaction_context.get_current_instruction_context()?;
335 let mut deduplicated_instruction_accounts: Vec<InstructionAccount> = Vec::new();
336 let mut duplicate_indicies = Vec::with_capacity(instruction.accounts.len() as usize);
337 for (instruction_account_index, account_meta) in instruction.accounts.iter().enumerate() {
338 let index_in_transaction = self
339 .transaction_context
340 .find_index_of_account(&account_meta.pubkey)
341 .ok_or_else(|| {
342 ic_msg!(
343 self,
344 "Instruction references an unknown account {}",
345 account_meta.pubkey,
346 );
347 InstructionError::MissingAccount
348 })?;
349 if let Some(duplicate_index) =
350 deduplicated_instruction_accounts
351 .iter()
352 .position(|instruction_account| {
353 instruction_account.index_in_transaction == index_in_transaction
354 })
355 {
356 duplicate_indicies.push(duplicate_index);
357 let instruction_account = deduplicated_instruction_accounts
358 .get_mut(duplicate_index)
359 .ok_or(InstructionError::NotEnoughAccountKeys)?;
360 instruction_account.is_signer |= account_meta.is_signer;
361 instruction_account.is_writable |= account_meta.is_writable;
362 } else {
363 let index_in_caller = instruction_context
364 .find_index_of_instruction_account(
365 self.transaction_context,
366 &account_meta.pubkey,
367 )
368 .ok_or_else(|| {
369 ic_msg!(
370 self,
371 "Instruction references an unknown account {}",
372 account_meta.pubkey,
373 );
374 InstructionError::MissingAccount
375 })?;
376 duplicate_indicies.push(deduplicated_instruction_accounts.len());
377 deduplicated_instruction_accounts.push(InstructionAccount {
378 index_in_transaction,
379 index_in_caller,
380 index_in_callee: instruction_account_index as IndexOfAccount,
381 is_signer: account_meta.is_signer,
382 is_writable: account_meta.is_writable,
383 });
384 }
385 }
386 for instruction_account in deduplicated_instruction_accounts.iter() {
387 let borrowed_account = instruction_context.try_borrow_instruction_account(
388 self.transaction_context,
389 instruction_account.index_in_caller,
390 )?;
391
392 if instruction_account.is_writable && !borrowed_account.is_writable() {
394 ic_msg!(
395 self,
396 "{}'s writable privilege escalated",
397 borrowed_account.get_key(),
398 );
399 return Err(InstructionError::PrivilegeEscalation);
400 }
401
402 if instruction_account.is_signer
405 && !(borrowed_account.is_signer() || signers.contains(borrowed_account.get_key()))
406 {
407 ic_msg!(
408 self,
409 "{}'s signer privilege escalated",
410 borrowed_account.get_key()
411 );
412 return Err(InstructionError::PrivilegeEscalation);
413 }
414 }
415 let instruction_accounts = duplicate_indicies
416 .into_iter()
417 .map(|duplicate_index| {
418 deduplicated_instruction_accounts
419 .get(duplicate_index)
420 .cloned()
421 .ok_or(InstructionError::NotEnoughAccountKeys)
422 })
423 .collect::<Result<Vec<InstructionAccount>, InstructionError>>()?;
424
425 let callee_program_id = instruction.program_id;
427 let program_account_index = if self.get_feature_set().lift_cpi_caller_restriction {
428 self.transaction_context
429 .find_index_of_program_account(&callee_program_id)
430 .ok_or_else(|| {
431 ic_msg!(self, "Unknown program {}", callee_program_id);
432 InstructionError::MissingAccount
433 })?
434 } else {
435 let program_account_index = instruction_context
436 .find_index_of_instruction_account(self.transaction_context, &callee_program_id)
437 .ok_or_else(|| {
438 ic_msg!(self, "Unknown program {}", callee_program_id);
439 InstructionError::MissingAccount
440 })?;
441 let borrowed_program_account = instruction_context
442 .try_borrow_instruction_account(self.transaction_context, program_account_index)?;
443 #[allow(deprecated)]
444 if !self
445 .get_feature_set()
446 .remove_accounts_executable_flag_checks
447 && !borrowed_program_account.is_executable()
448 {
449 ic_msg!(self, "Account {} is not executable", callee_program_id);
450 return Err(InstructionError::AccountNotExecutable);
451 }
452 borrowed_program_account.get_index_in_transaction()
453 };
454
455 Ok((instruction_accounts, vec![program_account_index]))
456 }
457
458 pub fn process_instruction(
460 &mut self,
461 instruction_data: &[u8],
462 instruction_accounts: &[InstructionAccount],
463 program_indices: &[IndexOfAccount],
464 compute_units_consumed: &mut u64,
465 timings: &mut ExecuteTimings,
466 ) -> Result<(), InstructionError> {
467 *compute_units_consumed = 0;
468 self.transaction_context
469 .get_next_instruction_context()?
470 .configure(program_indices, instruction_accounts, instruction_data);
471 self.push()?;
472 self.process_executable_chain(compute_units_consumed, timings)
473 .and(self.pop())
476 }
477
478 pub fn process_precompile<'ix_data>(
480 &mut self,
481 program_id: &Pubkey,
482 instruction_data: &[u8],
483 instruction_accounts: &[InstructionAccount],
484 program_indices: &[IndexOfAccount],
485 message_instruction_datas_iter: impl Iterator<Item = &'ix_data [u8]>,
486 ) -> Result<(), InstructionError> {
487 self.transaction_context
488 .get_next_instruction_context()?
489 .configure(program_indices, instruction_accounts, instruction_data);
490 self.push()?;
491
492 let instruction_datas: Vec<_> = message_instruction_datas_iter.collect();
493 self.environment_config
494 .epoch_stake_callback
495 .process_precompile(program_id, instruction_data, instruction_datas)
496 .map_err(InstructionError::from)
497 .and(self.pop())
498 }
499
500 fn process_executable_chain(
502 &mut self,
503 compute_units_consumed: &mut u64,
504 timings: &mut ExecuteTimings,
505 ) -> Result<(), InstructionError> {
506 let instruction_context = self.transaction_context.get_current_instruction_context()?;
507 let process_executable_chain_time = Measure::start("process_executable_chain_time");
508
509 let builtin_id = {
510 debug_assert!(instruction_context.get_number_of_program_accounts() <= 1);
511 let borrowed_root_account = instruction_context
512 .try_borrow_program_account(self.transaction_context, 0)
513 .map_err(|_| InstructionError::UnsupportedProgramId)?;
514 let owner_id = borrowed_root_account.get_owner();
515 if native_loader::check_id(owner_id) {
516 *borrowed_root_account.get_key()
517 } else if self
518 .get_feature_set()
519 .remove_accounts_executable_flag_checks
520 {
521 if bpf_loader_deprecated::check_id(owner_id)
522 || bpf_loader::check_id(owner_id)
523 || bpf_loader_upgradeable::check_id(owner_id)
524 || loader_v4::check_id(owner_id)
525 {
526 *owner_id
527 } else {
528 return Err(InstructionError::UnsupportedProgramId);
529 }
530 } else {
531 *owner_id
532 }
533 };
534
535 const ENTRYPOINT_KEY: u32 = 0x71E3CF81;
537 let entry = self
538 .program_cache_for_tx_batch
539 .find(&builtin_id)
540 .ok_or(InstructionError::UnsupportedProgramId)?;
541 let function = match &entry.program {
542 ProgramCacheEntryType::Builtin(program) => program
543 .get_function_registry()
544 .lookup_by_key(ENTRYPOINT_KEY)
545 .map(|(_name, function)| function),
546 _ => None,
547 }
548 .ok_or(InstructionError::UnsupportedProgramId)?;
549 entry.ix_usage_counter.fetch_add(1, Ordering::Relaxed);
550
551 let program_id = *instruction_context.get_last_program_key(self.transaction_context)?;
552 self.transaction_context
553 .set_return_data(program_id, Vec::new())?;
554 let logger = self.get_log_collector();
555 stable_log::program_invoke(&logger, &program_id, self.get_stack_height());
556 let pre_remaining_units = self.get_remaining();
557 let mock_config = Config::default();
561 let empty_memory_mapping =
562 MemoryMapping::new(Vec::new(), &mock_config, SBPFVersion::V0).unwrap();
563 let mut vm = EbpfVm::new(
564 self.program_cache_for_tx_batch
565 .environments
566 .program_runtime_v2
567 .clone(),
568 SBPFVersion::V0,
569 unsafe { std::mem::transmute::<&mut InvokeContext, &mut InvokeContext>(self) },
571 empty_memory_mapping,
572 0,
573 );
574 vm.invoke_function(function);
575 let result = match vm.program_result {
576 ProgramResult::Ok(_) => {
577 stable_log::program_success(&logger, &program_id);
578 Ok(())
579 }
580 ProgramResult::Err(ref err) => {
581 if let EbpfError::SyscallError(syscall_error) = err {
582 if let Some(instruction_err) = syscall_error.downcast_ref::<InstructionError>()
583 {
584 stable_log::program_failure(&logger, &program_id, instruction_err);
585 Err(instruction_err.clone())
586 } else {
587 stable_log::program_failure(&logger, &program_id, syscall_error);
588 Err(InstructionError::ProgramFailedToComplete)
589 }
590 } else {
591 stable_log::program_failure(&logger, &program_id, err);
592 Err(InstructionError::ProgramFailedToComplete)
593 }
594 }
595 };
596 let post_remaining_units = self.get_remaining();
597 *compute_units_consumed = pre_remaining_units.saturating_sub(post_remaining_units);
598
599 if builtin_id == program_id && result.is_ok() && *compute_units_consumed == 0 {
600 return Err(InstructionError::BuiltinProgramsMustConsumeComputeUnits);
601 }
602
603 timings
604 .execute_accessories
605 .process_instructions
606 .process_executable_chain_us += process_executable_chain_time.end_as_us();
607 result
608 }
609
610 pub fn get_log_collector(&self) -> Option<Rc<RefCell<LogCollector>>> {
612 self.log_collector.clone()
613 }
614
615 pub fn consume_checked(&self, amount: u64) -> Result<(), Box<dyn std::error::Error>> {
617 let mut compute_meter = self.compute_meter.borrow_mut();
618 let exceeded = *compute_meter < amount;
619 *compute_meter = compute_meter.saturating_sub(amount);
620 if exceeded {
621 return Err(Box::new(InstructionError::ComputationalBudgetExceeded));
622 }
623 Ok(())
624 }
625
626 pub fn mock_set_remaining(&self, remaining: u64) {
630 *self.compute_meter.borrow_mut() = remaining;
631 }
632
633 pub fn get_compute_budget(&self) -> &SVMTransactionExecutionBudget {
635 &self.compute_budget
636 }
637
638 pub fn get_execution_cost(&self) -> &SVMTransactionExecutionCost {
640 &self.execution_cost
641 }
642
643 pub fn get_feature_set(&self) -> &SVMFeatureSet {
645 self.environment_config.feature_set
646 }
647
648 pub fn is_stake_raise_minimum_delegation_to_1_sol_active(&self) -> bool {
649 self.environment_config
650 .feature_set
651 .stake_raise_minimum_delegation_to_1_sol
652 }
653
654 pub fn is_deprecate_legacy_vote_ixs_active(&self) -> bool {
655 self.environment_config
656 .feature_set
657 .deprecate_legacy_vote_ixs
658 }
659
660 pub fn get_sysvar_cache(&self) -> &SysvarCache {
662 self.environment_config.sysvar_cache
663 }
664
665 pub fn get_epoch_stake(&self) -> u64 {
667 self.environment_config
668 .epoch_stake_callback
669 .get_epoch_stake()
670 }
671
672 pub fn get_epoch_stake_for_vote_account(&self, pubkey: &'a Pubkey) -> u64 {
674 self.environment_config
675 .epoch_stake_callback
676 .get_epoch_stake_for_vote_account(pubkey)
677 }
678
679 pub fn is_precompile(&self, pubkey: &Pubkey) -> bool {
680 self.environment_config
681 .epoch_stake_callback
682 .is_precompile(pubkey)
683 }
684
685 pub fn get_check_aligned(&self) -> bool {
687 self.transaction_context
688 .get_current_instruction_context()
689 .and_then(|instruction_context| {
690 let program_account =
691 instruction_context.try_borrow_last_program_account(self.transaction_context);
692 debug_assert!(program_account.is_ok());
693 program_account
694 })
695 .map(|program_account| *program_account.get_owner() != bpf_loader_deprecated::id())
696 .unwrap_or(true)
697 }
698
699 pub fn set_syscall_context(
701 &mut self,
702 syscall_context: SyscallContext,
703 ) -> Result<(), InstructionError> {
704 *self
705 .syscall_context
706 .last_mut()
707 .ok_or(InstructionError::CallDepth)? = Some(syscall_context);
708 Ok(())
709 }
710
711 pub fn get_syscall_context(&self) -> Result<&SyscallContext, InstructionError> {
713 self.syscall_context
714 .last()
715 .and_then(std::option::Option::as_ref)
716 .ok_or(InstructionError::CallDepth)
717 }
718
719 pub fn get_syscall_context_mut(&mut self) -> Result<&mut SyscallContext, InstructionError> {
721 self.syscall_context
722 .last_mut()
723 .and_then(|syscall_context| syscall_context.as_mut())
724 .ok_or(InstructionError::CallDepth)
725 }
726
727 pub fn get_traces(&self) -> &Vec<Vec<[u64; 12]>> {
729 &self.traces
730 }
731}
732
733#[macro_export]
734macro_rules! with_mock_invoke_context_with_feature_set {
735 (
736 $invoke_context:ident,
737 $transaction_context:ident,
738 $feature_set:ident,
739 $transaction_accounts:expr $(,)?
740 ) => {
741 use {
742 solana_log_collector::LogCollector,
743 solana_svm_callback::InvokeContextCallback,
744 $crate::{
745 __private::{Hash, ReadableAccount, Rent, TransactionContext},
746 execution_budget::{SVMTransactionExecutionBudget, SVMTransactionExecutionCost},
747 invoke_context::{EnvironmentConfig, InvokeContext},
748 loaded_programs::ProgramCacheForTxBatch,
749 sysvar_cache::SysvarCache,
750 },
751 };
752
753 struct MockInvokeContextCallback {}
754 impl InvokeContextCallback for MockInvokeContextCallback {}
755
756 let compute_budget = SVMTransactionExecutionBudget::default();
757 let mut $transaction_context = TransactionContext::new(
758 $transaction_accounts,
759 Rent::default(),
760 compute_budget.max_instruction_stack_depth,
761 compute_budget.max_instruction_trace_length,
762 );
763 let mut sysvar_cache = SysvarCache::default();
764 sysvar_cache.fill_missing_entries(|pubkey, callback| {
765 for index in 0..$transaction_context.get_number_of_accounts() {
766 if $transaction_context
767 .get_key_of_account_at_index(index)
768 .unwrap()
769 == pubkey
770 {
771 callback(
772 $transaction_context
773 .accounts()
774 .try_borrow(index)
775 .unwrap()
776 .data(),
777 );
778 }
779 }
780 });
781 let environment_config = EnvironmentConfig::new(
782 Hash::default(),
783 0,
784 &MockInvokeContextCallback {},
785 $feature_set,
786 &sysvar_cache,
787 );
788 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
789 let mut $invoke_context = InvokeContext::new(
790 &mut $transaction_context,
791 &mut program_cache_for_tx_batch,
792 environment_config,
793 Some(LogCollector::new_ref()),
794 compute_budget,
795 SVMTransactionExecutionCost::default(),
796 );
797 };
798}
799
800#[macro_export]
801macro_rules! with_mock_invoke_context {
802 (
803 $invoke_context:ident,
804 $transaction_context:ident,
805 $transaction_accounts:expr $(,)?
806 ) => {
807 use $crate::with_mock_invoke_context_with_feature_set;
808 let feature_set = &solana_svm_feature_set::SVMFeatureSet::default();
809 with_mock_invoke_context_with_feature_set!(
810 $invoke_context,
811 $transaction_context,
812 feature_set,
813 $transaction_accounts
814 )
815 };
816}
817
818#[allow(clippy::too_many_arguments)]
819pub fn mock_process_instruction_with_feature_set<
820 F: FnMut(&mut InvokeContext),
821 G: FnMut(&mut InvokeContext),
822>(
823 loader_id: &Pubkey,
824 mut program_indices: Vec<IndexOfAccount>,
825 instruction_data: &[u8],
826 mut transaction_accounts: Vec<TransactionAccount>,
827 instruction_account_metas: Vec<AccountMeta>,
828 expected_result: Result<(), InstructionError>,
829 builtin_function: BuiltinFunctionWithContext,
830 mut pre_adjustments: F,
831 mut post_adjustments: G,
832 feature_set: &SVMFeatureSet,
833) -> Vec<AccountSharedData> {
834 let mut instruction_accounts: Vec<InstructionAccount> =
835 Vec::with_capacity(instruction_account_metas.len());
836 for (instruction_account_index, account_meta) in instruction_account_metas.iter().enumerate() {
837 let index_in_transaction = transaction_accounts
838 .iter()
839 .position(|(key, _account)| *key == account_meta.pubkey)
840 .unwrap_or(transaction_accounts.len())
841 as IndexOfAccount;
842 let index_in_callee = instruction_accounts
843 .get(0..instruction_account_index)
844 .unwrap()
845 .iter()
846 .position(|instruction_account| {
847 instruction_account.index_in_transaction == index_in_transaction
848 })
849 .unwrap_or(instruction_account_index) as IndexOfAccount;
850 instruction_accounts.push(InstructionAccount {
851 index_in_transaction,
852 index_in_caller: index_in_transaction,
853 index_in_callee,
854 is_signer: account_meta.is_signer,
855 is_writable: account_meta.is_writable,
856 });
857 }
858 if program_indices.is_empty() {
859 program_indices.insert(0, transaction_accounts.len() as IndexOfAccount);
860 let processor_account = AccountSharedData::new(0, 0, &native_loader::id());
861 transaction_accounts.push((*loader_id, processor_account));
862 }
863 let pop_epoch_schedule_account = if !transaction_accounts
864 .iter()
865 .any(|(key, _)| *key == sysvar::epoch_schedule::id())
866 {
867 transaction_accounts.push((
868 sysvar::epoch_schedule::id(),
869 create_account_shared_data_for_test(&EpochSchedule::default()),
870 ));
871 true
872 } else {
873 false
874 };
875 with_mock_invoke_context_with_feature_set!(
876 invoke_context,
877 transaction_context,
878 feature_set,
879 transaction_accounts
880 );
881 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
882 program_cache_for_tx_batch.replenish(
883 *loader_id,
884 Arc::new(ProgramCacheEntry::new_builtin(0, 0, builtin_function)),
885 );
886 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
887 pre_adjustments(&mut invoke_context);
888 let result = invoke_context.process_instruction(
889 instruction_data,
890 &instruction_accounts,
891 &program_indices,
892 &mut 0,
893 &mut ExecuteTimings::default(),
894 );
895 assert_eq!(result, expected_result);
896 post_adjustments(&mut invoke_context);
897 let mut transaction_accounts = transaction_context.deconstruct_without_keys().unwrap();
898 if pop_epoch_schedule_account {
899 transaction_accounts.pop();
900 }
901 transaction_accounts.pop();
902 transaction_accounts
903}
904
905pub fn mock_process_instruction<F: FnMut(&mut InvokeContext), G: FnMut(&mut InvokeContext)>(
906 loader_id: &Pubkey,
907 program_indices: Vec<IndexOfAccount>,
908 instruction_data: &[u8],
909 transaction_accounts: Vec<TransactionAccount>,
910 instruction_account_metas: Vec<AccountMeta>,
911 expected_result: Result<(), InstructionError>,
912 builtin_function: BuiltinFunctionWithContext,
913 pre_adjustments: F,
914 post_adjustments: G,
915) -> Vec<AccountSharedData> {
916 mock_process_instruction_with_feature_set(
917 loader_id,
918 program_indices,
919 instruction_data,
920 transaction_accounts,
921 instruction_account_metas,
922 expected_result,
923 builtin_function,
924 pre_adjustments,
925 post_adjustments,
926 &SVMFeatureSet::all_enabled(),
927 )
928}
929
930#[cfg(test)]
931mod tests {
932 use {
933 super::*,
934 crate::execution_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
935 serde::{Deserialize, Serialize},
936 solana_account::WritableAccount,
937 solana_instruction::Instruction,
938 solana_rent::Rent,
939 test_case::test_case,
940 };
941
942 #[derive(Debug, Serialize, Deserialize)]
943 enum MockInstruction {
944 NoopSuccess,
945 NoopFail,
946 ModifyOwned,
947 ModifyNotOwned,
948 ModifyReadonly,
949 UnbalancedPush,
950 UnbalancedPop,
951 ConsumeComputeUnits {
952 compute_units_to_consume: u64,
953 desired_result: Result<(), InstructionError>,
954 },
955 Resize {
956 new_len: u64,
957 },
958 }
959
960 const MOCK_BUILTIN_COMPUTE_UNIT_COST: u64 = 1;
961
962 declare_process_instruction!(
963 MockBuiltin,
964 MOCK_BUILTIN_COMPUTE_UNIT_COST,
965 |invoke_context| {
966 let transaction_context = &invoke_context.transaction_context;
967 let instruction_context = transaction_context.get_current_instruction_context()?;
968 let instruction_data = instruction_context.get_instruction_data();
969 let program_id = instruction_context.get_last_program_key(transaction_context)?;
970 let instruction_accounts = (0..4)
971 .map(|instruction_account_index| InstructionAccount {
972 index_in_transaction: instruction_account_index,
973 index_in_caller: instruction_account_index,
974 index_in_callee: instruction_account_index,
975 is_signer: false,
976 is_writable: false,
977 })
978 .collect::<Vec<_>>();
979 assert_eq!(
980 program_id,
981 instruction_context
982 .try_borrow_instruction_account(transaction_context, 0)?
983 .get_owner()
984 );
985 assert_ne!(
986 instruction_context
987 .try_borrow_instruction_account(transaction_context, 1)?
988 .get_owner(),
989 instruction_context
990 .try_borrow_instruction_account(transaction_context, 0)?
991 .get_key()
992 );
993
994 if let Ok(instruction) = bincode::deserialize(instruction_data) {
995 match instruction {
996 MockInstruction::NoopSuccess => (),
997 MockInstruction::NoopFail => return Err(InstructionError::GenericError),
998 MockInstruction::ModifyOwned => instruction_context
999 .try_borrow_instruction_account(transaction_context, 0)?
1000 .set_data_from_slice(&[1])?,
1001 MockInstruction::ModifyNotOwned => instruction_context
1002 .try_borrow_instruction_account(transaction_context, 1)?
1003 .set_data_from_slice(&[1])?,
1004 MockInstruction::ModifyReadonly => instruction_context
1005 .try_borrow_instruction_account(transaction_context, 2)?
1006 .set_data_from_slice(&[1])?,
1007 MockInstruction::UnbalancedPush => {
1008 instruction_context
1009 .try_borrow_instruction_account(transaction_context, 0)?
1010 .checked_add_lamports(1)?;
1011 let program_id = *transaction_context.get_key_of_account_at_index(3)?;
1012 let metas = vec![
1013 AccountMeta::new_readonly(
1014 *transaction_context.get_key_of_account_at_index(0)?,
1015 false,
1016 ),
1017 AccountMeta::new_readonly(
1018 *transaction_context.get_key_of_account_at_index(1)?,
1019 false,
1020 ),
1021 ];
1022 let inner_instruction = Instruction::new_with_bincode(
1023 program_id,
1024 &MockInstruction::NoopSuccess,
1025 metas,
1026 );
1027 invoke_context
1028 .transaction_context
1029 .get_next_instruction_context()
1030 .unwrap()
1031 .configure(&[3], &instruction_accounts, &[]);
1032 let result = invoke_context.push();
1033 assert_eq!(result, Err(InstructionError::UnbalancedInstruction));
1034 result?;
1035 invoke_context
1036 .native_invoke(inner_instruction.into(), &[])
1037 .and(invoke_context.pop())?;
1038 }
1039 MockInstruction::UnbalancedPop => instruction_context
1040 .try_borrow_instruction_account(transaction_context, 0)?
1041 .checked_add_lamports(1)?,
1042 MockInstruction::ConsumeComputeUnits {
1043 compute_units_to_consume,
1044 desired_result,
1045 } => {
1046 invoke_context
1047 .consume_checked(compute_units_to_consume)
1048 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
1049 return desired_result;
1050 }
1051 MockInstruction::Resize { new_len } => instruction_context
1052 .try_borrow_instruction_account(transaction_context, 0)?
1053 .set_data(vec![0; new_len as usize])?,
1054 }
1055 } else {
1056 return Err(InstructionError::InvalidInstructionData);
1057 }
1058 Ok(())
1059 }
1060 );
1061
1062 #[test]
1063 fn test_instruction_stack_height() {
1064 let one_more_than_max_depth = SVMTransactionExecutionBudget::default()
1065 .max_instruction_stack_depth
1066 .saturating_add(1);
1067 let mut invoke_stack = vec![];
1068 let mut transaction_accounts = vec![];
1069 let mut instruction_accounts = vec![];
1070 for index in 0..one_more_than_max_depth {
1071 invoke_stack.push(solana_pubkey::new_rand());
1072 transaction_accounts.push((
1073 solana_pubkey::new_rand(),
1074 AccountSharedData::new(index as u64, 1, invoke_stack.get(index).unwrap()),
1075 ));
1076 instruction_accounts.push(InstructionAccount {
1077 index_in_transaction: index as IndexOfAccount,
1078 index_in_caller: index as IndexOfAccount,
1079 index_in_callee: instruction_accounts.len() as IndexOfAccount,
1080 is_signer: false,
1081 is_writable: true,
1082 });
1083 }
1084 for (index, program_id) in invoke_stack.iter().enumerate() {
1085 transaction_accounts.push((
1086 *program_id,
1087 AccountSharedData::new(1, 1, &solana_pubkey::Pubkey::default()),
1088 ));
1089 instruction_accounts.push(InstructionAccount {
1090 index_in_transaction: index as IndexOfAccount,
1091 index_in_caller: index as IndexOfAccount,
1092 index_in_callee: index as IndexOfAccount,
1093 is_signer: false,
1094 is_writable: false,
1095 });
1096 }
1097 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1098
1099 let mut depth_reached: usize = 0;
1101 for _ in 0..invoke_stack.len() {
1102 invoke_context
1103 .transaction_context
1104 .get_next_instruction_context()
1105 .unwrap()
1106 .configure(
1107 &[one_more_than_max_depth.saturating_add(depth_reached) as IndexOfAccount],
1108 &instruction_accounts,
1109 &[],
1110 );
1111 if Err(InstructionError::CallDepth) == invoke_context.push() {
1112 break;
1113 }
1114 depth_reached = depth_reached.saturating_add(1);
1115 }
1116 assert_ne!(depth_reached, 0);
1117 assert!(depth_reached < one_more_than_max_depth);
1118 }
1119
1120 #[test]
1121 fn test_max_instruction_trace_length() {
1122 const MAX_INSTRUCTIONS: usize = 8;
1123 let mut transaction_context =
1124 TransactionContext::new(Vec::new(), Rent::default(), 1, MAX_INSTRUCTIONS);
1125 for _ in 0..MAX_INSTRUCTIONS {
1126 transaction_context.push().unwrap();
1127 transaction_context.pop().unwrap();
1128 }
1129 assert_eq!(
1130 transaction_context.push(),
1131 Err(InstructionError::MaxInstructionTraceLengthExceeded)
1132 );
1133 }
1134
1135 #[test_case(MockInstruction::NoopSuccess, Ok(()); "NoopSuccess")]
1136 #[test_case(MockInstruction::NoopFail, Err(InstructionError::GenericError); "NoopFail")]
1137 #[test_case(MockInstruction::ModifyOwned, Ok(()); "ModifyOwned")]
1138 #[test_case(MockInstruction::ModifyNotOwned, Err(InstructionError::ExternalAccountDataModified); "ModifyNotOwned")]
1139 #[test_case(MockInstruction::ModifyReadonly, Err(InstructionError::ReadonlyDataModified); "ModifyReadonly")]
1140 #[test_case(MockInstruction::UnbalancedPush, Err(InstructionError::UnbalancedInstruction); "UnbalancedPush")]
1141 #[test_case(MockInstruction::UnbalancedPop, Err(InstructionError::UnbalancedInstruction); "UnbalancedPop")]
1142 fn test_process_instruction_account_modifications(
1143 instruction: MockInstruction,
1144 expected_result: Result<(), InstructionError>,
1145 ) {
1146 let callee_program_id = solana_pubkey::new_rand();
1147 let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1148 let not_owned_account = AccountSharedData::new(84, 1, &solana_pubkey::new_rand());
1149 let readonly_account = AccountSharedData::new(168, 1, &solana_pubkey::new_rand());
1150 let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1151 let mut program_account = AccountSharedData::new(1, 1, &native_loader::id());
1152 program_account.set_executable(true);
1153 let transaction_accounts = vec![
1154 (solana_pubkey::new_rand(), owned_account),
1155 (solana_pubkey::new_rand(), not_owned_account),
1156 (solana_pubkey::new_rand(), readonly_account),
1157 (callee_program_id, program_account),
1158 (solana_pubkey::new_rand(), loader_account),
1159 ];
1160 let metas = vec![
1161 AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1162 AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1163 AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1164 ];
1165 let instruction_accounts = (0..4)
1166 .map(|instruction_account_index| InstructionAccount {
1167 index_in_transaction: instruction_account_index,
1168 index_in_caller: instruction_account_index,
1169 index_in_callee: instruction_account_index,
1170 is_signer: false,
1171 is_writable: instruction_account_index < 2,
1172 })
1173 .collect::<Vec<_>>();
1174 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1175 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1176 program_cache_for_tx_batch.replenish(
1177 callee_program_id,
1178 Arc::new(ProgramCacheEntry::new_builtin(0, 1, MockBuiltin::vm)),
1179 );
1180 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1181
1182 invoke_context
1184 .transaction_context
1185 .get_next_instruction_context()
1186 .unwrap()
1187 .configure(&[4], &instruction_accounts, &[]);
1188 invoke_context.push().unwrap();
1189 let inner_instruction =
1190 Instruction::new_with_bincode(callee_program_id, &instruction, metas.clone());
1191 let result = invoke_context
1192 .native_invoke(inner_instruction.into(), &[])
1193 .and(invoke_context.pop());
1194 assert_eq!(result, expected_result);
1195 }
1196
1197 #[test_case(Ok(()); "Ok")]
1198 #[test_case(Err(InstructionError::GenericError); "GenericError")]
1199 fn test_process_instruction_compute_unit_consumption(
1200 expected_result: Result<(), InstructionError>,
1201 ) {
1202 let callee_program_id = solana_pubkey::new_rand();
1203 let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1204 let not_owned_account = AccountSharedData::new(84, 1, &solana_pubkey::new_rand());
1205 let readonly_account = AccountSharedData::new(168, 1, &solana_pubkey::new_rand());
1206 let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1207 let mut program_account = AccountSharedData::new(1, 1, &native_loader::id());
1208 program_account.set_executable(true);
1209 let transaction_accounts = vec![
1210 (solana_pubkey::new_rand(), owned_account),
1211 (solana_pubkey::new_rand(), not_owned_account),
1212 (solana_pubkey::new_rand(), readonly_account),
1213 (callee_program_id, program_account),
1214 (solana_pubkey::new_rand(), loader_account),
1215 ];
1216 let metas = vec![
1217 AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1218 AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1219 AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1220 ];
1221 let instruction_accounts = (0..4)
1222 .map(|instruction_account_index| InstructionAccount {
1223 index_in_transaction: instruction_account_index,
1224 index_in_caller: instruction_account_index,
1225 index_in_callee: instruction_account_index,
1226 is_signer: false,
1227 is_writable: instruction_account_index < 2,
1228 })
1229 .collect::<Vec<_>>();
1230 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1231 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1232 program_cache_for_tx_batch.replenish(
1233 callee_program_id,
1234 Arc::new(ProgramCacheEntry::new_builtin(0, 1, MockBuiltin::vm)),
1235 );
1236 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1237
1238 let compute_units_to_consume = 10;
1240 invoke_context
1241 .transaction_context
1242 .get_next_instruction_context()
1243 .unwrap()
1244 .configure(&[4], &instruction_accounts, &[]);
1245 invoke_context.push().unwrap();
1246 let inner_instruction = Instruction::new_with_bincode(
1247 callee_program_id,
1248 &MockInstruction::ConsumeComputeUnits {
1249 compute_units_to_consume,
1250 desired_result: expected_result.clone(),
1251 },
1252 metas.clone(),
1253 );
1254 let inner_instruction = StableInstruction::from(inner_instruction);
1255 let (inner_instruction_accounts, program_indices) = invoke_context
1256 .prepare_instruction(&inner_instruction, &[])
1257 .unwrap();
1258
1259 let mut compute_units_consumed = 0;
1260 let result = invoke_context.process_instruction(
1261 &inner_instruction.data,
1262 &inner_instruction_accounts,
1263 &program_indices,
1264 &mut compute_units_consumed,
1265 &mut ExecuteTimings::default(),
1266 );
1267
1268 assert!(compute_units_consumed > 0);
1272 assert_eq!(
1273 compute_units_consumed,
1274 compute_units_to_consume.saturating_add(MOCK_BUILTIN_COMPUTE_UNIT_COST),
1275 );
1276 assert_eq!(result, expected_result);
1277
1278 invoke_context.pop().unwrap();
1279 }
1280
1281 #[test]
1282 fn test_invoke_context_compute_budget() {
1283 let transaction_accounts = vec![(solana_pubkey::new_rand(), AccountSharedData::default())];
1284 let execution_budget = SVMTransactionExecutionBudget {
1285 compute_unit_limit: u64::from(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT),
1286 ..SVMTransactionExecutionBudget::default()
1287 };
1288
1289 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1290 invoke_context.compute_budget = execution_budget;
1291
1292 invoke_context
1293 .transaction_context
1294 .get_next_instruction_context()
1295 .unwrap()
1296 .configure(&[0], &[], &[]);
1297 invoke_context.push().unwrap();
1298 assert_eq!(*invoke_context.get_compute_budget(), execution_budget);
1299 invoke_context.pop().unwrap();
1300 }
1301
1302 #[test_case(0; "Resize the account to *the same size*, so not consuming any additional size")]
1303 #[test_case(1; "Resize the account larger")]
1304 #[test_case(-1; "Resize the account smaller")]
1305 fn test_process_instruction_accounts_resize_delta(resize_delta: i64) {
1306 let program_key = Pubkey::new_unique();
1307 let user_account_data_len = 123u64;
1308 let user_account =
1309 AccountSharedData::new(100, user_account_data_len as usize, &program_key);
1310 let dummy_account = AccountSharedData::new(10, 0, &program_key);
1311 let mut program_account = AccountSharedData::new(500, 500, &native_loader::id());
1312 program_account.set_executable(true);
1313 let transaction_accounts = vec![
1314 (Pubkey::new_unique(), user_account),
1315 (Pubkey::new_unique(), dummy_account),
1316 (program_key, program_account),
1317 ];
1318 let instruction_accounts = [
1319 InstructionAccount {
1320 index_in_transaction: 0,
1321 index_in_caller: 0,
1322 index_in_callee: 0,
1323 is_signer: false,
1324 is_writable: true,
1325 },
1326 InstructionAccount {
1327 index_in_transaction: 1,
1328 index_in_caller: 1,
1329 index_in_callee: 1,
1330 is_signer: false,
1331 is_writable: false,
1332 },
1333 ];
1334 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1335 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1336 program_cache_for_tx_batch.replenish(
1337 program_key,
1338 Arc::new(ProgramCacheEntry::new_builtin(0, 0, MockBuiltin::vm)),
1339 );
1340 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1341
1342 let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
1343 let instruction_data = bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1344
1345 let result = invoke_context.process_instruction(
1346 &instruction_data,
1347 &instruction_accounts,
1348 &[2],
1349 &mut 0,
1350 &mut ExecuteTimings::default(),
1351 );
1352
1353 assert!(result.is_ok());
1354 assert_eq!(
1355 invoke_context
1356 .transaction_context
1357 .accounts_resize_delta()
1358 .unwrap(),
1359 resize_delta
1360 );
1361 }
1362}