1#[cfg(feature = "dev-context-only-utils")]
2use {
3 crate::program_cache_entry::ProgramCacheEntry,
4 solana_account::{AccountSharedData, WritableAccount, create_account_shared_data_for_test},
5 solana_epoch_schedule::EpochSchedule,
6 solana_instruction::AccountMeta,
7 solana_message::{LegacyMessage, Message, SanitizedMessage},
8 solana_sdk_ids::sysvar,
9 solana_transaction_context::transaction_accounts::KeyedAccountSharedData,
10 std::collections::{HashMap, HashSet},
11};
12use {
13 crate::{
14 execution_budget::{SVMTransactionExecutionBudget, SVMTransactionExecutionCost},
15 loaded_programs::{
16 ProgramCacheForTxBatch, ProgramRuntimeEnvironment, ProgramRuntimeEnvironments,
17 },
18 memory_context::{MemoryContext, MemoryContexts},
19 program_cache_entry::ProgramCacheEntryType,
20 stable_log,
21 sysvar_cache::SysvarCache,
22 },
23 solana_hash::Hash,
24 solana_instruction::{Instruction, error::InstructionError},
25 solana_pubkey::Pubkey,
26 solana_sbpf::{
27 ebpf::MM_HEAP_START,
28 elf::{ElfError, Executable as GenericExecutable},
29 error::{EbpfError, ProgramResult},
30 memory_region::MemoryMapping,
31 program::{BuiltinProgram, SBPFVersion},
32 vm::{Config, ContextObject, EbpfVm},
33 },
34 solana_sdk_ids::{
35 bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, loader_v4, native_loader,
36 },
37 solana_svm_callback::InvokeContextCallback,
38 solana_svm_feature_set::SVMFeatureSet,
39 solana_svm_log_collector::{LogCollector, ic_msg},
40 solana_svm_measure::measure::Measure,
41 solana_svm_timings::{ExecuteDetailsTimings, ExecuteTimings},
42 solana_svm_transaction::svm_message::SVMMessage,
43 solana_svm_type_overrides::sync::Arc,
44 solana_transaction_context::{
45 IndexOfAccount, MAX_ACCOUNTS_PER_TRANSACTION, instruction::InstructionContext,
46 instruction_accounts::InstructionAccount, transaction::TransactionContext,
47 },
48 std::{
49 alloc::Layout,
50 borrow::Cow,
51 cell::{Cell, RefCell},
52 fmt::{self, Debug},
53 ptr,
54 rc::Rc,
55 time::Duration,
56 },
57};
58
59pub type BuiltinFunctionRegisterer =
60 fn(&mut BuiltinProgram<InvokeContext<'static, 'static>>, &str) -> Result<(), ElfError>;
61pub type Executable = GenericExecutable<InvokeContext<'static, 'static>>;
62pub type RegisterTrace<'a> = &'a [[u64; 12]];
63
64#[macro_export]
66macro_rules! declare_process_instruction {
67 ($process_instruction:ident, $cu_to_consume:expr, |$invoke_context:ident| $inner:tt) => {
68 $crate::solana_sbpf::declare_builtin_function!(
69 $process_instruction,
70 fn rust(
71 invoke_context: &mut $crate::invoke_context::InvokeContext<'_, '_>,
72 _arg0: u64,
73 _arg1: u64,
74 _arg2: u64,
75 _arg3: u64,
76 _arg4: u64,
77 ) -> Result<u64, Box<dyn std::error::Error>> {
78 fn process_instruction_inner(
79 $invoke_context: &mut $crate::invoke_context::InvokeContext,
80 ) -> std::result::Result<(), $crate::__private::InstructionError>
81 $inner
82
83 let consumption_result = if $cu_to_consume > 0
84 {
85 invoke_context.compute_meter.consume_checked($cu_to_consume)
86 } else {
87 Ok(())
88 };
89 consumption_result
90 .and_then(|_| {
91 process_instruction_inner(invoke_context)
92 .map(|_| 0)
93 .map_err(|err| Box::new(err) as Box<dyn std::error::Error>)
94 })
95 .into()
96 }
97 );
98 };
99}
100
101impl ContextObject for InvokeContext<'_, '_> {
102 fn consume(&mut self, amount: u64) {
103 let compute_meter = self.compute_meter.0.get();
106 self.compute_meter
107 .0
108 .set(compute_meter.saturating_sub(amount));
109 }
110
111 fn get_remaining(&self) -> u64 {
112 self.compute_meter.0.get()
113 }
114
115 fn active_mapping_ptr(&mut self) -> ptr::NonNull<MemoryMapping> {
116 let memory = self
117 .memory_contexts
118 .memory_mapping_mut()
119 .expect("The memory context must have been set for the current instruction");
120 ptr::NonNull::from_mut(memory)
121 }
122}
123
124#[derive(Clone, PartialEq, Eq, Debug)]
125pub struct AllocErr;
126impl fmt::Display for AllocErr {
127 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
128 f.write_str("Error: Memory allocation failed")
129 }
130}
131
132pub struct BpfAllocator {
133 len: u64,
134 pos: u64,
135}
136
137impl BpfAllocator {
138 pub fn new(len: u64) -> Self {
139 Self { len, pos: 0 }
140 }
141
142 pub fn alloc(&mut self, layout: Layout) -> Result<u64, AllocErr> {
143 let bytes_to_align = (self.pos as *const u8).align_offset(layout.align()) as u64;
144 if self
145 .pos
146 .saturating_add(bytes_to_align)
147 .saturating_add(layout.size() as u64)
148 <= self.len
149 {
150 self.pos = self.pos.saturating_add(bytes_to_align);
151 let addr = MM_HEAP_START.saturating_add(self.pos);
152 self.pos = self.pos.saturating_add(layout.size() as u64);
153 Ok(addr)
154 } else {
155 Err(AllocErr)
156 }
157 }
158}
159
160pub struct EnvironmentConfig<'a> {
161 pub blockhash: Hash,
162 pub blockhash_lamports_per_signature: u64,
163 alpenglow_migration_succeeded: bool,
164 epoch_stake_callback: &'a dyn InvokeContextCallback,
165 feature_set: &'a SVMFeatureSet,
166 program_runtime_environments: &'a ProgramRuntimeEnvironments,
167 sysvar_cache: &'a SysvarCache,
168}
169impl<'a> EnvironmentConfig<'a> {
170 pub fn new(
171 blockhash: Hash,
172 blockhash_lamports_per_signature: u64,
173 alpenglow_migration_succeeded: bool,
174 epoch_stake_callback: &'a dyn InvokeContextCallback,
175 feature_set: &'a SVMFeatureSet,
176 program_runtime_environments: &'a ProgramRuntimeEnvironments,
177 sysvar_cache: &'a SysvarCache,
178 ) -> Self {
179 Self {
180 blockhash,
181 blockhash_lamports_per_signature,
182 alpenglow_migration_succeeded,
183 epoch_stake_callback,
184 feature_set,
185 program_runtime_environments,
186 sysvar_cache,
187 }
188 }
189
190 pub fn sysvar_cache(&self) -> &SysvarCache {
192 self.sysvar_cache
193 }
194}
195
196pub struct ComputeMeter(Cell<u64>);
197
198impl ComputeMeter {
199 pub fn consume_checked(&self, amount: u64) -> Result<(), Box<dyn std::error::Error>> {
201 let compute_meter = self.0.get();
202 let exceeded = compute_meter < amount;
203 self.0.set(compute_meter.saturating_sub(amount));
204 if exceeded {
205 return Err(Box::new(InstructionError::ComputationalBudgetExceeded));
206 }
207 Ok(())
208 }
209
210 #[cfg(feature = "dev-context-only-utils")]
214 pub fn mock_set_remaining(&self, remaining: u64) {
215 self.0.set(remaining);
216 }
217}
218
219pub struct InvokeContext<'a, 'ix_data> {
221 pub transaction_context: &'a mut TransactionContext<'ix_data>,
223 pub program_cache_for_tx_batch: &'a mut ProgramCacheForTxBatch,
225 pub environment_config: EnvironmentConfig<'a>,
227 compute_budget: SVMTransactionExecutionBudget,
229 execution_cost: SVMTransactionExecutionCost,
231 pub compute_meter: ComputeMeter,
234 log_collector: Option<Rc<RefCell<LogCollector>>>,
235 pub total_nested_exec_time: Duration,
237 pub timings: ExecuteDetailsTimings,
238 pub memory_contexts: MemoryContexts,
239 register_traces: Vec<(usize, Vec<[u64; 12]>)>,
241 #[cfg(feature = "sbpf-debugger")]
243 pub debug_port: Option<u16>,
244}
245
246impl<'a, 'ix_data> InvokeContext<'a, 'ix_data> {
247 pub fn new(
248 transaction_context: &'a mut TransactionContext<'ix_data>,
249 program_cache_for_tx_batch: &'a mut ProgramCacheForTxBatch,
250 environment_config: EnvironmentConfig<'a>,
251 log_collector: Option<Rc<RefCell<LogCollector>>>,
252 compute_budget: SVMTransactionExecutionBudget,
253 execution_cost: SVMTransactionExecutionCost,
254 ) -> Self {
255 Self {
256 transaction_context,
257 program_cache_for_tx_batch,
258 environment_config,
259 log_collector,
260 compute_budget,
261 execution_cost,
262 compute_meter: ComputeMeter(Cell::new(compute_budget.compute_unit_limit)),
263 total_nested_exec_time: Duration::ZERO,
264 timings: ExecuteDetailsTimings::default(),
265 memory_contexts: MemoryContexts::new(),
266 register_traces: Vec::new(),
267 #[cfg(feature = "sbpf-debugger")]
268 debug_port: None,
269 }
270 }
271
272 pub fn push(&mut self) -> Result<(), InstructionError> {
274 let instruction_context = self.transaction_context.get_next_instruction_context()?;
275 let program_id = instruction_context
276 .get_program_key()
277 .map_err(|_| InstructionError::UnsupportedProgramId)?;
278 if self.transaction_context.get_instruction_stack_height() != 0 {
279 let contains =
280 (0..self.transaction_context.get_instruction_stack_height()).any(|level| {
281 self.transaction_context
282 .get_instruction_context_at_nesting_level(level)
283 .and_then(|instruction_context| instruction_context.get_program_key())
284 .map(|program_key| program_key == program_id)
285 .unwrap_or(false)
286 });
287 let is_last = self
288 .transaction_context
289 .get_current_instruction_context()
290 .and_then(|instruction_context| instruction_context.get_program_key())
291 .map(|program_key| program_key == program_id)
292 .unwrap_or(false);
293 if contains && !is_last {
294 return Err(InstructionError::ReentrancyNotAllowed);
296 }
297 }
298
299 self.transaction_context.push()?;
300 self.memory_contexts.push_placeholder();
301 Ok(())
302 }
303
304 pub fn pop(&mut self) -> Result<(), InstructionError> {
306 self.memory_contexts.pop();
307 self.transaction_context.pop()
308 }
309
310 pub fn get_stack_height(&self) -> usize {
313 self.transaction_context.get_instruction_stack_height()
314 }
315
316 pub fn native_invoke_signed(
323 &mut self,
324 instruction: Instruction,
325 signer_seeds: &[&[&[u8]]],
326 ) -> Result<(), InstructionError> {
327 let caller_program_id = *self
328 .transaction_context
329 .get_current_instruction_context()?
330 .get_program_key()?;
331 let signers = signer_seeds
334 .iter()
335 .map(|seeds| Pubkey::create_program_address(seeds, &caller_program_id))
336 .collect::<Result<Vec<Pubkey>, solana_pubkey::PubkeyError>>()
337 .map_err(|e| e as u64)?;
338 self.prepare_next_cpi_instruction(instruction, &signers)?;
339 let mut compute_units_consumed = 0;
340 self.process_instruction(&mut compute_units_consumed, &mut ExecuteTimings::default())?;
341 Ok(())
342 }
343
344 pub fn prepare_next_cpi_instruction(
347 &mut self,
348 instruction: Instruction,
349 signers: &[Pubkey],
350 ) -> Result<(), InstructionError> {
351 let mut transaction_callee_map: Vec<u16> = vec![u16::MAX; MAX_ACCOUNTS_PER_TRANSACTION];
353 let mut instruction_accounts: Vec<InstructionAccount> =
354 Vec::with_capacity(instruction.accounts.len());
355
356 let program_account_index = {
360 let instruction_context = self.transaction_context.get_current_instruction_context()?;
361
362 for account_meta in instruction.accounts.iter() {
363 let index_in_transaction = self
364 .transaction_context
365 .find_index_of_account(&account_meta.pubkey)
366 .ok_or_else(|| {
367 ic_msg!(
368 self,
369 "Instruction references an unknown account {}",
370 account_meta.pubkey,
371 );
372 InstructionError::MissingAccount
373 })?;
374
375 debug_assert!((index_in_transaction as usize) < transaction_callee_map.len());
376 let index_in_callee = transaction_callee_map
377 .get_mut(index_in_transaction as usize)
378 .unwrap();
379
380 if (*index_in_callee as usize) < instruction_accounts.len() {
381 let cloned_account = {
382 let instruction_account = instruction_accounts
383 .get_mut(*index_in_callee as usize)
384 .ok_or(InstructionError::MissingAccount)?;
385 instruction_account.set_is_signer(
386 instruction_account.is_signer() || account_meta.is_signer,
387 );
388 instruction_account.set_is_writable(
389 instruction_account.is_writable() || account_meta.is_writable,
390 );
391 *instruction_account
392 };
393 instruction_accounts.push(cloned_account);
394 } else {
395 *index_in_callee = instruction_accounts.len() as u16;
396 instruction_accounts.push(InstructionAccount::new(
397 index_in_transaction,
398 account_meta.is_signer,
399 account_meta.is_writable,
400 ));
401 }
402 }
403
404 for current_index in 0..instruction_accounts.len() {
405 let instruction_account = instruction_accounts.get(current_index).unwrap();
406 let index_in_callee = *transaction_callee_map
407 .get(instruction_account.index_in_transaction as usize)
408 .unwrap() as usize;
409
410 if current_index != index_in_callee {
411 let (is_signer, is_writable) = {
412 let reference_account = instruction_accounts
413 .get(index_in_callee)
414 .ok_or(InstructionError::MissingAccount)?;
415 (
416 reference_account.is_signer(),
417 reference_account.is_writable(),
418 )
419 };
420
421 let current_account = instruction_accounts.get_mut(current_index).unwrap();
422 current_account.set_is_signer(current_account.is_signer() || is_signer);
423 current_account.set_is_writable(current_account.is_writable() || is_writable);
424 continue;
426 }
427
428 let index_in_caller = instruction_context.get_index_of_account_in_instruction(
429 instruction_account.index_in_transaction,
430 )?;
431
432 let account_key = &instruction.accounts.get(current_index).unwrap().pubkey;
434 let caller_instruction_account = instruction_context
436 .instruction_accounts()
437 .get(index_in_caller as usize)
438 .unwrap();
439
440 if instruction_account.is_writable() && !caller_instruction_account.is_writable() {
442 ic_msg!(self, "{}'s writable privilege escalated", account_key,);
443 return Err(InstructionError::PrivilegeEscalation);
444 }
445
446 if instruction_account.is_signer()
449 && !(caller_instruction_account.is_signer() || signers.contains(account_key))
450 {
451 ic_msg!(self, "{}'s signer privilege escalated", account_key,);
452 return Err(InstructionError::PrivilegeEscalation);
453 }
454 }
455
456 let callee_program_id = &instruction.program_id;
458 let program_account_index_in_transaction = self
459 .transaction_context
460 .find_index_of_account(callee_program_id);
461 let program_account_index_in_instruction = program_account_index_in_transaction
462 .map(|index| instruction_context.get_index_of_account_in_instruction(index));
463
464 if program_account_index_in_instruction.is_none()
467 || program_account_index_in_instruction.unwrap().is_err()
468 {
469 ic_msg!(self, "Unknown program {}", callee_program_id);
470 return Err(InstructionError::MissingAccount);
471 }
472
473 program_account_index_in_transaction.unwrap()
476 };
477
478 let caller_index = self.transaction_context.get_current_instruction_index()?;
481 self.transaction_context.configure_instruction_at_index(
482 self.transaction_context.get_instruction_trace_length(),
483 program_account_index,
484 instruction_accounts,
485 transaction_callee_map,
486 Cow::Owned(instruction.data),
487 Some(caller_index as u16),
488 )?;
489 Ok(())
490 }
491
492 pub fn prepare_top_level_instructions(
494 &mut self,
495 message: &'ix_data impl SVMMessage,
496 ) -> Result<(), (u8, InstructionError)> {
497 for (top_level_instruction_index, (_, instruction)) in
498 message.program_instructions_iter().enumerate()
499 {
500 let mut transaction_callee_map: Vec<u16> = vec![u16::MAX; MAX_ACCOUNTS_PER_TRANSACTION];
501
502 let mut instruction_accounts: Vec<InstructionAccount> =
503 Vec::with_capacity(instruction.accounts.len());
504 for index_in_transaction in instruction.accounts.iter() {
505 let index_in_callee = transaction_callee_map
506 .get_mut(*index_in_transaction as usize)
507 .expect("Invalid index in transaction");
508
509 if (*index_in_callee as usize) > instruction_accounts.len() {
510 *index_in_callee = instruction_accounts.len() as u16;
511 }
512
513 let index_in_transaction = *index_in_transaction as usize;
514 instruction_accounts.push(InstructionAccount::new(
515 index_in_transaction as IndexOfAccount,
516 message.is_signer(index_in_transaction),
517 message.is_writable(index_in_transaction),
518 ));
519 }
520
521 self.transaction_context
522 .configure_instruction_at_index(
523 top_level_instruction_index,
524 instruction.program_id_index as u16,
525 instruction_accounts,
526 transaction_callee_map,
527 Cow::Borrowed(instruction.data),
528 None,
529 )
530 .map_err(|err| (top_level_instruction_index as u8, err))?;
531 }
532 Ok(())
533 }
534
535 pub fn process_instruction(
537 &mut self,
538 compute_units_consumed: &mut u64,
539 timings: &mut ExecuteTimings,
540 ) -> Result<(), InstructionError> {
541 *compute_units_consumed = 0;
542 self.push()?;
543 self.process_executable_chain(compute_units_consumed, timings)
544 .and(self.pop())
547 }
548
549 pub fn process_precompile(
551 &mut self,
552 program_id: &Pubkey,
553 instruction_data: &[u8],
554 message_instruction_datas_iter: impl Iterator<Item = &'ix_data [u8]>,
555 ) -> Result<(), InstructionError> {
556 self.push()?;
557 let instruction_datas: Vec<_> = message_instruction_datas_iter.collect();
558 self.environment_config
559 .epoch_stake_callback
560 .process_precompile(program_id, instruction_data, instruction_datas)
561 .map_err(InstructionError::from)
562 .and(self.pop())
563 }
564
565 fn process_executable_chain(
567 &mut self,
568 compute_units_consumed: &mut u64,
569 timings: &mut ExecuteTimings,
570 ) -> Result<(), InstructionError> {
571 let instruction_context = self.transaction_context.get_current_instruction_context()?;
572 let process_executable_chain_time = Measure::start("process_executable_chain_time");
573
574 let builtin_id = {
575 let owner_id = instruction_context.get_program_owner()?;
576 if native_loader::check_id(&owner_id) {
577 *instruction_context.get_program_key()?
578 } else if bpf_loader_deprecated::check_id(&owner_id)
579 || bpf_loader::check_id(&owner_id)
580 || bpf_loader_upgradeable::check_id(&owner_id)
581 || loader_v4::check_id(&owner_id)
582 {
583 owner_id
584 } else {
585 return Err(InstructionError::UnsupportedProgramId);
586 }
587 };
588
589 const ENTRYPOINT_KEY: u32 = 0x71E3CF81;
591 let entry = self
592 .program_cache_for_tx_batch
593 .find(&builtin_id)
594 .ok_or(InstructionError::UnsupportedProgramId)?;
595 let function = match &entry.program {
596 ProgramCacheEntryType::Builtin(program) => program
597 .get_function_registry()
598 .lookup_by_key(ENTRYPOINT_KEY)
599 .map(|(_name, (function, _codegen))| function),
600 _ => None,
601 }
602 .ok_or(InstructionError::UnsupportedProgramId)?;
603
604 let program_id = *instruction_context.get_program_key()?;
605 self.transaction_context
606 .set_return_data(program_id, Vec::new())?;
607 let logger = self.get_log_collector();
608 stable_log::program_invoke(&logger, &program_id, self.get_stack_height());
609 let pre_remaining_units = self.get_remaining();
610 self.memory_contexts
612 .set_memory_context_abi_v1(MemoryContext::new(
613 BpfAllocator::new(0),
614 Vec::new(),
615 unsafe {
618 MemoryMapping::new(Vec::new(), &Config::default(), SBPFVersion::Reserved)
619 .unwrap()
620 },
621 ))?;
622 let mut vm = EbpfVm::new(
623 Arc::clone(
624 &**self
625 .environment_config
626 .program_runtime_environments
627 .get_env_for_execution(),
628 ),
629 SBPFVersion::V0,
630 unsafe { std::mem::transmute::<&mut InvokeContext, &mut InvokeContext>(self) },
632 0,
633 );
634 vm.invoke_function(function);
635 let result = match vm.program_result {
636 ProgramResult::Ok(_) => {
637 stable_log::program_success(&logger, &program_id);
638 Ok(())
639 }
640 ProgramResult::Err(ref err) => {
641 if let EbpfError::SyscallError(syscall_error) = err {
642 if let Some(instruction_err) = syscall_error.downcast_ref::<InstructionError>()
643 {
644 stable_log::program_failure(&logger, &program_id, instruction_err);
645 Err(instruction_err.clone())
646 } else {
647 stable_log::program_failure(&logger, &program_id, syscall_error);
648 Err(InstructionError::ProgramFailedToComplete)
649 }
650 } else {
651 stable_log::program_failure(&logger, &program_id, err);
652 Err(InstructionError::ProgramFailedToComplete)
653 }
654 }
655 };
656 let post_remaining_units = self.get_remaining();
657 *compute_units_consumed = pre_remaining_units.saturating_sub(post_remaining_units);
658
659 if builtin_id == program_id && result.is_ok() && *compute_units_consumed == 0 {
660 return Err(InstructionError::BuiltinProgramsMustConsumeComputeUnits);
661 }
662
663 timings
664 .execute_accessories
665 .process_instructions
666 .process_executable_chain_us += process_executable_chain_time.end_as_us();
667 result
668 }
669
670 pub fn get_log_collector(&self) -> Option<Rc<RefCell<LogCollector>>> {
672 self.log_collector.clone()
673 }
674
675 #[cfg(feature = "dev-context-only-utils")]
676 pub fn set_alpenglow_migration_succeeded_for_tests(&mut self, succeeded: bool) {
677 self.environment_config.alpenglow_migration_succeeded = succeeded;
678 }
679
680 pub fn get_compute_budget(&self) -> &SVMTransactionExecutionBudget {
682 &self.compute_budget
683 }
684
685 pub fn get_execution_cost(&self) -> &SVMTransactionExecutionCost {
687 &self.execution_cost
688 }
689
690 pub fn get_feature_set(&self) -> &SVMFeatureSet {
692 self.environment_config.feature_set
693 }
694
695 pub fn get_program_runtime_environment_for_deployment(&self) -> &ProgramRuntimeEnvironment {
696 self.environment_config
697 .program_runtime_environments
698 .get_env_for_deployment()
699 }
700
701 pub fn is_deprecate_legacy_vote_ixs_active(&self) -> bool {
702 self.environment_config
703 .feature_set
704 .deprecate_legacy_vote_ixs
705 }
706
707 pub fn is_alpenglow_migration_succeeded(&self) -> bool {
708 self.environment_config.alpenglow_migration_succeeded
709 }
710
711 pub fn get_epoch_stake(&self) -> u64 {
713 self.environment_config
714 .epoch_stake_callback
715 .get_epoch_stake()
716 }
717
718 pub fn get_epoch_stake_for_vote_account(&self, pubkey: &'a Pubkey) -> u64 {
720 self.environment_config
721 .epoch_stake_callback
722 .get_epoch_stake_for_vote_account(pubkey)
723 }
724
725 pub fn is_precompile(&self, pubkey: &Pubkey) -> bool {
726 self.environment_config
727 .epoch_stake_callback
728 .is_precompile(pubkey)
729 }
730
731 pub fn get_check_aligned(&self) -> bool {
733 self.transaction_context
734 .get_current_instruction_context()
735 .and_then(|instruction_context| {
736 let owner_id = instruction_context.get_program_owner();
737 debug_assert!(owner_id.is_ok());
738 owner_id
739 })
740 .map(|owner_key| owner_key != bpf_loader_deprecated::id())
741 .unwrap_or(true)
742 }
743
744 pub fn insert_register_trace(&mut self, register_trace: Vec<[u64; 12]>) {
746 if register_trace.is_empty() {
747 return;
748 }
749 let Ok(instruction_context) = self.transaction_context.get_current_instruction_context()
750 else {
751 return;
752 };
753 self.register_traces
754 .push((instruction_context.get_index_in_trace(), register_trace));
755 }
756
757 pub fn iterate_vm_traces(
759 &self,
760 callback: &dyn Fn(InstructionContext, &Executable, RegisterTrace),
761 ) {
762 for (index_in_trace, register_trace) in &self.register_traces {
763 let Ok(instruction_context) = self
764 .transaction_context
765 .get_instruction_context_at_index_in_trace(*index_in_trace)
766 else {
767 continue;
768 };
769 let Ok(program_id) = instruction_context.get_program_key() else {
770 continue;
771 };
772 let Some(entry) = self.program_cache_for_tx_batch.find(program_id) else {
773 continue;
774 };
775 let ProgramCacheEntryType::Loaded(ref executable) = entry.program else {
776 continue;
777 };
778 callback(instruction_context, executable, register_trace.as_slice());
779 }
780 }
781}
782
783#[cfg(feature = "dev-context-only-utils")]
784#[macro_export]
785macro_rules! with_mock_invoke_context_with_feature_set {
786 (
787 $invoke_context:ident,
788 $transaction_context:ident,
789 $feature_set:ident,
790 $top_level_instructions:literal,
791 $transaction_accounts:expr,
792 $all_accounts:expr $(,)?
793 ) => {
794 use {
795 solana_svm_callback::InvokeContextCallback,
796 solana_svm_log_collector::LogCollector,
797 $crate::{
798 __private::{Hash, ReadableAccount, Rent, TransactionContext},
799 execution_budget::{SVMTransactionExecutionBudget, SVMTransactionExecutionCost},
800 invoke_context::{EnvironmentConfig, InvokeContext},
801 loaded_programs::{ProgramCacheForTxBatch, ProgramRuntimeEnvironments},
802 sysvar_cache::SysvarCache,
803 },
804 };
805
806 struct MockInvokeContextCallback {}
807 impl InvokeContextCallback for MockInvokeContextCallback {}
808
809 let compute_budget = SVMTransactionExecutionBudget::new_with_defaults(
810 $feature_set.raise_cpi_nesting_limit_to_8,
811 );
812 let mut sysvar_cache = SysvarCache::default();
813 sysvar_cache.fill_missing_entries(|pubkey, callback| {
814 for (key, account) in $all_accounts.iter() {
815 if key == pubkey {
816 callback(account.data());
817 }
818 }
819 });
820 let mut $transaction_context = TransactionContext::new(
821 $transaction_accounts,
822 Rent::default(),
823 compute_budget.max_instruction_stack_depth,
824 compute_budget.max_instruction_trace_length,
825 $top_level_instructions,
826 );
827 let program_runtime_environments = ProgramRuntimeEnvironments::mock();
828 let environment_config = EnvironmentConfig::new(
829 Hash::default(),
830 0,
831 false,
832 &MockInvokeContextCallback {},
833 $feature_set,
834 &program_runtime_environments,
835 &sysvar_cache,
836 );
837 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
838 let mut $invoke_context = InvokeContext::new(
839 &mut $transaction_context,
840 &mut program_cache_for_tx_batch,
841 environment_config,
842 Some(LogCollector::new_ref()),
843 compute_budget,
844 SVMTransactionExecutionCost::default(),
845 );
846 };
847 (
848 $invoke_context:ident,
849 $transaction_context:ident,
850 $feature_set:ident,
851 $top_level_instructions:literal,
852 $transaction_accounts:expr $(,)?
853 ) => {
854 let transaction_accounts: Vec<(solana_pubkey::Pubkey, solana_account::AccountSharedData)> =
855 $transaction_accounts;
856 $crate::with_mock_invoke_context_with_feature_set!(
857 $invoke_context,
858 $transaction_context,
859 $feature_set,
860 $top_level_instructions,
861 transaction_accounts,
862 &transaction_accounts
863 );
864 };
865 (
866 $invoke_context:ident,
867 $transaction_context:ident,
868 $feature_set:ident,
869 $transaction_accounts:expr $(,)?
870 ) => {
871 $crate::with_mock_invoke_context_with_feature_set!(
872 $invoke_context,
873 $transaction_context,
874 $feature_set,
875 1,
876 $transaction_accounts
877 );
878 };
879}
880
881#[cfg(feature = "dev-context-only-utils")]
882#[macro_export]
883macro_rules! with_mock_invoke_context {
884 (
885 $invoke_context:ident,
886 $transaction_context:ident,
887 $top_level_instructions:literal,
888 $transaction_accounts:expr $(,)?
889 ) => {
890 let feature_set = &solana_svm_feature_set::SVMFeatureSet::default();
891 $crate::with_mock_invoke_context_with_feature_set!(
892 $invoke_context,
893 $transaction_context,
894 feature_set,
895 $top_level_instructions,
896 $transaction_accounts
897 )
898 };
899 (
900 $invoke_context:ident,
901 $transaction_context:ident,
902 $transaction_accounts:expr $(,)?
903 ) => {
904 with_mock_invoke_context!(
905 $invoke_context,
906 $transaction_context,
907 1,
908 $transaction_accounts
909 );
910 };
911}
912
913#[cfg(feature = "dev-context-only-utils")]
914pub fn mock_compile_message<A>(
915 instruction: &Instruction,
916 accounts: &[(Pubkey, A)],
917 program_id: &Pubkey,
918 loader_key: &Pubkey,
919) -> (SanitizedMessage, Vec<(Pubkey, AccountSharedData)>)
920where
921 AccountSharedData: From<A>,
922 A: Clone,
923{
924 let message = Message::new(std::slice::from_ref(instruction), None);
925 let transaction_accounts: Vec<_> = message
926 .account_keys
927 .iter()
928 .map(|key| {
929 let account = accounts
930 .iter()
931 .find(|(k, _)| k == key)
932 .map(|(_, a)| AccountSharedData::from(a.clone()))
933 .unwrap_or_else(|| {
934 if key == program_id {
935 let mut account = AccountSharedData::new(0, 0, loader_key);
936 account.set_executable(true);
937 account
938 } else {
939 AccountSharedData::default()
940 }
941 });
942 (*key, account)
943 })
944 .collect();
945
946 let sanitized_message = SanitizedMessage::Legacy(LegacyMessage::new(message, &HashSet::new()));
947
948 (sanitized_message, transaction_accounts)
949}
950
951#[cfg(feature = "dev-context-only-utils")]
952pub fn mock_process_instruction_with_feature_set<
953 F: FnMut(&mut InvokeContext),
954 G: FnMut(&mut InvokeContext),
955>(
956 program_id: &Pubkey,
957 instruction_data: &[u8],
958 mut accounts: Vec<KeyedAccountSharedData>,
959 instruction_account_metas: Vec<AccountMeta>,
960 expected_result: Result<(), InstructionError>,
961 builtin: BuiltinFunctionRegisterer,
962 mut pre_adjustments: F,
963 mut post_adjustments: G,
964 feature_set: &SVMFeatureSet,
965) -> Vec<AccountSharedData> {
966 let original_len = accounts.len();
967 if !accounts
968 .iter()
969 .any(|(key, _)| *key == sysvar::epoch_schedule::id())
970 {
971 accounts.push((
972 sysvar::epoch_schedule::id(),
973 create_account_shared_data_for_test(&EpochSchedule::default()),
974 ));
975 }
976
977 let instruction =
978 Instruction::new_with_bytes(*program_id, instruction_data, instruction_account_metas);
979 let (sanitized_message, transaction_accounts) =
980 mock_compile_message(&instruction, &accounts, program_id, &native_loader::id());
981
982 let program_owner = accounts
983 .iter()
984 .find(|(key, _)| key == program_id)
985 .map(|(_, acct)| *acct.owner())
986 .unwrap_or_else(native_loader::id);
987 let is_builtin = native_loader::check_id(&program_owner);
988
989 with_mock_invoke_context_with_feature_set!(
990 invoke_context,
991 transaction_context,
992 feature_set,
993 1,
994 transaction_accounts,
995 &accounts
996 );
997
998 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
999 program_cache_for_tx_batch.replenish(
1000 if is_builtin {
1001 *program_id
1002 } else {
1003 program_owner
1004 },
1005 Arc::new(ProgramCacheEntry::new_builtin(0, 0, builtin)),
1006 );
1007 program_cache_for_tx_batch.set_slot_for_tests(
1008 invoke_context
1009 .environment_config
1010 .sysvar_cache()
1011 .get_clock()
1012 .map(|clock| clock.slot)
1013 .unwrap_or(1),
1014 );
1015 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1016
1017 pre_adjustments(&mut invoke_context);
1018
1019 invoke_context
1020 .prepare_top_level_instructions(&sanitized_message)
1021 .unwrap();
1022
1023 let result = invoke_context.process_instruction(&mut 0, &mut ExecuteTimings::default());
1024 assert_eq!(result, expected_result);
1025 post_adjustments(&mut invoke_context);
1026
1027 let txn_result_keys: Vec<_> = (0..transaction_context.get_number_of_accounts())
1028 .map(|i| *transaction_context.get_key_of_account_at_index(i).unwrap())
1029 .collect();
1030 let txn_result_accounts = transaction_context.deconstruct_without_keys().unwrap();
1031 let txn_result_map = txn_result_keys
1032 .into_iter()
1033 .zip(txn_result_accounts)
1034 .collect::<HashMap<Pubkey, AccountSharedData>>();
1035
1036 accounts
1037 .into_iter()
1038 .take(original_len)
1039 .map(|(key, original)| txn_result_map.get(&key).cloned().unwrap_or(original))
1040 .collect()
1041}
1042
1043#[cfg(feature = "dev-context-only-utils")]
1044pub fn mock_process_instruction<F: FnMut(&mut InvokeContext), G: FnMut(&mut InvokeContext)>(
1045 program_id: &Pubkey,
1046 instruction_data: &[u8],
1047 accounts: Vec<KeyedAccountSharedData>,
1048 instruction_account_metas: Vec<AccountMeta>,
1049 expected_result: Result<(), InstructionError>,
1050 builtin: BuiltinFunctionRegisterer,
1051 pre_adjustments: F,
1052 post_adjustments: G,
1053) -> Vec<AccountSharedData> {
1054 mock_process_instruction_with_feature_set(
1055 program_id,
1056 instruction_data,
1057 accounts,
1058 instruction_account_metas,
1059 expected_result,
1060 builtin,
1061 pre_adjustments,
1062 post_adjustments,
1063 &SVMFeatureSet::all_enabled(),
1064 )
1065}
1066
1067#[cfg(test)]
1068mod tests {
1069 use {
1070 super::*,
1071 crate::execution_budget::{
1072 DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, MAX_INSTRUCTION_STACK_DEPTH,
1073 MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268,
1074 },
1075 serde::{Deserialize, Serialize},
1076 solana_account::Account,
1077 solana_keypair::Keypair,
1078 solana_rent::Rent,
1079 solana_sbpf::program::BuiltinFunctionDefinition,
1080 solana_sdk_ids::system_program,
1081 solana_signer::Signer,
1082 solana_svm_feature_set::SVMFeatureSet,
1083 solana_transaction::{Transaction, sanitized::SanitizedTransaction},
1084 solana_transaction_context::MAX_ACCOUNTS_PER_INSTRUCTION,
1085 test_case::test_case,
1086 };
1087
1088 #[derive(Debug, Serialize, Deserialize)]
1089 enum MockInstruction {
1090 NoopSuccess,
1091 NoopFail,
1092 ModifyOwned,
1093 ModifyNotOwned,
1094 ModifyReadonly,
1095 UnbalancedPush,
1096 UnbalancedPop,
1097 ConsumeComputeUnits {
1098 compute_units_to_consume: u64,
1099 desired_result: Result<(), InstructionError>,
1100 },
1101 Resize {
1102 new_len: u64,
1103 },
1104 }
1105
1106 const MOCK_BUILTIN_COMPUTE_UNIT_COST: u64 = 1;
1107
1108 declare_process_instruction!(
1109 MockBuiltin,
1110 MOCK_BUILTIN_COMPUTE_UNIT_COST,
1111 |invoke_context| {
1112 let transaction_context = &invoke_context.transaction_context;
1113 let instruction_context = transaction_context.get_current_instruction_context()?;
1114 let instruction_data = instruction_context.get_instruction_data();
1115 let program_id = instruction_context.get_program_key()?;
1116 let instruction_accounts = (0..4)
1117 .map(|instruction_account_index| {
1118 InstructionAccount::new(instruction_account_index, false, false)
1119 })
1120 .collect::<Vec<_>>();
1121 assert_eq!(
1122 program_id,
1123 instruction_context
1124 .try_borrow_instruction_account(0)?
1125 .get_owner()
1126 );
1127 assert_ne!(
1128 instruction_context
1129 .try_borrow_instruction_account(1)?
1130 .get_owner(),
1131 instruction_context.get_key_of_instruction_account(0)?
1132 );
1133
1134 if let Ok(instruction) = bincode::deserialize(instruction_data) {
1135 match instruction {
1136 MockInstruction::NoopSuccess => (),
1137 MockInstruction::NoopFail => return Err(InstructionError::GenericError),
1138 MockInstruction::ModifyOwned => instruction_context
1139 .try_borrow_instruction_account(0)?
1140 .set_data_from_slice(&[1])?,
1141 MockInstruction::ModifyNotOwned => instruction_context
1142 .try_borrow_instruction_account(1)?
1143 .set_data_from_slice(&[1])?,
1144 MockInstruction::ModifyReadonly => instruction_context
1145 .try_borrow_instruction_account(2)?
1146 .set_data_from_slice(&[1])?,
1147 MockInstruction::UnbalancedPush => {
1148 instruction_context
1149 .try_borrow_instruction_account(0)?
1150 .checked_add_lamports(1)?;
1151 let program_id = *transaction_context.get_key_of_account_at_index(3)?;
1152 let metas = vec![
1153 AccountMeta::new_readonly(
1154 *transaction_context.get_key_of_account_at_index(0)?,
1155 false,
1156 ),
1157 AccountMeta::new_readonly(
1158 *transaction_context.get_key_of_account_at_index(1)?,
1159 false,
1160 ),
1161 ];
1162 let inner_instruction = Instruction::new_with_bincode(
1163 program_id,
1164 &MockInstruction::NoopSuccess,
1165 metas,
1166 );
1167 invoke_context
1168 .transaction_context
1169 .configure_top_level_instruction_for_tests(
1170 3,
1171 instruction_accounts,
1172 vec![],
1173 )
1174 .unwrap();
1175 let result = invoke_context.push();
1176 assert_eq!(result, Err(InstructionError::UnbalancedInstruction));
1177 result?;
1178 invoke_context
1179 .native_invoke_signed(inner_instruction, &[])
1180 .and(invoke_context.pop())?;
1181 }
1182 MockInstruction::UnbalancedPop => instruction_context
1183 .try_borrow_instruction_account(0)?
1184 .checked_add_lamports(1)?,
1185 MockInstruction::ConsumeComputeUnits {
1186 compute_units_to_consume,
1187 desired_result,
1188 } => {
1189 invoke_context
1190 .compute_meter
1191 .consume_checked(compute_units_to_consume)
1192 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
1193 return desired_result;
1194 }
1195 MockInstruction::Resize { new_len } => instruction_context
1196 .try_borrow_instruction_account(0)?
1197 .set_data_from_slice(&vec![0; new_len as usize])?,
1198 }
1199 } else {
1200 return Err(InstructionError::InvalidInstructionData);
1201 }
1202 Ok(())
1203 }
1204 );
1205
1206 #[test_case(false; "SIMD-0268 disabled")]
1207 #[test_case(true; "SIMD-0268 enabled")]
1208 fn test_instruction_stack_height(simd_0268_active: bool) {
1209 let feature_set = &SVMFeatureSet {
1210 raise_cpi_nesting_limit_to_8: simd_0268_active,
1211 ..SVMFeatureSet::all_enabled()
1212 };
1213 let max_depth = SVMTransactionExecutionBudget::new_with_defaults(simd_0268_active)
1214 .max_instruction_stack_depth;
1215 assert_eq!(
1216 max_depth,
1217 if simd_0268_active {
1218 MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268
1219 } else {
1220 MAX_INSTRUCTION_STACK_DEPTH
1221 },
1222 );
1223
1224 let mut invoke_stack = vec![];
1227 let mut transaction_accounts = vec![];
1228 let mut instruction_accounts = vec![];
1229 for index in 0..max_depth.saturating_add(1) {
1230 let program_id = solana_pubkey::new_rand();
1231 invoke_stack.push(program_id);
1232 transaction_accounts.push((
1233 solana_pubkey::new_rand(),
1234 AccountSharedData::new(1, 1, &program_id),
1235 ));
1236 instruction_accounts.push(InstructionAccount::new(
1237 index as IndexOfAccount,
1238 false,
1239 true,
1240 ));
1241 }
1242
1243 let first_program_account = transaction_accounts.len();
1246 for (index, program_id) in invoke_stack.iter().enumerate() {
1247 transaction_accounts.push((
1248 *program_id,
1249 AccountSharedData::new(1, 1, &solana_pubkey::Pubkey::default()),
1250 ));
1251 instruction_accounts.push(InstructionAccount::new(
1252 index as IndexOfAccount,
1253 false,
1254 false,
1255 ));
1256 }
1257 with_mock_invoke_context_with_feature_set!(
1258 invoke_context,
1259 transaction_context,
1260 feature_set,
1261 transaction_accounts,
1262 );
1263
1264 for depth in 0..max_depth {
1266 assert_eq!(invoke_context.get_stack_height(), depth);
1267 invoke_context
1268 .transaction_context
1269 .configure_top_level_instruction_for_tests(
1270 (first_program_account.saturating_add(depth)) as IndexOfAccount,
1271 instruction_accounts.clone(),
1272 vec![],
1273 )
1274 .unwrap();
1275 assert!(
1276 invoke_context.push().is_ok(),
1277 "push at depth {depth} should succeed (max_depth={max_depth})",
1278 );
1279 }
1280
1281 assert_eq!(invoke_context.get_stack_height(), max_depth);
1283 invoke_context
1284 .transaction_context
1285 .configure_top_level_instruction_for_tests(
1286 (first_program_account.saturating_add(max_depth)) as IndexOfAccount,
1287 instruction_accounts.clone(),
1288 vec![],
1289 )
1290 .unwrap();
1291 assert_eq!(invoke_context.push(), Err(InstructionError::CallDepth),);
1292
1293 assert_eq!(invoke_context.get_stack_height(), max_depth);
1295 }
1296
1297 #[test]
1298 fn test_max_instruction_trace_length_top_level() {
1299 const MAX_INSTRUCTIONS: usize = 8;
1300 let mut transaction_context = TransactionContext::new(
1301 vec![(
1302 Pubkey::new_unique(),
1303 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1304 )],
1305 Rent::default(),
1306 1,
1307 MAX_INSTRUCTIONS,
1308 MAX_INSTRUCTIONS,
1309 );
1310 for _ in 0..MAX_INSTRUCTIONS {
1311 transaction_context.push().unwrap();
1312 transaction_context
1313 .configure_top_level_instruction_for_tests(
1314 0,
1315 vec![InstructionAccount::new(0, false, false)],
1316 vec![],
1317 )
1318 .unwrap();
1319 transaction_context.pop().unwrap();
1320 }
1321 assert_eq!(
1322 transaction_context.push(),
1323 Err(InstructionError::MaxInstructionTraceLengthExceeded)
1324 );
1325 }
1326
1327 #[test]
1328 fn test_max_instruction_trace_length_cpi() {
1329 const MAX_INSTRUCTIONS: usize = 8;
1331 let mut transaction_context = TransactionContext::new(
1332 vec![(
1333 Pubkey::new_unique(),
1334 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1335 )],
1336 Rent::default(),
1337 256,
1338 MAX_INSTRUCTIONS,
1339 2,
1340 );
1341
1342 transaction_context
1343 .configure_instruction_at_index(
1344 0,
1345 0,
1346 vec![InstructionAccount::new(0, false, false)],
1347 vec![u16::MAX; 256],
1348 Cow::Owned(Vec::new()),
1349 None,
1350 )
1351 .unwrap();
1352
1353 transaction_context
1354 .configure_instruction_at_index(
1355 1,
1356 0,
1357 vec![InstructionAccount::new(0, false, false)],
1358 vec![u16::MAX; 256],
1359 Cow::Owned(Vec::new()),
1360 None,
1361 )
1362 .unwrap();
1363
1364 for _ in 0..MAX_INSTRUCTIONS {
1365 transaction_context.push().unwrap();
1366 transaction_context
1367 .configure_next_cpi_for_tests(
1368 0,
1369 vec![InstructionAccount::new(0, false, false)],
1370 Vec::new(),
1371 )
1372 .unwrap();
1373 }
1374
1375 assert_eq!(
1376 transaction_context.push(),
1377 Err(InstructionError::MaxInstructionTraceLengthExceeded)
1378 );
1379 }
1380
1381 #[test_case(MockInstruction::NoopSuccess, Ok(()); "NoopSuccess")]
1382 #[test_case(MockInstruction::NoopFail, Err(InstructionError::GenericError); "NoopFail")]
1383 #[test_case(MockInstruction::ModifyOwned, Ok(()); "ModifyOwned")]
1384 #[test_case(MockInstruction::ModifyNotOwned, Err(InstructionError::ExternalAccountDataModified); "ModifyNotOwned")]
1385 #[test_case(MockInstruction::ModifyReadonly, Err(InstructionError::ReadonlyDataModified); "ModifyReadonly")]
1386 #[test_case(MockInstruction::UnbalancedPush, Err(InstructionError::UnbalancedInstruction); "UnbalancedPush")]
1387 #[test_case(MockInstruction::UnbalancedPop, Err(InstructionError::UnbalancedInstruction); "UnbalancedPop")]
1388 fn test_process_instruction_account_modifications(
1389 instruction: MockInstruction,
1390 expected_result: Result<(), InstructionError>,
1391 ) {
1392 let callee_program_id = solana_pubkey::new_rand();
1393 let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1394 let not_owned_account = AccountSharedData::new(84, 1, &solana_pubkey::new_rand());
1395 let readonly_account = AccountSharedData::new(168, 1, &solana_pubkey::new_rand());
1396 let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1397 let mut program_account = AccountSharedData::new(1, 1, &native_loader::id());
1398 program_account.set_executable(true);
1399 let transaction_accounts = vec![
1400 (solana_pubkey::new_rand(), owned_account),
1401 (solana_pubkey::new_rand(), not_owned_account),
1402 (solana_pubkey::new_rand(), readonly_account),
1403 (callee_program_id, program_account),
1404 (solana_pubkey::new_rand(), loader_account),
1405 ];
1406 let metas = vec![
1407 AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1408 AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1409 AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1410 ];
1411 let instruction_accounts = (0..4)
1412 .map(|instruction_account_index| {
1413 InstructionAccount::new(
1414 instruction_account_index,
1415 false,
1416 instruction_account_index < 2,
1417 )
1418 })
1419 .collect::<Vec<_>>();
1420 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1421 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1422 program_cache_for_tx_batch.replenish(
1423 callee_program_id,
1424 Arc::new(ProgramCacheEntry::new_builtin(0, 1, MockBuiltin::register)),
1425 );
1426 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1427
1428 invoke_context
1430 .transaction_context
1431 .configure_top_level_instruction_for_tests(4, instruction_accounts, vec![])
1432 .unwrap();
1433 invoke_context.push().unwrap();
1434 let inner_instruction =
1435 Instruction::new_with_bincode(callee_program_id, &instruction, metas);
1436 let result = invoke_context
1437 .native_invoke_signed(inner_instruction, &[])
1438 .and(invoke_context.pop());
1439 assert_eq!(result, expected_result);
1440 }
1441
1442 #[test_case(Ok(()); "Ok")]
1443 #[test_case(Err(InstructionError::GenericError); "GenericError")]
1444 fn test_process_instruction_compute_unit_consumption(
1445 expected_result: Result<(), InstructionError>,
1446 ) {
1447 let callee_program_id = solana_pubkey::new_rand();
1448 let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1449 let not_owned_account = AccountSharedData::new(84, 1, &solana_pubkey::new_rand());
1450 let readonly_account = AccountSharedData::new(168, 1, &solana_pubkey::new_rand());
1451 let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1452 let mut program_account = AccountSharedData::new(1, 1, &native_loader::id());
1453 program_account.set_executable(true);
1454 let transaction_accounts = vec![
1455 (solana_pubkey::new_rand(), owned_account),
1456 (solana_pubkey::new_rand(), not_owned_account),
1457 (solana_pubkey::new_rand(), readonly_account),
1458 (callee_program_id, program_account),
1459 (solana_pubkey::new_rand(), loader_account),
1460 ];
1461 let metas = vec![
1462 AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1463 AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1464 AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1465 ];
1466 let instruction_accounts = (0..4)
1467 .map(|instruction_account_index| {
1468 InstructionAccount::new(
1469 instruction_account_index,
1470 false,
1471 instruction_account_index < 2,
1472 )
1473 })
1474 .collect::<Vec<_>>();
1475 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1476 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1477 program_cache_for_tx_batch.replenish(
1478 callee_program_id,
1479 Arc::new(ProgramCacheEntry::new_builtin(0, 1, MockBuiltin::register)),
1480 );
1481 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1482
1483 let compute_units_to_consume = 10;
1485 invoke_context
1486 .transaction_context
1487 .configure_top_level_instruction_for_tests(4, instruction_accounts, vec![])
1488 .unwrap();
1489 invoke_context.push().unwrap();
1490 let inner_instruction = Instruction::new_with_bincode(
1491 callee_program_id,
1492 &MockInstruction::ConsumeComputeUnits {
1493 compute_units_to_consume,
1494 desired_result: expected_result.clone(),
1495 },
1496 metas,
1497 );
1498 invoke_context
1499 .prepare_next_cpi_instruction(inner_instruction, &[])
1500 .unwrap();
1501
1502 let mut compute_units_consumed = 0;
1503 let result = invoke_context
1504 .process_instruction(&mut compute_units_consumed, &mut ExecuteTimings::default());
1505
1506 assert!(compute_units_consumed > 0);
1510 assert_eq!(
1511 compute_units_consumed,
1512 compute_units_to_consume.saturating_add(MOCK_BUILTIN_COMPUTE_UNIT_COST),
1513 );
1514 assert_eq!(result, expected_result);
1515
1516 invoke_context.pop().unwrap();
1517 }
1518
1519 #[test]
1520 fn test_invoke_context_compute_budget() {
1521 let transaction_accounts = vec![(solana_pubkey::new_rand(), AccountSharedData::default())];
1522 let execution_budget = SVMTransactionExecutionBudget {
1523 compute_unit_limit: u64::from(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT),
1524 ..SVMTransactionExecutionBudget::default()
1525 };
1526
1527 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1528 invoke_context.compute_budget = execution_budget;
1529
1530 invoke_context
1531 .transaction_context
1532 .configure_top_level_instruction_for_tests(0, vec![], vec![])
1533 .unwrap();
1534 invoke_context.push().unwrap();
1535 assert_eq!(*invoke_context.get_compute_budget(), execution_budget);
1536 invoke_context.pop().unwrap();
1537 }
1538
1539 #[test_case(0; "Resize the account to *the same size*, so not consuming any additional size")]
1540 #[test_case(1; "Resize the account larger")]
1541 #[test_case(-1; "Resize the account smaller")]
1542 fn test_process_instruction_accounts_resize_delta(resize_delta: i64) {
1543 let program_key = Pubkey::new_unique();
1544 let user_account_data_len = 123u64;
1545 let user_account =
1546 AccountSharedData::new(100, user_account_data_len as usize, &program_key);
1547 let dummy_account = AccountSharedData::new(10, 0, &program_key);
1548 let mut program_account = AccountSharedData::new(500, 500, &native_loader::id());
1549 program_account.set_executable(true);
1550 let transaction_accounts = vec![
1551 (Pubkey::new_unique(), user_account),
1552 (Pubkey::new_unique(), dummy_account),
1553 (program_key, program_account),
1554 ];
1555 let instruction_accounts = vec![
1556 InstructionAccount::new(0, false, true),
1557 InstructionAccount::new(1, false, false),
1558 ];
1559 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1560 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1561 program_cache_for_tx_batch.replenish(
1562 program_key,
1563 Arc::new(ProgramCacheEntry::new_builtin(0, 0, MockBuiltin::register)),
1564 );
1565 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1566
1567 let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
1568 let instruction_data = bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1569
1570 invoke_context
1571 .transaction_context
1572 .configure_top_level_instruction_for_tests(2, instruction_accounts, instruction_data)
1573 .unwrap();
1574 let result = invoke_context.process_instruction(&mut 0, &mut ExecuteTimings::default());
1575
1576 assert!(result.is_ok());
1577 assert_eq!(
1578 invoke_context.transaction_context.accounts().resize_delta(),
1579 resize_delta
1580 );
1581 }
1582
1583 #[test]
1584 fn test_prepare_instruction_maximum_accounts() {
1585 const MAX_ACCOUNTS_REFERENCED: usize = u16::MAX as usize;
1586 let mut transaction_accounts: Vec<KeyedAccountSharedData> =
1587 Vec::with_capacity(MAX_ACCOUNTS_PER_TRANSACTION);
1588 let mut account_metas: Vec<AccountMeta> = Vec::with_capacity(MAX_ACCOUNTS_REFERENCED);
1589
1590 let fee_payer = Keypair::new();
1592 transaction_accounts.push((
1593 fee_payer.pubkey(),
1594 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1595 ));
1596 account_metas.push(AccountMeta::new(fee_payer.pubkey(), true));
1597
1598 let program_id = Pubkey::new_unique();
1599 let mut program_account = AccountSharedData::new(1, 1, &Pubkey::new_unique());
1600 program_account.set_executable(true);
1601 transaction_accounts.push((program_id, program_account));
1602 account_metas.push(AccountMeta::new_readonly(program_id, false));
1603
1604 for i in 2..MAX_ACCOUNTS_REFERENCED {
1605 if i < MAX_ACCOUNTS_PER_TRANSACTION {
1607 let key = Pubkey::new_unique();
1608 transaction_accounts
1609 .push((key, AccountSharedData::new(1, 1, &Pubkey::new_unique())));
1610 account_metas.push(AccountMeta::new_readonly(key, false));
1611 } else {
1612 let repeated_key = transaction_accounts
1613 .get(i % MAX_ACCOUNTS_PER_TRANSACTION)
1614 .unwrap()
1615 .0;
1616 account_metas.push(AccountMeta::new_readonly(repeated_key, false));
1617 }
1618 }
1619
1620 with_mock_invoke_context!(invoke_context, transaction_context, 2, transaction_accounts);
1621
1622 let instruction_1 = Instruction::new_with_bytes(program_id, &[20], account_metas.clone());
1623
1624 let instruction_2 = Instruction::new_with_bytes(
1625 program_id,
1626 &[20],
1627 account_metas.iter().rev().cloned().collect(),
1628 );
1629
1630 let transaction = Transaction::new_with_payer(
1631 &[instruction_1.clone(), instruction_2.clone()],
1632 Some(&fee_payer.pubkey()),
1633 );
1634
1635 let sanitized =
1636 SanitizedTransaction::try_from_legacy_transaction(transaction, &HashSet::new())
1637 .unwrap();
1638
1639 fn test_case_1(invoke_context: &InvokeContext) {
1640 let instruction_context = invoke_context
1641 .transaction_context
1642 .get_next_instruction_context()
1643 .unwrap();
1644 for index_in_instruction in 0..MAX_ACCOUNTS_REFERENCED as IndexOfAccount {
1645 let index_in_transaction = instruction_context
1646 .get_index_of_instruction_account_in_transaction(index_in_instruction)
1647 .unwrap();
1648 let other_ix_index = instruction_context
1649 .get_index_of_account_in_instruction(index_in_transaction)
1650 .unwrap();
1651 if (index_in_instruction as usize) < MAX_ACCOUNTS_PER_TRANSACTION {
1652 assert_eq!(index_in_instruction, index_in_transaction);
1653 assert_eq!(index_in_instruction, other_ix_index);
1654 } else {
1655 assert_eq!(
1656 index_in_instruction as usize % MAX_ACCOUNTS_PER_TRANSACTION,
1657 index_in_transaction as usize
1658 );
1659 assert_eq!(
1660 index_in_instruction as usize % MAX_ACCOUNTS_PER_TRANSACTION,
1661 other_ix_index as usize
1662 );
1663 }
1664 }
1665 }
1666
1667 fn test_case_2(invoke_context: &InvokeContext) {
1668 let instruction_context = invoke_context
1669 .transaction_context
1670 .get_next_instruction_context()
1671 .unwrap();
1672 for index_in_instruction in 0..MAX_ACCOUNTS_REFERENCED as IndexOfAccount {
1673 let index_in_transaction = instruction_context
1674 .get_index_of_instruction_account_in_transaction(index_in_instruction)
1675 .unwrap();
1676 let other_ix_index = instruction_context
1677 .get_index_of_account_in_instruction(index_in_transaction)
1678 .unwrap();
1679 assert_eq!(
1680 index_in_transaction,
1681 (MAX_ACCOUNTS_REFERENCED as u16)
1682 .saturating_sub(index_in_instruction)
1683 .saturating_sub(1)
1684 .overflowing_rem(MAX_ACCOUNTS_PER_TRANSACTION as u16)
1685 .0
1686 );
1687 if (index_in_instruction as usize) < MAX_ACCOUNTS_PER_TRANSACTION {
1688 assert_eq!(index_in_instruction, other_ix_index);
1689 } else {
1690 assert_eq!(
1691 index_in_instruction as usize % MAX_ACCOUNTS_PER_TRANSACTION,
1692 other_ix_index as usize
1693 );
1694 }
1695 }
1696 }
1697
1698 invoke_context
1699 .prepare_top_level_instructions(&sanitized)
1700 .unwrap();
1701
1702 test_case_1(&invoke_context);
1703
1704 invoke_context.transaction_context.push().unwrap();
1705 invoke_context.transaction_context.pop().unwrap();
1706
1707 test_case_2(&invoke_context);
1708
1709 invoke_context.transaction_context.push().unwrap();
1710 invoke_context
1711 .prepare_next_cpi_instruction(instruction_1, &[fee_payer.pubkey()])
1712 .unwrap();
1713 test_case_1(&invoke_context);
1714
1715 invoke_context.transaction_context.push().unwrap();
1716 invoke_context
1717 .prepare_next_cpi_instruction(instruction_2, &[fee_payer.pubkey()])
1718 .unwrap();
1719 test_case_2(&invoke_context);
1720 }
1721
1722 #[test]
1723 fn test_duplicated_accounts() {
1724 let mut transaction_accounts: Vec<KeyedAccountSharedData> =
1725 Vec::with_capacity(MAX_ACCOUNTS_PER_TRANSACTION);
1726 let mut account_metas: Vec<AccountMeta> =
1727 Vec::with_capacity(MAX_ACCOUNTS_PER_INSTRUCTION.saturating_sub(1));
1728
1729 let fee_payer = Keypair::new();
1731 transaction_accounts.push((
1732 fee_payer.pubkey(),
1733 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1734 ));
1735 account_metas.push(AccountMeta::new(fee_payer.pubkey(), true));
1736
1737 let program_id = Pubkey::new_unique();
1738 let mut program_account = AccountSharedData::new(1, 1, &Pubkey::new_unique());
1739 program_account.set_executable(true);
1740 transaction_accounts.push((program_id, program_account));
1741 account_metas.push(AccountMeta::new_readonly(program_id, false));
1742
1743 for i in 2..account_metas.capacity() {
1744 if i % 2 == 0 {
1745 let key = Pubkey::new_unique();
1746 transaction_accounts
1747 .push((key, AccountSharedData::new(1, 1, &Pubkey::new_unique())));
1748 account_metas.push(AccountMeta::new_readonly(key, false));
1749 } else {
1750 let last_key = transaction_accounts.last().unwrap().0;
1751 account_metas.push(AccountMeta::new_readonly(last_key, false));
1752 }
1753 }
1754
1755 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1756
1757 let instruction = Instruction::new_with_bytes(program_id, &[20], account_metas.clone());
1758
1759 let transaction = Transaction::new_with_payer(&[instruction], Some(&fee_payer.pubkey()));
1760
1761 let sanitized =
1762 SanitizedTransaction::try_from_legacy_transaction(transaction, &HashSet::new())
1763 .unwrap();
1764
1765 invoke_context
1766 .prepare_top_level_instructions(&sanitized)
1767 .unwrap();
1768
1769 {
1770 let instruction_context = invoke_context
1771 .transaction_context
1772 .get_next_instruction_context()
1773 .unwrap();
1774 for index_in_instruction in 2..account_metas.len() as IndexOfAccount {
1775 let is_duplicate = instruction_context
1776 .is_instruction_account_duplicate(index_in_instruction)
1777 .unwrap();
1778 if index_in_instruction % 2 == 0 {
1779 assert!(is_duplicate.is_none());
1780 } else {
1781 assert_eq!(is_duplicate, Some(index_in_instruction.saturating_sub(1)));
1782 }
1783 }
1784 }
1785
1786 invoke_context.transaction_context.push().unwrap();
1787
1788 let instruction = Instruction::new_with_bytes(
1789 program_id,
1790 &[20],
1791 account_metas.iter().cloned().rev().collect(),
1792 );
1793
1794 invoke_context
1795 .prepare_next_cpi_instruction(instruction, &[fee_payer.pubkey()])
1796 .unwrap();
1797 let instruction_context = invoke_context
1798 .transaction_context
1799 .get_next_instruction_context()
1800 .unwrap();
1801 for index_in_instruction in 2..account_metas.len().saturating_sub(1) as u16 {
1802 let is_duplicate = instruction_context
1803 .is_instruction_account_duplicate(index_in_instruction)
1804 .unwrap();
1805 if index_in_instruction % 2 == 0 {
1806 assert!(is_duplicate.is_none());
1807 } else {
1808 assert_eq!(is_duplicate, Some(index_in_instruction.saturating_sub(1)));
1809 }
1810 }
1811 }
1812
1813 const TEST_CALLER_PROGRAM_ID: Pubkey = Pubkey::new_from_array([1u8; 32]);
1815 const TEST_CALLEE_PROGRAM_ID: Pubkey = Pubkey::new_from_array([2u8; 32]);
1816 const TEST_WRONG_PROGRAM_ID: Pubkey = Pubkey::new_from_array([3u8; 32]);
1817 const TEST_MOCK_EXTRA_KEY: Pubkey = Pubkey::new_from_array([4u8; 32]);
1818 const TEST_ACCOUNT_KEY: Pubkey = Pubkey::new_from_array([5u8; 32]);
1819
1820 fn run_native_invoke_signed_test(
1829 target_key: Pubkey,
1830 target_is_signer: bool,
1831 inner_instruction: Instruction,
1832 signer_seeds: &[&[&[u8]]],
1833 ) -> Result<(), InstructionError> {
1834 let target_account = AccountSharedData::new(100, 0, &TEST_CALLEE_PROGRAM_ID);
1835 let mock_extra_account = AccountSharedData::new(0, 1, &system_program::id());
1836 let mut caller_program_account = AccountSharedData::new(1, 1, &native_loader::id());
1837 caller_program_account.set_executable(true);
1838 let mut callee_program_account = AccountSharedData::new(1, 1, &native_loader::id());
1839 callee_program_account.set_executable(true);
1840 let transaction_accounts = vec![
1841 (target_key, target_account),
1842 (TEST_CALLER_PROGRAM_ID, caller_program_account),
1843 (TEST_MOCK_EXTRA_KEY, mock_extra_account),
1844 (TEST_CALLEE_PROGRAM_ID, callee_program_account),
1845 ];
1846
1847 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1848 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1849 program_cache_for_tx_batch.replenish(
1850 TEST_CALLEE_PROGRAM_ID,
1851 Arc::new(ProgramCacheEntry::new_builtin(0, 1, MockBuiltin::register)),
1852 );
1853 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1854
1855 let instruction_accounts = (0..4)
1856 .map(|i| InstructionAccount::new(i, i == 0 && target_is_signer, i < 2))
1857 .collect::<Vec<_>>();
1858 invoke_context
1859 .transaction_context
1860 .configure_top_level_instruction_for_tests(1, instruction_accounts, vec![])
1861 .unwrap();
1862 invoke_context.push().unwrap();
1863
1864 let result = invoke_context.native_invoke_signed(inner_instruction, signer_seeds);
1865 invoke_context.pop().unwrap();
1866 result
1867 }
1868
1869 #[test]
1871 fn test_native_invoke_signed_with_valid_pda_signer() {
1872 let (pda_key, bump_seed) =
1873 Pubkey::find_program_address(&[b"seed"], &TEST_CALLER_PROGRAM_ID);
1874 let instruction = Instruction::new_with_bincode(
1875 TEST_CALLEE_PROGRAM_ID,
1876 &MockInstruction::NoopSuccess,
1877 vec![
1878 AccountMeta::new(pda_key, true),
1879 AccountMeta::new_readonly(TEST_MOCK_EXTRA_KEY, false),
1880 ],
1881 );
1882 let result =
1883 run_native_invoke_signed_test(pda_key, false, instruction, &[&[b"seed", &[bump_seed]]]);
1884 assert!(
1885 result.is_ok(),
1886 "valid PDA signer should succeed: {result:?}"
1887 );
1888 }
1889
1890 #[test]
1894 fn test_native_invoke_signed_with_invalid_seeds() {
1895 let instruction = Instruction::new_with_bincode(
1896 TEST_CALLEE_PROGRAM_ID,
1897 &MockInstruction::NoopSuccess,
1898 vec![AccountMeta::new(TEST_ACCOUNT_KEY, true)],
1899 );
1900 let oversized_seed = [0u8; 33];
1901 let result = run_native_invoke_signed_test(
1902 TEST_ACCOUNT_KEY,
1903 false,
1904 instruction,
1905 &[&[&oversized_seed]],
1906 );
1907 assert_eq!(result, Err(InstructionError::Custom(0)));
1908 }
1909
1910 #[test]
1913 fn test_native_invoke_signed_pda_privilege_escalation_without_seeds() {
1914 let (pda_key, _bump_seed) =
1915 Pubkey::find_program_address(&[b"seed"], &TEST_CALLER_PROGRAM_ID);
1916 let instruction = Instruction::new_with_bincode(
1917 TEST_CALLEE_PROGRAM_ID,
1918 &MockInstruction::NoopSuccess,
1919 vec![AccountMeta::new(pda_key, true)],
1920 );
1921 let result = run_native_invoke_signed_test(pda_key, false, instruction, &[]);
1922 assert_eq!(result, Err(InstructionError::PrivilegeEscalation));
1923 }
1924
1925 #[test]
1928 fn test_native_invoke_signed_uses_caller_program_id_for_pda() {
1929 let (pda_key, bump_seed) = Pubkey::find_program_address(&[b"seed"], &TEST_WRONG_PROGRAM_ID);
1930 let instruction = Instruction::new_with_bincode(
1931 TEST_CALLEE_PROGRAM_ID,
1932 &MockInstruction::NoopSuccess,
1933 vec![AccountMeta::new(pda_key, true)],
1934 );
1935 let result =
1936 run_native_invoke_signed_test(pda_key, false, instruction, &[&[b"seed", &[bump_seed]]]);
1937 assert_eq!(result, Err(InstructionError::PrivilegeEscalation));
1938 }
1939
1940 #[test]
1942 fn test_native_invoke_signed_top_level_signer_needs_no_seeds() {
1943 let (pda_key, _bump_seed) =
1944 Pubkey::find_program_address(&[b"seed"], &TEST_CALLER_PROGRAM_ID);
1945 let instruction = Instruction::new_with_bincode(
1946 TEST_CALLEE_PROGRAM_ID,
1947 &MockInstruction::NoopSuccess,
1948 vec![
1949 AccountMeta::new(pda_key, true),
1950 AccountMeta::new_readonly(TEST_MOCK_EXTRA_KEY, false),
1951 ],
1952 );
1953 let result = run_native_invoke_signed_test(pda_key, true, instruction, &[]);
1954 assert!(
1955 result.is_ok(),
1956 "top-level signer should not need seeds: {result:?}"
1957 );
1958 }
1959
1960 #[test]
1961 fn test_compile_message() {
1962 let program_id = Pubkey::new_from_array([1u8; 32]);
1963 let writable = Pubkey::new_from_array([2u8; 32]);
1964 let loader_key = Pubkey::new_from_array([3u8; 32]);
1965
1966 let instruction = Instruction {
1967 program_id,
1968 accounts: vec![AccountMeta::new(writable, false)],
1969 data: vec![1, 2, 3],
1970 };
1971
1972 let accounts = vec![(
1973 writable,
1974 Account {
1975 lamports: 100,
1976 ..Account::default()
1977 },
1978 )];
1979
1980 let (message, tx_accounts) =
1981 mock_compile_message(&instruction, &accounts, &program_id, &loader_key);
1982
1983 assert_eq!(message.instructions().len(), 1);
1984 assert_eq!(tx_accounts.len(), 2);
1985 assert_eq!(tx_accounts.first().unwrap().0, writable);
1986 assert_eq!(tx_accounts.get(1).unwrap().0, program_id);
1987
1988 assert!(!message.is_signer(0));
1990 }
1991}