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