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