1use {
2 crate::{
3 loaded_programs::{
4 ProgramCacheEntry, ProgramCacheEntryType, ProgramCacheForTxBatch,
5 ProgramRuntimeEnvironments,
6 },
7 stable_log,
8 sysvar_cache::SysvarCache,
9 },
10 solana_account::{create_account_shared_data_for_test, AccountSharedData},
11 solana_clock::Slot,
12 solana_compute_budget::compute_budget::ComputeBudget,
13 solana_epoch_schedule::EpochSchedule,
14 solana_feature_set::{
15 lift_cpi_caller_restriction, move_precompile_verification_to_svm,
16 remove_accounts_executable_flag_checks, FeatureSet,
17 },
18 solana_hash::Hash,
19 solana_instruction::{error::InstructionError, AccountMeta},
20 solana_log_collector::{ic_msg, LogCollector},
21 solana_measure::measure::Measure,
22 solana_precompiles::Precompile,
23 solana_pubkey::Pubkey,
24 solana_sbpf::{
25 ebpf::MM_HEAP_START,
26 error::{EbpfError, ProgramResult},
27 memory_region::MemoryMapping,
28 program::{BuiltinFunction, SBPFVersion},
29 vm::{Config, ContextObject, EbpfVm},
30 },
31 solana_sdk_ids::{bpf_loader_deprecated, native_loader, sysvar},
32 solana_stable_layout::stable_instruction::StableInstruction,
33 solana_timings::{ExecuteDetailsTimings, ExecuteTimings},
34 solana_transaction_context::{
35 IndexOfAccount, InstructionAccount, TransactionAccount, TransactionContext,
36 },
37 solana_type_overrides::sync::{atomic::Ordering, Arc},
38 std::{
39 alloc::Layout,
40 cell::RefCell,
41 fmt::{self, Debug},
42 rc::Rc,
43 },
44};
45
46pub type BuiltinFunctionWithContext = BuiltinFunction<InvokeContext<'static>>;
47
48#[macro_export]
50macro_rules! declare_process_instruction {
51 ($process_instruction:ident, $cu_to_consume:expr, |$invoke_context:ident| $inner:tt) => {
52 $crate::solana_sbpf::declare_builtin_function!(
53 $process_instruction,
54 fn rust(
55 invoke_context: &mut $crate::invoke_context::InvokeContext,
56 _arg0: u64,
57 _arg1: u64,
58 _arg2: u64,
59 _arg3: u64,
60 _arg4: u64,
61 _memory_mapping: &mut $crate::solana_sbpf::memory_region::MemoryMapping,
62 ) -> std::result::Result<u64, Box<dyn std::error::Error>> {
63 fn process_instruction_inner(
64 $invoke_context: &mut $crate::invoke_context::InvokeContext,
65 ) -> std::result::Result<(), $crate::__private::InstructionError>
66 $inner
67
68 let consumption_result = if $cu_to_consume > 0
69 {
70 invoke_context.consume_checked($cu_to_consume)
71 } else {
72 Ok(())
73 };
74 consumption_result
75 .and_then(|_| {
76 process_instruction_inner(invoke_context)
77 .map(|_| 0)
78 .map_err(|err| Box::new(err) as Box<dyn std::error::Error>)
79 })
80 .into()
81 }
82 );
83 };
84}
85
86impl ContextObject for InvokeContext<'_> {
87 fn trace(&mut self, state: [u64; 12]) {
88 self.syscall_context
89 .last_mut()
90 .unwrap()
91 .as_mut()
92 .unwrap()
93 .trace_log
94 .push(state);
95 }
96
97 fn consume(&mut self, amount: u64) {
98 let mut compute_meter = self.compute_meter.borrow_mut();
101 *compute_meter = compute_meter.saturating_sub(amount);
102 }
103
104 fn get_remaining(&self) -> u64 {
105 *self.compute_meter.borrow()
106 }
107}
108
109#[derive(Clone, PartialEq, Eq, Debug)]
110pub struct AllocErr;
111impl fmt::Display for AllocErr {
112 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
113 f.write_str("Error: Memory allocation failed")
114 }
115}
116
117pub struct BpfAllocator {
118 len: u64,
119 pos: u64,
120}
121
122impl BpfAllocator {
123 pub fn new(len: u64) -> Self {
124 Self { len, pos: 0 }
125 }
126
127 pub fn alloc(&mut self, layout: Layout) -> Result<u64, AllocErr> {
128 let bytes_to_align = (self.pos as *const u8).align_offset(layout.align()) as u64;
129 if self
130 .pos
131 .saturating_add(bytes_to_align)
132 .saturating_add(layout.size() as u64)
133 <= self.len
134 {
135 self.pos = self.pos.saturating_add(bytes_to_align);
136 let addr = MM_HEAP_START.saturating_add(self.pos);
137 self.pos = self.pos.saturating_add(layout.size() as u64);
138 Ok(addr)
139 } else {
140 Err(AllocErr)
141 }
142 }
143}
144
145pub struct EnvironmentConfig<'a> {
146 pub blockhash: Hash,
147 pub blockhash_lamports_per_signature: u64,
148 epoch_total_stake: u64,
149 get_epoch_vote_account_stake_callback: &'a dyn Fn(&'a Pubkey) -> u64,
150 pub feature_set: Arc<FeatureSet>,
151 sysvar_cache: &'a SysvarCache,
152}
153impl<'a> EnvironmentConfig<'a> {
154 pub fn new(
155 blockhash: Hash,
156 blockhash_lamports_per_signature: u64,
157 epoch_total_stake: u64,
158 get_epoch_vote_account_stake_callback: &'a dyn Fn(&'a Pubkey) -> u64,
159 feature_set: Arc<FeatureSet>,
160 sysvar_cache: &'a SysvarCache,
161 ) -> Self {
162 Self {
163 blockhash,
164 blockhash_lamports_per_signature,
165 epoch_total_stake,
166 get_epoch_vote_account_stake_callback,
167 feature_set,
168 sysvar_cache,
169 }
170 }
171}
172
173pub struct SyscallContext {
174 pub allocator: BpfAllocator,
175 pub accounts_metadata: Vec<SerializedAccountMetadata>,
176 pub trace_log: Vec<[u64; 12]>,
177}
178
179#[derive(Debug, Clone)]
180pub struct SerializedAccountMetadata {
181 pub original_data_len: usize,
182 pub vm_data_addr: u64,
183 pub vm_key_addr: u64,
184 pub vm_lamports_addr: u64,
185 pub vm_owner_addr: u64,
186}
187
188pub struct InvokeContext<'a> {
190 pub transaction_context: &'a mut TransactionContext,
192 pub program_cache_for_tx_batch: &'a mut ProgramCacheForTxBatch,
194 pub environment_config: EnvironmentConfig<'a>,
196 compute_budget: ComputeBudget,
198 compute_meter: RefCell<u64>,
201 log_collector: Option<Rc<RefCell<LogCollector>>>,
202 pub execute_time: Option<Measure>,
204 pub timings: ExecuteDetailsTimings,
205 pub syscall_context: Vec<Option<SyscallContext>>,
206 traces: Vec<Vec<[u64; 12]>>,
207}
208
209impl<'a> InvokeContext<'a> {
210 #[allow(clippy::too_many_arguments)]
211 pub fn new(
212 transaction_context: &'a mut TransactionContext,
213 program_cache_for_tx_batch: &'a mut ProgramCacheForTxBatch,
214 environment_config: EnvironmentConfig<'a>,
215 log_collector: Option<Rc<RefCell<LogCollector>>>,
216 compute_budget: ComputeBudget,
217 ) -> Self {
218 Self {
219 transaction_context,
220 program_cache_for_tx_batch,
221 environment_config,
222 log_collector,
223 compute_budget,
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
428 .get_feature_set()
429 .is_active(&lift_cpi_caller_restriction::id())
430 {
431 self.transaction_context
432 .find_index_of_program_account(&callee_program_id)
433 .ok_or_else(|| {
434 ic_msg!(self, "Unknown program {}", callee_program_id);
435 InstructionError::MissingAccount
436 })?
437 } else {
438 let program_account_index = instruction_context
439 .find_index_of_instruction_account(self.transaction_context, &callee_program_id)
440 .ok_or_else(|| {
441 ic_msg!(self, "Unknown program {}", callee_program_id);
442 InstructionError::MissingAccount
443 })?;
444 let borrowed_program_account = instruction_context
445 .try_borrow_instruction_account(self.transaction_context, program_account_index)?;
446 #[allow(deprecated)]
447 if !self
448 .get_feature_set()
449 .is_active(&remove_accounts_executable_flag_checks::id())
450 && !borrowed_program_account.is_executable()
451 {
452 ic_msg!(self, "Account {} is not executable", callee_program_id);
453 return Err(InstructionError::AccountNotExecutable);
454 }
455 borrowed_program_account.get_index_in_transaction()
456 };
457
458 Ok((instruction_accounts, vec![program_account_index]))
459 }
460
461 pub fn process_instruction(
463 &mut self,
464 instruction_data: &[u8],
465 instruction_accounts: &[InstructionAccount],
466 program_indices: &[IndexOfAccount],
467 compute_units_consumed: &mut u64,
468 timings: &mut ExecuteTimings,
469 ) -> Result<(), InstructionError> {
470 *compute_units_consumed = 0;
471 self.transaction_context
472 .get_next_instruction_context()?
473 .configure(program_indices, instruction_accounts, instruction_data);
474 self.push()?;
475 self.process_executable_chain(compute_units_consumed, timings)
476 .and(self.pop())
479 }
480
481 pub fn process_precompile<'ix_data>(
483 &mut self,
484 precompile: &Precompile,
485 instruction_data: &[u8],
486 instruction_accounts: &[InstructionAccount],
487 program_indices: &[IndexOfAccount],
488 message_instruction_datas_iter: impl Iterator<Item = &'ix_data [u8]>,
489 ) -> Result<(), InstructionError> {
490 self.transaction_context
491 .get_next_instruction_context()?
492 .configure(program_indices, instruction_accounts, instruction_data);
493 self.push()?;
494
495 let feature_set = self.get_feature_set();
496 let move_precompile_verification_to_svm =
497 feature_set.is_active(&move_precompile_verification_to_svm::id());
498 if move_precompile_verification_to_svm {
499 let instruction_datas: Vec<_> = message_instruction_datas_iter.collect();
500 precompile
501 .verify(instruction_data, &instruction_datas, feature_set)
502 .map_err(InstructionError::from)
503 .and(self.pop())
504 } else {
505 self.pop()
506 }
507 }
508
509 fn process_executable_chain(
511 &mut self,
512 compute_units_consumed: &mut u64,
513 timings: &mut ExecuteTimings,
514 ) -> Result<(), InstructionError> {
515 let instruction_context = self.transaction_context.get_current_instruction_context()?;
516 let process_executable_chain_time = Measure::start("process_executable_chain_time");
517
518 let builtin_id = {
519 debug_assert!(instruction_context.get_number_of_program_accounts() <= 1);
520 let borrowed_root_account = instruction_context
521 .try_borrow_program_account(self.transaction_context, 0)
522 .map_err(|_| InstructionError::UnsupportedProgramId)?;
523 let owner_id = borrowed_root_account.get_owner();
524 if native_loader::check_id(owner_id) {
525 *borrowed_root_account.get_key()
526 } else {
527 *owner_id
528 }
529 };
530
531 const ENTRYPOINT_KEY: u32 = 0x71E3CF81;
533 let entry = self
534 .program_cache_for_tx_batch
535 .find(&builtin_id)
536 .ok_or(InstructionError::UnsupportedProgramId)?;
537 let function = match &entry.program {
538 ProgramCacheEntryType::Builtin(program) => program
539 .get_function_registry()
540 .lookup_by_key(ENTRYPOINT_KEY)
541 .map(|(_name, function)| function),
542 _ => None,
543 }
544 .ok_or(InstructionError::UnsupportedProgramId)?;
545 entry.ix_usage_counter.fetch_add(1, Ordering::Relaxed);
546
547 let program_id = *instruction_context.get_last_program_key(self.transaction_context)?;
548 self.transaction_context
549 .set_return_data(program_id, Vec::new())?;
550 let logger = self.get_log_collector();
551 stable_log::program_invoke(&logger, &program_id, self.get_stack_height());
552 let pre_remaining_units = self.get_remaining();
553 let mock_config = Config::default();
557 let empty_memory_mapping =
558 MemoryMapping::new(Vec::new(), &mock_config, SBPFVersion::V0).unwrap();
559 let mut vm = EbpfVm::new(
560 self.program_cache_for_tx_batch
561 .environments
562 .program_runtime_v2
563 .clone(),
564 SBPFVersion::V0,
565 unsafe { std::mem::transmute::<&mut InvokeContext, &mut InvokeContext>(self) },
567 empty_memory_mapping,
568 0,
569 );
570 vm.invoke_function(function);
571 let result = match vm.program_result {
572 ProgramResult::Ok(_) => {
573 stable_log::program_success(&logger, &program_id);
574 Ok(())
575 }
576 ProgramResult::Err(ref err) => {
577 if let EbpfError::SyscallError(syscall_error) = err {
578 if let Some(instruction_err) = syscall_error.downcast_ref::<InstructionError>()
579 {
580 stable_log::program_failure(&logger, &program_id, instruction_err);
581 Err(instruction_err.clone())
582 } else {
583 stable_log::program_failure(&logger, &program_id, syscall_error);
584 Err(InstructionError::ProgramFailedToComplete)
585 }
586 } else {
587 stable_log::program_failure(&logger, &program_id, err);
588 Err(InstructionError::ProgramFailedToComplete)
589 }
590 }
591 };
592 let post_remaining_units = self.get_remaining();
593 *compute_units_consumed = pre_remaining_units.saturating_sub(post_remaining_units);
594
595 if builtin_id == program_id && result.is_ok() && *compute_units_consumed == 0 {
596 return Err(InstructionError::BuiltinProgramsMustConsumeComputeUnits);
597 }
598
599 timings
600 .execute_accessories
601 .process_instructions
602 .process_executable_chain_us += process_executable_chain_time.end_as_us();
603 result
604 }
605
606 pub fn get_log_collector(&self) -> Option<Rc<RefCell<LogCollector>>> {
608 self.log_collector.clone()
609 }
610
611 pub fn consume_checked(&self, amount: u64) -> Result<(), Box<dyn std::error::Error>> {
613 let mut compute_meter = self.compute_meter.borrow_mut();
614 let exceeded = *compute_meter < amount;
615 *compute_meter = compute_meter.saturating_sub(amount);
616 if exceeded {
617 return Err(Box::new(InstructionError::ComputationalBudgetExceeded));
618 }
619 Ok(())
620 }
621
622 pub fn mock_set_remaining(&self, remaining: u64) {
626 *self.compute_meter.borrow_mut() = remaining;
627 }
628
629 pub fn get_compute_budget(&self) -> &ComputeBudget {
631 &self.compute_budget
632 }
633
634 pub fn get_feature_set(&self) -> &FeatureSet {
636 &self.environment_config.feature_set
637 }
638
639 pub fn mock_set_feature_set(&mut self, feature_set: Arc<FeatureSet>) {
643 self.environment_config.feature_set = feature_set;
644 }
645
646 pub fn get_sysvar_cache(&self) -> &SysvarCache {
648 self.environment_config.sysvar_cache
649 }
650
651 pub fn get_epoch_total_stake(&self) -> u64 {
653 self.environment_config.epoch_total_stake
654 }
655
656 pub fn get_epoch_vote_account_stake(&self, pubkey: &'a Pubkey) -> u64 {
658 (self
659 .environment_config
660 .get_epoch_vote_account_stake_callback)(pubkey)
661 }
662
663 pub fn get_check_aligned(&self) -> bool {
665 self.transaction_context
666 .get_current_instruction_context()
667 .and_then(|instruction_context| {
668 let program_account =
669 instruction_context.try_borrow_last_program_account(self.transaction_context);
670 debug_assert!(program_account.is_ok());
671 program_account
672 })
673 .map(|program_account| *program_account.get_owner() != bpf_loader_deprecated::id())
674 .unwrap_or(true)
675 }
676
677 pub fn set_syscall_context(
679 &mut self,
680 syscall_context: SyscallContext,
681 ) -> Result<(), InstructionError> {
682 *self
683 .syscall_context
684 .last_mut()
685 .ok_or(InstructionError::CallDepth)? = Some(syscall_context);
686 Ok(())
687 }
688
689 pub fn get_syscall_context(&self) -> Result<&SyscallContext, InstructionError> {
691 self.syscall_context
692 .last()
693 .and_then(std::option::Option::as_ref)
694 .ok_or(InstructionError::CallDepth)
695 }
696
697 pub fn get_syscall_context_mut(&mut self) -> Result<&mut SyscallContext, InstructionError> {
699 self.syscall_context
700 .last_mut()
701 .and_then(|syscall_context| syscall_context.as_mut())
702 .ok_or(InstructionError::CallDepth)
703 }
704
705 pub fn get_traces(&self) -> &Vec<Vec<[u64; 12]>> {
707 &self.traces
708 }
709}
710
711#[macro_export]
712macro_rules! with_mock_invoke_context {
713 (
714 $invoke_context:ident,
715 $transaction_context:ident,
716 $transaction_accounts:expr $(,)?
717 ) => {
718 use {
719 solana_compute_budget::compute_budget::ComputeBudget,
720 solana_feature_set::FeatureSet,
721 solana_log_collector::LogCollector,
722 solana_type_overrides::sync::Arc,
723 $crate::{
724 __private::{Hash, ReadableAccount, Rent, TransactionContext},
725 invoke_context::{EnvironmentConfig, InvokeContext},
726 loaded_programs::ProgramCacheForTxBatch,
727 sysvar_cache::SysvarCache,
728 },
729 };
730 let compute_budget = ComputeBudget::default();
731 let mut $transaction_context = TransactionContext::new(
732 $transaction_accounts,
733 Rent::default(),
734 compute_budget.max_instruction_stack_depth,
735 compute_budget.max_instruction_trace_length,
736 );
737 let mut sysvar_cache = SysvarCache::default();
738 sysvar_cache.fill_missing_entries(|pubkey, callback| {
739 for index in 0..$transaction_context.get_number_of_accounts() {
740 if $transaction_context
741 .get_key_of_account_at_index(index)
742 .unwrap()
743 == pubkey
744 {
745 callback(
746 $transaction_context
747 .get_account_at_index(index)
748 .unwrap()
749 .borrow()
750 .data(),
751 );
752 }
753 }
754 });
755 let environment_config = EnvironmentConfig::new(
756 Hash::default(),
757 0,
758 0,
759 &|_| 0,
760 Arc::new(FeatureSet::all_enabled()),
761 &sysvar_cache,
762 );
763 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
764 let mut $invoke_context = InvokeContext::new(
765 &mut $transaction_context,
766 &mut program_cache_for_tx_batch,
767 environment_config,
768 Some(LogCollector::new_ref()),
769 compute_budget,
770 );
771 };
772}
773
774pub fn mock_process_instruction<F: FnMut(&mut InvokeContext), G: FnMut(&mut InvokeContext)>(
775 loader_id: &Pubkey,
776 mut program_indices: Vec<IndexOfAccount>,
777 instruction_data: &[u8],
778 mut transaction_accounts: Vec<TransactionAccount>,
779 instruction_account_metas: Vec<AccountMeta>,
780 expected_result: Result<(), InstructionError>,
781 builtin_function: BuiltinFunctionWithContext,
782 mut pre_adjustments: F,
783 mut post_adjustments: G,
784) -> Vec<AccountSharedData> {
785 let mut instruction_accounts: Vec<InstructionAccount> =
786 Vec::with_capacity(instruction_account_metas.len());
787 for (instruction_account_index, account_meta) in instruction_account_metas.iter().enumerate() {
788 let index_in_transaction = transaction_accounts
789 .iter()
790 .position(|(key, _account)| *key == account_meta.pubkey)
791 .unwrap_or(transaction_accounts.len())
792 as IndexOfAccount;
793 let index_in_callee = instruction_accounts
794 .get(0..instruction_account_index)
795 .unwrap()
796 .iter()
797 .position(|instruction_account| {
798 instruction_account.index_in_transaction == index_in_transaction
799 })
800 .unwrap_or(instruction_account_index) as IndexOfAccount;
801 instruction_accounts.push(InstructionAccount {
802 index_in_transaction,
803 index_in_caller: index_in_transaction,
804 index_in_callee,
805 is_signer: account_meta.is_signer,
806 is_writable: account_meta.is_writable,
807 });
808 }
809 if program_indices.is_empty() {
810 program_indices.insert(0, transaction_accounts.len() as IndexOfAccount);
811 let processor_account = AccountSharedData::new(0, 0, &native_loader::id());
812 transaction_accounts.push((*loader_id, processor_account));
813 }
814 let pop_epoch_schedule_account = if !transaction_accounts
815 .iter()
816 .any(|(key, _)| *key == sysvar::epoch_schedule::id())
817 {
818 transaction_accounts.push((
819 sysvar::epoch_schedule::id(),
820 create_account_shared_data_for_test(&EpochSchedule::default()),
821 ));
822 true
823 } else {
824 false
825 };
826 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
827 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
828 program_cache_for_tx_batch.replenish(
829 *loader_id,
830 Arc::new(ProgramCacheEntry::new_builtin(0, 0, builtin_function)),
831 );
832 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
833 pre_adjustments(&mut invoke_context);
834 let result = invoke_context.process_instruction(
835 instruction_data,
836 &instruction_accounts,
837 &program_indices,
838 &mut 0,
839 &mut ExecuteTimings::default(),
840 );
841 assert_eq!(result, expected_result);
842 post_adjustments(&mut invoke_context);
843 let mut transaction_accounts = transaction_context.deconstruct_without_keys().unwrap();
844 if pop_epoch_schedule_account {
845 transaction_accounts.pop();
846 }
847 transaction_accounts.pop();
848 transaction_accounts
849}
850
851#[cfg(test)]
852mod tests {
853 use {
854 super::*,
855 serde::{Deserialize, Serialize},
856 solana_account::WritableAccount,
857 solana_compute_budget::compute_budget_limits,
858 solana_instruction::Instruction,
859 solana_rent::Rent,
860 };
861
862 #[derive(Debug, Serialize, Deserialize)]
863 enum MockInstruction {
864 NoopSuccess,
865 NoopFail,
866 ModifyOwned,
867 ModifyNotOwned,
868 ModifyReadonly,
869 UnbalancedPush,
870 UnbalancedPop,
871 ConsumeComputeUnits {
872 compute_units_to_consume: u64,
873 desired_result: Result<(), InstructionError>,
874 },
875 Resize {
876 new_len: u64,
877 },
878 }
879
880 const MOCK_BUILTIN_COMPUTE_UNIT_COST: u64 = 1;
881
882 declare_process_instruction!(
883 MockBuiltin,
884 MOCK_BUILTIN_COMPUTE_UNIT_COST,
885 |invoke_context| {
886 let transaction_context = &invoke_context.transaction_context;
887 let instruction_context = transaction_context.get_current_instruction_context()?;
888 let instruction_data = instruction_context.get_instruction_data();
889 let program_id = instruction_context.get_last_program_key(transaction_context)?;
890 let instruction_accounts = (0..4)
891 .map(|instruction_account_index| InstructionAccount {
892 index_in_transaction: instruction_account_index,
893 index_in_caller: instruction_account_index,
894 index_in_callee: instruction_account_index,
895 is_signer: false,
896 is_writable: false,
897 })
898 .collect::<Vec<_>>();
899 assert_eq!(
900 program_id,
901 instruction_context
902 .try_borrow_instruction_account(transaction_context, 0)?
903 .get_owner()
904 );
905 assert_ne!(
906 instruction_context
907 .try_borrow_instruction_account(transaction_context, 1)?
908 .get_owner(),
909 instruction_context
910 .try_borrow_instruction_account(transaction_context, 0)?
911 .get_key()
912 );
913
914 if let Ok(instruction) = bincode::deserialize(instruction_data) {
915 match instruction {
916 MockInstruction::NoopSuccess => (),
917 MockInstruction::NoopFail => return Err(InstructionError::GenericError),
918 MockInstruction::ModifyOwned => instruction_context
919 .try_borrow_instruction_account(transaction_context, 0)?
920 .set_data_from_slice(&[1])?,
921 MockInstruction::ModifyNotOwned => instruction_context
922 .try_borrow_instruction_account(transaction_context, 1)?
923 .set_data_from_slice(&[1])?,
924 MockInstruction::ModifyReadonly => instruction_context
925 .try_borrow_instruction_account(transaction_context, 2)?
926 .set_data_from_slice(&[1])?,
927 MockInstruction::UnbalancedPush => {
928 instruction_context
929 .try_borrow_instruction_account(transaction_context, 0)?
930 .checked_add_lamports(1)?;
931 let program_id = *transaction_context.get_key_of_account_at_index(3)?;
932 let metas = vec![
933 AccountMeta::new_readonly(
934 *transaction_context.get_key_of_account_at_index(0)?,
935 false,
936 ),
937 AccountMeta::new_readonly(
938 *transaction_context.get_key_of_account_at_index(1)?,
939 false,
940 ),
941 ];
942 let inner_instruction = Instruction::new_with_bincode(
943 program_id,
944 &MockInstruction::NoopSuccess,
945 metas,
946 );
947 invoke_context
948 .transaction_context
949 .get_next_instruction_context()
950 .unwrap()
951 .configure(&[3], &instruction_accounts, &[]);
952 let result = invoke_context.push();
953 assert_eq!(result, Err(InstructionError::UnbalancedInstruction));
954 result?;
955 invoke_context
956 .native_invoke(inner_instruction.into(), &[])
957 .and(invoke_context.pop())?;
958 }
959 MockInstruction::UnbalancedPop => instruction_context
960 .try_borrow_instruction_account(transaction_context, 0)?
961 .checked_add_lamports(1)?,
962 MockInstruction::ConsumeComputeUnits {
963 compute_units_to_consume,
964 desired_result,
965 } => {
966 invoke_context
967 .consume_checked(compute_units_to_consume)
968 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
969 return desired_result;
970 }
971 MockInstruction::Resize { new_len } => instruction_context
972 .try_borrow_instruction_account(transaction_context, 0)?
973 .set_data(vec![0; new_len as usize])?,
974 }
975 } else {
976 return Err(InstructionError::InvalidInstructionData);
977 }
978 Ok(())
979 }
980 );
981
982 #[test]
983 fn test_instruction_stack_height() {
984 let one_more_than_max_depth = ComputeBudget::default()
985 .max_instruction_stack_depth
986 .saturating_add(1);
987 let mut invoke_stack = vec![];
988 let mut transaction_accounts = vec![];
989 let mut instruction_accounts = vec![];
990 for index in 0..one_more_than_max_depth {
991 invoke_stack.push(solana_pubkey::new_rand());
992 transaction_accounts.push((
993 solana_pubkey::new_rand(),
994 AccountSharedData::new(index as u64, 1, invoke_stack.get(index).unwrap()),
995 ));
996 instruction_accounts.push(InstructionAccount {
997 index_in_transaction: index as IndexOfAccount,
998 index_in_caller: index as IndexOfAccount,
999 index_in_callee: instruction_accounts.len() as IndexOfAccount,
1000 is_signer: false,
1001 is_writable: true,
1002 });
1003 }
1004 for (index, program_id) in invoke_stack.iter().enumerate() {
1005 transaction_accounts.push((
1006 *program_id,
1007 AccountSharedData::new(1, 1, &solana_pubkey::Pubkey::default()),
1008 ));
1009 instruction_accounts.push(InstructionAccount {
1010 index_in_transaction: index as IndexOfAccount,
1011 index_in_caller: index as IndexOfAccount,
1012 index_in_callee: index as IndexOfAccount,
1013 is_signer: false,
1014 is_writable: false,
1015 });
1016 }
1017 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1018
1019 let mut depth_reached = 0;
1021 for _ in 0..invoke_stack.len() {
1022 invoke_context
1023 .transaction_context
1024 .get_next_instruction_context()
1025 .unwrap()
1026 .configure(
1027 &[one_more_than_max_depth.saturating_add(depth_reached) as IndexOfAccount],
1028 &instruction_accounts,
1029 &[],
1030 );
1031 if Err(InstructionError::CallDepth) == invoke_context.push() {
1032 break;
1033 }
1034 depth_reached = depth_reached.saturating_add(1);
1035 }
1036 assert_ne!(depth_reached, 0);
1037 assert!(depth_reached < one_more_than_max_depth);
1038 }
1039
1040 #[test]
1041 fn test_max_instruction_trace_length() {
1042 const MAX_INSTRUCTIONS: usize = 8;
1043 let mut transaction_context =
1044 TransactionContext::new(Vec::new(), Rent::default(), 1, MAX_INSTRUCTIONS);
1045 for _ in 0..MAX_INSTRUCTIONS {
1046 transaction_context.push().unwrap();
1047 transaction_context.pop().unwrap();
1048 }
1049 assert_eq!(
1050 transaction_context.push(),
1051 Err(InstructionError::MaxInstructionTraceLengthExceeded)
1052 );
1053 }
1054
1055 #[test]
1056 fn test_process_instruction() {
1057 let callee_program_id = solana_pubkey::new_rand();
1058 let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1059 let not_owned_account = AccountSharedData::new(84, 1, &solana_pubkey::new_rand());
1060 let readonly_account = AccountSharedData::new(168, 1, &solana_pubkey::new_rand());
1061 let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1062 let mut program_account = AccountSharedData::new(1, 1, &native_loader::id());
1063 program_account.set_executable(true);
1064 let transaction_accounts = vec![
1065 (solana_pubkey::new_rand(), owned_account),
1066 (solana_pubkey::new_rand(), not_owned_account),
1067 (solana_pubkey::new_rand(), readonly_account),
1068 (callee_program_id, program_account),
1069 (solana_pubkey::new_rand(), loader_account),
1070 ];
1071 let metas = vec![
1072 AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1073 AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1074 AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1075 ];
1076 let instruction_accounts = (0..4)
1077 .map(|instruction_account_index| InstructionAccount {
1078 index_in_transaction: instruction_account_index,
1079 index_in_caller: instruction_account_index,
1080 index_in_callee: instruction_account_index,
1081 is_signer: false,
1082 is_writable: instruction_account_index < 2,
1083 })
1084 .collect::<Vec<_>>();
1085 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1086 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1087 program_cache_for_tx_batch.replenish(
1088 callee_program_id,
1089 Arc::new(ProgramCacheEntry::new_builtin(0, 1, MockBuiltin::vm)),
1090 );
1091 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1092
1093 let cases = vec![
1095 (MockInstruction::NoopSuccess, Ok(())),
1096 (
1097 MockInstruction::NoopFail,
1098 Err(InstructionError::GenericError),
1099 ),
1100 (MockInstruction::ModifyOwned, Ok(())),
1101 (
1102 MockInstruction::ModifyNotOwned,
1103 Err(InstructionError::ExternalAccountDataModified),
1104 ),
1105 (
1106 MockInstruction::ModifyReadonly,
1107 Err(InstructionError::ReadonlyDataModified),
1108 ),
1109 (
1110 MockInstruction::UnbalancedPush,
1111 Err(InstructionError::UnbalancedInstruction),
1112 ),
1113 (
1114 MockInstruction::UnbalancedPop,
1115 Err(InstructionError::UnbalancedInstruction),
1116 ),
1117 ];
1118 for case in cases {
1119 invoke_context
1120 .transaction_context
1121 .get_next_instruction_context()
1122 .unwrap()
1123 .configure(&[4], &instruction_accounts, &[]);
1124 invoke_context.push().unwrap();
1125 let inner_instruction =
1126 Instruction::new_with_bincode(callee_program_id, &case.0, metas.clone());
1127 let result = invoke_context
1128 .native_invoke(inner_instruction.into(), &[])
1129 .and(invoke_context.pop());
1130 assert_eq!(result, case.1);
1131 }
1132
1133 let compute_units_to_consume = 10;
1135 let expected_results = vec![Ok(()), Err(InstructionError::GenericError)];
1136 for expected_result in expected_results {
1137 invoke_context
1138 .transaction_context
1139 .get_next_instruction_context()
1140 .unwrap()
1141 .configure(&[4], &instruction_accounts, &[]);
1142 invoke_context.push().unwrap();
1143 let inner_instruction = Instruction::new_with_bincode(
1144 callee_program_id,
1145 &MockInstruction::ConsumeComputeUnits {
1146 compute_units_to_consume,
1147 desired_result: expected_result.clone(),
1148 },
1149 metas.clone(),
1150 );
1151 let inner_instruction = StableInstruction::from(inner_instruction);
1152 let (inner_instruction_accounts, program_indices) = invoke_context
1153 .prepare_instruction(&inner_instruction, &[])
1154 .unwrap();
1155
1156 let mut compute_units_consumed = 0;
1157 let result = invoke_context.process_instruction(
1158 &inner_instruction.data,
1159 &inner_instruction_accounts,
1160 &program_indices,
1161 &mut compute_units_consumed,
1162 &mut ExecuteTimings::default(),
1163 );
1164
1165 assert!(compute_units_consumed > 0);
1169 assert_eq!(
1170 compute_units_consumed,
1171 compute_units_to_consume.saturating_add(MOCK_BUILTIN_COMPUTE_UNIT_COST),
1172 );
1173 assert_eq!(result, expected_result);
1174
1175 invoke_context.pop().unwrap();
1176 }
1177 }
1178
1179 #[test]
1180 fn test_invoke_context_compute_budget() {
1181 let transaction_accounts = vec![(solana_pubkey::new_rand(), AccountSharedData::default())];
1182
1183 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1184 invoke_context.compute_budget = ComputeBudget::new(
1185 compute_budget_limits::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64,
1186 );
1187
1188 invoke_context
1189 .transaction_context
1190 .get_next_instruction_context()
1191 .unwrap()
1192 .configure(&[0], &[], &[]);
1193 invoke_context.push().unwrap();
1194 assert_eq!(
1195 *invoke_context.get_compute_budget(),
1196 ComputeBudget::new(
1197 compute_budget_limits::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64
1198 )
1199 );
1200 invoke_context.pop().unwrap();
1201 }
1202
1203 #[test]
1204 fn test_process_instruction_accounts_resize_delta() {
1205 let program_key = Pubkey::new_unique();
1206 let user_account_data_len = 123u64;
1207 let user_account =
1208 AccountSharedData::new(100, user_account_data_len as usize, &program_key);
1209 let dummy_account = AccountSharedData::new(10, 0, &program_key);
1210 let mut program_account = AccountSharedData::new(500, 500, &native_loader::id());
1211 program_account.set_executable(true);
1212 let transaction_accounts = vec![
1213 (Pubkey::new_unique(), user_account),
1214 (Pubkey::new_unique(), dummy_account),
1215 (program_key, program_account),
1216 ];
1217 let instruction_accounts = [
1218 InstructionAccount {
1219 index_in_transaction: 0,
1220 index_in_caller: 0,
1221 index_in_callee: 0,
1222 is_signer: false,
1223 is_writable: true,
1224 },
1225 InstructionAccount {
1226 index_in_transaction: 1,
1227 index_in_caller: 1,
1228 index_in_callee: 1,
1229 is_signer: false,
1230 is_writable: false,
1231 },
1232 ];
1233 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1234 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1235 program_cache_for_tx_batch.replenish(
1236 program_key,
1237 Arc::new(ProgramCacheEntry::new_builtin(0, 0, MockBuiltin::vm)),
1238 );
1239 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1240
1241 {
1243 let resize_delta: i64 = 0;
1244 let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
1245 let instruction_data =
1246 bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1247
1248 let result = invoke_context.process_instruction(
1249 &instruction_data,
1250 &instruction_accounts,
1251 &[2],
1252 &mut 0,
1253 &mut ExecuteTimings::default(),
1254 );
1255
1256 assert!(result.is_ok());
1257 assert_eq!(
1258 invoke_context
1259 .transaction_context
1260 .accounts_resize_delta()
1261 .unwrap(),
1262 resize_delta
1263 );
1264 }
1265
1266 {
1268 let resize_delta: i64 = 1;
1269 let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
1270 let instruction_data =
1271 bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1272
1273 let result = invoke_context.process_instruction(
1274 &instruction_data,
1275 &instruction_accounts,
1276 &[2],
1277 &mut 0,
1278 &mut ExecuteTimings::default(),
1279 );
1280
1281 assert!(result.is_ok());
1282 assert_eq!(
1283 invoke_context
1284 .transaction_context
1285 .accounts_resize_delta()
1286 .unwrap(),
1287 resize_delta
1288 );
1289 }
1290
1291 {
1293 let resize_delta: i64 = -1;
1294 let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
1295 let instruction_data =
1296 bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1297
1298 let result = invoke_context.process_instruction(
1299 &instruction_data,
1300 &instruction_accounts,
1301 &[2],
1302 &mut 0,
1303 &mut ExecuteTimings::default(),
1304 );
1305
1306 assert!(result.is_ok());
1307 assert_eq!(
1308 invoke_context
1309 .transaction_context
1310 .accounts_resize_delta()
1311 .unwrap(),
1312 resize_delta
1313 );
1314 }
1315 }
1316}