1#[cfg(feature = "dev-context-only-utils")]
2use {
3 crate::program_cache_entry::ProgramCacheEntry,
4 qualifier_attr::qualifiers,
5 solana_account::{AccountSharedData, WritableAccount},
6 solana_epoch_schedule::EpochSchedule,
7 solana_instruction::AccountMeta,
8 solana_message::{LegacyMessage, Message, SanitizedMessage},
9 solana_sdk_ids::sysvar,
10 solana_transaction_context::transaction_accounts::KeyedAccountSharedData,
11 std::collections::{HashMap, HashSet},
12};
13use {
14 crate::{
15 execution_budget::{SVMTransactionExecutionBudget, SVMTransactionExecutionCost},
16 loaded_programs::{
17 ProgramCacheForTxBatch, ProgramRuntimeEnvironment, ProgramRuntimeEnvironments,
18 },
19 memory_context::{MemoryContext, MemoryContexts},
20 program_cache_entry::ProgramCacheEntryType,
21 stable_log,
22 sysvar_cache::SysvarCache,
23 },
24 solana_hash::Hash,
25 solana_instruction::{Instruction, error::InstructionError},
26 solana_pubkey::Pubkey,
27 solana_sbpf::{
28 ebpf::MM_HEAP_START,
29 elf::{ElfError, Executable as GenericExecutable},
30 error::{EbpfError, ProgramResult},
31 memory_region::MemoryMapping,
32 program::{BuiltinProgram, SBPFVersion},
33 vm::{Config, ContextObject, EbpfVm},
34 },
35 solana_sdk_ids::{
36 bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, loader_v4, native_loader,
37 },
38 solana_svm_callback::InvokeContextCallback,
39 solana_svm_feature_set::SVMFeatureSet,
40 solana_svm_log_collector::{LogCollector, ic_msg},
41 solana_svm_measure::{measure::Measure, measure_us},
42 solana_svm_timings::{ExecuteDetailsTimings, ExecuteTimings},
43 solana_svm_transaction::svm_message::SVMMessage,
44 solana_svm_type_overrides::sync::Arc,
45 solana_transaction_context::{
46 IndexOfAccount, MAX_ACCOUNTS_PER_TRANSACTION, instruction::InstructionContext,
47 instruction_accounts::InstructionAccount, transaction::TransactionContext,
48 },
49 std::{
50 alloc::Layout,
51 borrow::Cow,
52 cell::{Cell, RefCell},
53 fmt::{self, Debug},
54 ptr,
55 rc::Rc,
56 time::Duration,
57 },
58};
59
60pub type BuiltinFunctionRegisterer =
61 fn(&mut BuiltinProgram<InvokeContext<'static, 'static>>, &str) -> Result<(), ElfError>;
62pub type Executable = GenericExecutable<InvokeContext<'static, 'static>>;
63pub type RegisterTrace<'a> = &'a [[u64; 12]];
64
65#[macro_export]
67macro_rules! declare_process_instruction {
68 ($process_instruction:ident, $cu_to_consume:expr, |$invoke_context:ident| $inner:tt) => {
69 $crate::solana_sbpf::declare_builtin_function!(
70 $process_instruction,
71 fn rust(
72 invoke_context: &mut $crate::invoke_context::InvokeContext<'_, '_>,
73 _arg0: u64,
74 _arg1: u64,
75 _arg2: u64,
76 _arg3: u64,
77 _arg4: u64,
78 ) -> Result<u64, Box<dyn std::error::Error>> {
79 fn process_instruction_inner(
80 $invoke_context: &mut $crate::invoke_context::InvokeContext,
81 ) -> std::result::Result<(), $crate::__private::InstructionError>
82 $inner
83
84 let consumption_result = if $cu_to_consume > 0
85 {
86 invoke_context.compute_meter.consume_checked($cu_to_consume)
87 } else {
88 Ok(())
89 };
90 consumption_result
91 .and_then(|_| {
92 process_instruction_inner(invoke_context)
93 .map(|_| 0)
94 .map_err(|err| Box::new(err) as Box<dyn std::error::Error>)
95 })
96 .into()
97 }
98 );
99 };
100}
101
102impl ContextObject for InvokeContext<'_, '_> {
103 fn consume(&mut self, amount: u64) {
104 let compute_meter = self.compute_meter.0.get();
107 self.compute_meter
108 .0
109 .set(compute_meter.saturating_sub(amount));
110 }
111
112 fn get_remaining(&self) -> u64 {
113 self.compute_meter.0.get()
114 }
115
116 fn active_mapping_ptr(&mut self) -> ptr::NonNull<MemoryMapping> {
117 let memory = self
118 .memory_contexts
119 .memory_mapping_mut()
120 .expect("The memory context must have been set for the current instruction");
121 ptr::NonNull::from_mut(memory)
122 }
123}
124
125#[derive(Clone, PartialEq, Eq, Debug)]
126pub struct AllocErr;
127impl fmt::Display for AllocErr {
128 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
129 f.write_str("Error: Memory allocation failed")
130 }
131}
132
133pub struct BpfAllocator {
134 len: u64,
135 pos: u64,
136}
137
138impl BpfAllocator {
139 pub fn new(len: u64) -> Self {
140 Self { len, pos: 0 }
141 }
142
143 pub fn alloc(&mut self, layout: Layout) -> Result<u64, AllocErr> {
144 let bytes_to_align = (self.pos as *const u8).align_offset(layout.align()) as u64;
145 if self
146 .pos
147 .saturating_add(bytes_to_align)
148 .saturating_add(layout.size() as u64)
149 <= self.len
150 {
151 self.pos = self.pos.saturating_add(bytes_to_align);
152 let addr = MM_HEAP_START.saturating_add(self.pos);
153 self.pos = self.pos.saturating_add(layout.size() as u64);
154 Ok(addr)
155 } else {
156 Err(AllocErr)
157 }
158 }
159}
160
161pub struct EnvironmentConfig<'a> {
162 pub blockhash: Hash,
163 pub blockhash_lamports_per_signature: u64,
164 alpenglow_migration_succeeded: bool,
165 epoch_stake_callback: &'a dyn InvokeContextCallback,
166 feature_set: &'a SVMFeatureSet,
167 program_runtime_environments: &'a ProgramRuntimeEnvironments,
168 sysvar_cache: &'a SysvarCache,
169}
170impl<'a> EnvironmentConfig<'a> {
171 pub fn new(
172 blockhash: Hash,
173 blockhash_lamports_per_signature: u64,
174 alpenglow_migration_succeeded: bool,
175 epoch_stake_callback: &'a dyn InvokeContextCallback,
176 feature_set: &'a SVMFeatureSet,
177 program_runtime_environments: &'a ProgramRuntimeEnvironments,
178 sysvar_cache: &'a SysvarCache,
179 ) -> Self {
180 Self {
181 blockhash,
182 blockhash_lamports_per_signature,
183 alpenglow_migration_succeeded,
184 epoch_stake_callback,
185 feature_set,
186 program_runtime_environments,
187 sysvar_cache,
188 }
189 }
190
191 pub fn sysvar_cache(&self) -> &SysvarCache {
193 self.sysvar_cache
194 }
195}
196
197pub struct ComputeMeter(Cell<u64>);
198
199impl ComputeMeter {
200 pub fn consume_checked(&self, amount: u64) -> Result<(), Box<dyn std::error::Error>> {
202 let compute_meter = self.0.get();
203 let exceeded = compute_meter < amount;
204 self.0.set(compute_meter.saturating_sub(amount));
205 if exceeded {
206 return Err(Box::new(InstructionError::ComputationalBudgetExceeded));
207 }
208 Ok(())
209 }
210
211 #[cfg(feature = "dev-context-only-utils")]
215 pub fn mock_set_remaining(&self, remaining: u64) {
216 self.0.set(remaining);
217 }
218}
219
220pub struct InvokeContext<'a, 'ix_data> {
222 pub transaction_context: &'a mut TransactionContext<'ix_data>,
224 pub program_cache_for_tx_batch: &'a mut ProgramCacheForTxBatch,
226 pub environment_config: EnvironmentConfig<'a>,
228 compute_budget: SVMTransactionExecutionBudget,
230 execution_cost: SVMTransactionExecutionCost,
232 pub compute_meter: ComputeMeter,
235 log_collector: Option<Rc<RefCell<LogCollector>>>,
236 pub total_nested_exec_time: Duration,
238 pub timings: ExecuteDetailsTimings,
239 pub memory_contexts: MemoryContexts,
240 register_traces: Vec<(usize, Vec<[u64; 12]>)>,
242 #[cfg(feature = "sbpf-debugger")]
244 pub debug_port: Option<u16>,
245}
246
247impl<'a, 'ix_data> InvokeContext<'a, 'ix_data> {
248 pub fn new(
249 transaction_context: &'a mut TransactionContext<'ix_data>,
250 program_cache_for_tx_batch: &'a mut ProgramCacheForTxBatch,
251 environment_config: EnvironmentConfig<'a>,
252 log_collector: Option<Rc<RefCell<LogCollector>>>,
253 compute_budget: SVMTransactionExecutionBudget,
254 execution_cost: SVMTransactionExecutionCost,
255 ) -> Self {
256 Self {
257 transaction_context,
258 program_cache_for_tx_batch,
259 environment_config,
260 log_collector,
261 compute_budget,
262 execution_cost,
263 compute_meter: ComputeMeter(Cell::new(compute_budget.compute_unit_limit)),
264 total_nested_exec_time: Duration::ZERO,
265 timings: ExecuteDetailsTimings::default(),
266 memory_contexts: MemoryContexts::new(),
267 register_traces: Vec::new(),
268 #[cfg(feature = "sbpf-debugger")]
269 debug_port: None,
270 }
271 }
272
273 #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
275 fn push(&mut self) -> Result<(), InstructionError> {
276 let instruction_context = self.transaction_context.get_next_instruction_context()?;
277 let program_id = instruction_context
278 .get_program_key()
279 .map_err(|_| InstructionError::UnsupportedProgramId)?;
280 if self.transaction_context.get_instruction_stack_height() != 0 {
281 let contains =
282 (0..self.transaction_context.get_instruction_stack_height()).any(|level| {
283 self.transaction_context
284 .get_instruction_context_at_nesting_level(level)
285 .and_then(|instruction_context| instruction_context.get_program_key())
286 .map(|program_key| program_key == program_id)
287 .unwrap_or(false)
288 });
289 let is_last = self
290 .transaction_context
291 .get_current_instruction_context()
292 .and_then(|instruction_context| instruction_context.get_program_key())
293 .map(|program_key| program_key == program_id)
294 .unwrap_or(false);
295 if contains && !is_last {
296 return Err(InstructionError::ReentrancyNotAllowed);
298 }
299 }
300
301 self.transaction_context.push()?;
302 self.memory_contexts.push_placeholder();
303 Ok(())
304 }
305
306 #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
308 fn pop(&mut self) -> Result<(), InstructionError> {
309 self.memory_contexts.pop();
310 self.transaction_context.pop()
311 }
312
313 pub fn get_stack_height(&self) -> usize {
316 self.transaction_context.get_instruction_stack_height()
317 }
318
319 pub fn native_invoke_signed(
326 &mut self,
327 instruction: Instruction,
328 signer_seeds: &[&[&[u8]]],
329 ) -> Result<(), InstructionError> {
330 let caller_program_id = *self
331 .transaction_context
332 .get_current_instruction_context()?
333 .get_program_key()?;
334 let signers = signer_seeds
337 .iter()
338 .map(|seeds| Pubkey::create_program_address(seeds, &caller_program_id))
339 .collect::<Result<Vec<Pubkey>, solana_pubkey::PubkeyError>>()
340 .map_err(|e| e as u64)?;
341 self.prepare_next_cpi_instruction(instruction, &signers)?;
342 let mut compute_units_consumed = 0;
343 self.process_instruction(&mut compute_units_consumed, &mut ExecuteTimings::default())?;
344 Ok(())
345 }
346
347 pub(crate) fn prepare_next_cpi_instruction(
350 &mut self,
351 instruction: Instruction,
352 signers: &[Pubkey],
353 ) -> Result<(), InstructionError> {
354 let transaction_callee_map_len = (self.transaction_context.get_number_of_accounts()
356 as usize)
357 .min(MAX_ACCOUNTS_PER_TRANSACTION);
358 let mut transaction_callee_map: Vec<u16> = vec![u16::MAX; transaction_callee_map_len];
359 let mut instruction_accounts: Vec<InstructionAccount> =
360 Vec::with_capacity(instruction.accounts.len());
361
362 let program_account_index = {
366 let instruction_context = self.transaction_context.get_current_instruction_context()?;
367
368 for account_meta in instruction.accounts.iter() {
369 let index_in_transaction = self
370 .transaction_context
371 .find_index_of_account(&account_meta.pubkey)
372 .ok_or_else(|| {
373 ic_msg!(
374 self,
375 "Instruction references an unknown account {}",
376 account_meta.pubkey,
377 );
378 InstructionError::MissingAccount
379 })?;
380
381 debug_assert!((index_in_transaction as usize) < transaction_callee_map.len());
382 let index_in_callee = transaction_callee_map
383 .get_mut(index_in_transaction as usize)
384 .unwrap();
385
386 if (*index_in_callee as usize) < instruction_accounts.len() {
387 let cloned_account = {
388 let instruction_account = instruction_accounts
389 .get_mut(*index_in_callee as usize)
390 .ok_or(InstructionError::MissingAccount)?;
391 instruction_account.set_is_signer(
392 instruction_account.is_signer() || account_meta.is_signer,
393 );
394 instruction_account.set_is_writable(
395 instruction_account.is_writable() || account_meta.is_writable,
396 );
397 *instruction_account
398 };
399 instruction_accounts.push(cloned_account);
400 } else {
401 *index_in_callee = instruction_accounts.len() as u16;
402 instruction_accounts.push(InstructionAccount::new(
403 index_in_transaction,
404 account_meta.is_signer,
405 account_meta.is_writable,
406 ));
407 }
408 }
409
410 for current_index in 0..instruction_accounts.len() {
411 let instruction_account = instruction_accounts.get(current_index).unwrap();
412 let index_in_callee = *transaction_callee_map
413 .get(instruction_account.index_in_transaction as usize)
414 .unwrap() as usize;
415
416 if current_index != index_in_callee {
417 let (is_signer, is_writable) = {
418 let reference_account = instruction_accounts
419 .get(index_in_callee)
420 .ok_or(InstructionError::MissingAccount)?;
421 (
422 reference_account.is_signer(),
423 reference_account.is_writable(),
424 )
425 };
426
427 let current_account = instruction_accounts.get_mut(current_index).unwrap();
428 current_account.set_is_signer(current_account.is_signer() || is_signer);
429 current_account.set_is_writable(current_account.is_writable() || is_writable);
430 continue;
432 }
433
434 let index_in_caller = instruction_context.get_index_of_account_in_instruction(
435 instruction_account.index_in_transaction,
436 )?;
437
438 let account_key = &instruction.accounts.get(current_index).unwrap().pubkey;
440 let caller_instruction_account = instruction_context
442 .instruction_accounts()
443 .get(index_in_caller as usize)
444 .unwrap();
445
446 if instruction_account.is_writable() && !caller_instruction_account.is_writable() {
448 ic_msg!(self, "{}'s writable privilege escalated", account_key,);
449 return Err(InstructionError::PrivilegeEscalation);
450 }
451
452 if instruction_account.is_signer()
455 && !(caller_instruction_account.is_signer() || signers.contains(account_key))
456 {
457 ic_msg!(self, "{}'s signer privilege escalated", account_key,);
458 return Err(InstructionError::PrivilegeEscalation);
459 }
460 }
461
462 let callee_program_id = &instruction.program_id;
464 let program_account_index_in_transaction = self
465 .transaction_context
466 .find_index_of_account(callee_program_id);
467 let program_account_index_in_instruction = program_account_index_in_transaction
468 .map(|index| instruction_context.get_index_of_account_in_instruction(index));
469
470 if program_account_index_in_instruction.is_none()
473 || program_account_index_in_instruction.unwrap().is_err()
474 {
475 ic_msg!(self, "Unknown program {}", callee_program_id);
476 return Err(InstructionError::MissingAccount);
477 }
478
479 program_account_index_in_transaction.unwrap()
482 };
483
484 let caller_index = self.transaction_context.get_current_instruction_index()?;
487 self.transaction_context.configure_instruction_at_index(
488 self.transaction_context.get_instruction_trace_length(),
489 program_account_index,
490 instruction_accounts,
491 transaction_callee_map,
492 Cow::Owned(instruction.data),
493 Some(caller_index as u16),
494 )?;
495 Ok(())
496 }
497
498 pub fn process_message(
504 &mut self,
505 message: &'ix_data impl SVMMessage,
506 execute_timings: &mut ExecuteTimings,
507 accumulated_consumed_units: &mut u64,
508 ) -> Result<(), (u8, InstructionError)> {
509 self.prepare_top_level_instructions(message)?;
510
511 for (top_level_instruction_index, (program_id, instruction)) in
512 message.program_instructions_iter().enumerate()
513 {
514 let mut compute_units_consumed = 0;
515 let (result, process_instruction_us) = measure_us!({
516 if self.is_precompile(program_id) {
517 self.process_precompile(
518 program_id,
519 instruction.data,
520 message.instructions_iter().map(|ix| ix.data),
521 )
522 } else {
523 self.process_instruction(&mut compute_units_consumed, execute_timings)
524 }
525 });
526
527 *accumulated_consumed_units =
528 accumulated_consumed_units.saturating_add(compute_units_consumed);
529 if log::log_enabled!(log::Level::Trace) {
532 execute_timings.details.accumulate_program(
533 program_id,
534 process_instruction_us,
535 compute_units_consumed,
536 result.is_err(),
537 );
538 }
539 self.timings = {
540 execute_timings.details.accumulate(&self.timings);
541 ExecuteDetailsTimings::default()
542 };
543 execute_timings
544 .execute_accessories
545 .process_instructions
546 .total_us += process_instruction_us;
547
548 result.map_err(|err| (top_level_instruction_index as u8, err))?;
549 }
550 Ok(())
551 }
552
553 #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
555 fn prepare_top_level_instructions(
556 &mut self,
557 message: &'ix_data impl SVMMessage,
558 ) -> Result<(), (u8, InstructionError)> {
559 for (top_level_instruction_index, (_, instruction)) in
560 message.program_instructions_iter().enumerate()
561 {
562 let transaction_callee_map_len = message
563 .account_keys()
564 .len()
565 .min(MAX_ACCOUNTS_PER_TRANSACTION);
566 let mut transaction_callee_map: Vec<u16> = vec![u16::MAX; transaction_callee_map_len];
567
568 let mut instruction_accounts: Vec<InstructionAccount> =
569 Vec::with_capacity(instruction.accounts.len());
570 for index_in_transaction in instruction.accounts.iter() {
571 let index_in_callee = transaction_callee_map
572 .get_mut(*index_in_transaction as usize)
573 .expect("Invalid index in transaction");
574
575 if (*index_in_callee as usize) > instruction_accounts.len() {
576 *index_in_callee = instruction_accounts.len() as u16;
577 }
578
579 let index_in_transaction = *index_in_transaction as usize;
580 instruction_accounts.push(InstructionAccount::new(
581 index_in_transaction as IndexOfAccount,
582 message.is_signer(index_in_transaction),
583 message.is_writable(index_in_transaction),
584 ));
585 }
586
587 self.transaction_context
588 .configure_instruction_at_index(
589 top_level_instruction_index,
590 instruction.program_id_index as u16,
591 instruction_accounts,
592 transaction_callee_map,
593 Cow::Borrowed(instruction.data),
594 None,
595 )
596 .map_err(|err| (top_level_instruction_index as u8, err))?;
597 }
598 Ok(())
599 }
600
601 #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
603 pub(crate) fn process_instruction(
604 &mut self,
605 compute_units_consumed: &mut u64,
606 timings: &mut ExecuteTimings,
607 ) -> Result<(), InstructionError> {
608 *compute_units_consumed = 0;
609 self.push()?;
610 self.process_executable_chain(compute_units_consumed, timings)
611 .and(self.pop())
614 }
615
616 #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
618 fn process_precompile(
619 &mut self,
620 program_id: &Pubkey,
621 instruction_data: &[u8],
622 message_instruction_datas_iter: impl Iterator<Item = &'ix_data [u8]>,
623 ) -> Result<(), InstructionError> {
624 self.push()?;
625 let instruction_datas: Vec<_> = message_instruction_datas_iter.collect();
626 self.environment_config
627 .epoch_stake_callback
628 .process_precompile(program_id, instruction_data, instruction_datas)
629 .map_err(InstructionError::from)
630 .and(self.pop())
631 }
632
633 fn process_executable_chain(
635 &mut self,
636 compute_units_consumed: &mut u64,
637 timings: &mut ExecuteTimings,
638 ) -> Result<(), InstructionError> {
639 let instruction_context = self.transaction_context.get_current_instruction_context()?;
640 let process_executable_chain_time = Measure::start("process_executable_chain_time");
641
642 let builtin_id = {
643 let owner_id = instruction_context.get_program_owner()?;
644 if native_loader::check_id(&owner_id) {
645 *instruction_context.get_program_key()?
646 } else if bpf_loader_deprecated::check_id(&owner_id)
647 || bpf_loader::check_id(&owner_id)
648 || bpf_loader_upgradeable::check_id(&owner_id)
649 || loader_v4::check_id(&owner_id)
650 {
651 owner_id
652 } else {
653 return Err(InstructionError::UnsupportedProgramId);
654 }
655 };
656
657 const ENTRYPOINT_KEY: u32 = 0x71E3CF81;
659 let entry = self
660 .program_cache_for_tx_batch
661 .find(&builtin_id)
662 .ok_or(InstructionError::UnsupportedProgramId)?;
663 let function = match &entry.program {
664 ProgramCacheEntryType::Builtin(program) => program
665 .get_function_registry()
666 .lookup_by_key(ENTRYPOINT_KEY)
667 .map(|(_name, (function, _codegen))| function),
668 _ => None,
669 }
670 .ok_or(InstructionError::UnsupportedProgramId)?;
671
672 let program_id = *instruction_context.get_program_key()?;
673 self.transaction_context
674 .set_return_data(program_id, Vec::new())?;
675 let logger = self.get_log_collector();
676 stable_log::program_invoke(&logger, &program_id, self.get_stack_height());
677 let pre_remaining_units = self.get_remaining();
678 self.memory_contexts
680 .set_memory_context_abi_v1(MemoryContext::new(
681 BpfAllocator::new(0),
682 Vec::new(),
683 unsafe {
686 MemoryMapping::new(Vec::new(), &Config::default(), SBPFVersion::Reserved)
687 .unwrap()
688 },
689 ))?;
690 let mut vm = EbpfVm::new(
691 Arc::clone(
692 &**self
693 .environment_config
694 .program_runtime_environments
695 .get_env_for_execution(),
696 ),
697 SBPFVersion::V0,
698 unsafe { std::mem::transmute::<&mut InvokeContext, &mut InvokeContext>(self) },
700 0,
701 );
702 vm.invoke_function(function);
703 let result = match vm.program_result {
704 ProgramResult::Ok(_) => {
705 stable_log::program_success(&logger, &program_id);
706 Ok(())
707 }
708 ProgramResult::Err(ref err) => {
709 if let EbpfError::SyscallError(syscall_error) = err {
710 if let Some(instruction_err) = syscall_error.downcast_ref::<InstructionError>()
711 {
712 stable_log::program_failure(&logger, &program_id, instruction_err);
713 Err(instruction_err.clone())
714 } else {
715 stable_log::program_failure(&logger, &program_id, syscall_error);
716 Err(InstructionError::ProgramFailedToComplete)
717 }
718 } else {
719 stable_log::program_failure(&logger, &program_id, err);
720 Err(InstructionError::ProgramFailedToComplete)
721 }
722 }
723 };
724 let post_remaining_units = self.get_remaining();
725 *compute_units_consumed = pre_remaining_units.saturating_sub(post_remaining_units);
726
727 if builtin_id == program_id && result.is_ok() && *compute_units_consumed == 0 {
728 return Err(InstructionError::BuiltinProgramsMustConsumeComputeUnits);
729 }
730
731 timings
732 .execute_accessories
733 .process_instructions
734 .process_executable_chain_us += process_executable_chain_time.end_as_us();
735 result
736 }
737
738 pub fn get_log_collector(&self) -> Option<Rc<RefCell<LogCollector>>> {
740 self.log_collector.clone()
741 }
742
743 #[cfg(feature = "dev-context-only-utils")]
744 pub fn set_alpenglow_migration_succeeded_for_tests(&mut self, succeeded: bool) {
745 self.environment_config.alpenglow_migration_succeeded = succeeded;
746 }
747
748 pub fn get_compute_budget(&self) -> &SVMTransactionExecutionBudget {
750 &self.compute_budget
751 }
752
753 pub fn get_execution_cost(&self) -> &SVMTransactionExecutionCost {
755 &self.execution_cost
756 }
757
758 pub fn get_feature_set(&self) -> &SVMFeatureSet {
760 self.environment_config.feature_set
761 }
762
763 pub fn get_program_runtime_environment_for_deployment(&self) -> &ProgramRuntimeEnvironment {
764 self.environment_config
765 .program_runtime_environments
766 .get_env_for_deployment()
767 }
768
769 pub fn is_deprecate_legacy_vote_ixs_active(&self) -> bool {
770 self.environment_config
771 .feature_set
772 .deprecate_legacy_vote_ixs
773 }
774
775 pub fn is_alpenglow_migration_succeeded(&self) -> bool {
776 self.environment_config.alpenglow_migration_succeeded
777 }
778
779 pub fn get_epoch_stake(&self) -> u64 {
781 self.environment_config
782 .epoch_stake_callback
783 .get_epoch_stake()
784 }
785
786 pub fn get_epoch_stake_for_vote_account(&self, pubkey: &'a Pubkey) -> u64 {
788 self.environment_config
789 .epoch_stake_callback
790 .get_epoch_stake_for_vote_account(pubkey)
791 }
792
793 pub fn is_precompile(&self, pubkey: &Pubkey) -> bool {
794 self.environment_config
795 .epoch_stake_callback
796 .is_precompile(pubkey)
797 }
798
799 pub fn get_check_aligned(&self) -> bool {
801 self.transaction_context
802 .get_current_instruction_context()
803 .and_then(|instruction_context| {
804 let owner_id = instruction_context.get_program_owner();
805 debug_assert!(owner_id.is_ok());
806 owner_id
807 })
808 .map(|owner_key| owner_key != bpf_loader_deprecated::id())
809 .unwrap_or(true)
810 }
811
812 pub(crate) fn insert_register_trace(&mut self, register_trace: Vec<[u64; 12]>) {
814 if register_trace.is_empty() {
815 return;
816 }
817 let Ok(instruction_context) = self.transaction_context.get_current_instruction_context()
818 else {
819 return;
820 };
821 self.register_traces
822 .push((instruction_context.get_index_in_trace(), register_trace));
823 }
824
825 pub fn iterate_vm_traces(
827 &self,
828 callback: &dyn Fn(InstructionContext, &Executable, RegisterTrace),
829 ) {
830 for (index_in_trace, register_trace) in &self.register_traces {
831 let Ok(instruction_context) = self
832 .transaction_context
833 .get_instruction_context_at_index_in_trace(*index_in_trace)
834 else {
835 continue;
836 };
837 let Ok(program_id) = instruction_context.get_program_key() else {
838 continue;
839 };
840 let Some(entry) = self.program_cache_for_tx_batch.find(program_id) else {
841 continue;
842 };
843 let ProgramCacheEntryType::Loaded(ref executable) = entry.program else {
844 continue;
845 };
846 callback(instruction_context, executable, register_trace.as_slice());
847 }
848 }
849}
850
851#[cfg(feature = "dev-context-only-utils")]
852#[macro_export]
853macro_rules! with_mock_invoke_context_with_feature_set {
854 (
855 $invoke_context:ident,
856 $transaction_context:ident,
857 $feature_set:ident,
858 $top_level_instructions:literal,
859 $transaction_accounts:expr,
860 $all_accounts:expr $(,)?
861 ) => {
862 use {
863 solana_svm_callback::InvokeContextCallback,
864 solana_svm_log_collector::LogCollector,
865 $crate::{
866 __private::{Hash, ReadableAccount, Rent, TransactionContext},
867 execution_budget::{SVMTransactionExecutionBudget, SVMTransactionExecutionCost},
868 invoke_context::{EnvironmentConfig, InvokeContext},
869 loaded_programs::{ProgramCacheForTxBatch, ProgramRuntimeEnvironments},
870 sysvar_cache::SysvarCache,
871 },
872 };
873
874 struct MockInvokeContextCallback {}
875 impl InvokeContextCallback for MockInvokeContextCallback {}
876
877 let compute_budget = SVMTransactionExecutionBudget::new_with_defaults(
878 $feature_set.raise_cpi_nesting_limit_to_8,
879 );
880 let mut sysvar_cache = SysvarCache::default();
881 sysvar_cache.fill_missing_entries(|pubkey, callback| {
882 for (key, account) in $all_accounts.iter() {
883 if key == pubkey {
884 callback(account.data());
885 }
886 }
887 });
888 let mut $transaction_context = TransactionContext::new(
889 $transaction_accounts,
890 Rent::default(),
891 compute_budget.max_instruction_stack_depth,
892 compute_budget.max_instruction_trace_length,
893 $top_level_instructions,
894 );
895 let program_runtime_environments = ProgramRuntimeEnvironments::mock();
896 let environment_config = EnvironmentConfig::new(
897 Hash::default(),
898 0,
899 false,
900 &MockInvokeContextCallback {},
901 $feature_set,
902 &program_runtime_environments,
903 &sysvar_cache,
904 );
905 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
906 let mut $invoke_context = InvokeContext::new(
907 &mut $transaction_context,
908 &mut program_cache_for_tx_batch,
909 environment_config,
910 Some(LogCollector::new_ref()),
911 compute_budget,
912 SVMTransactionExecutionCost::default(),
913 );
914 };
915 (
916 $invoke_context:ident,
917 $transaction_context:ident,
918 $feature_set:ident,
919 $top_level_instructions:literal,
920 $transaction_accounts:expr $(,)?
921 ) => {
922 let transaction_accounts: Vec<(solana_pubkey::Pubkey, solana_account::AccountSharedData)> =
923 $transaction_accounts;
924 $crate::with_mock_invoke_context_with_feature_set!(
925 $invoke_context,
926 $transaction_context,
927 $feature_set,
928 $top_level_instructions,
929 transaction_accounts,
930 &transaction_accounts
931 );
932 };
933 (
934 $invoke_context:ident,
935 $transaction_context:ident,
936 $feature_set:ident,
937 $transaction_accounts:expr $(,)?
938 ) => {
939 $crate::with_mock_invoke_context_with_feature_set!(
940 $invoke_context,
941 $transaction_context,
942 $feature_set,
943 1,
944 $transaction_accounts
945 );
946 };
947}
948
949#[cfg(feature = "dev-context-only-utils")]
950#[macro_export]
951macro_rules! with_mock_invoke_context {
952 (
953 $invoke_context:ident,
954 $transaction_context:ident,
955 $top_level_instructions:literal,
956 $transaction_accounts:expr $(,)?
957 ) => {
958 let feature_set = &solana_svm_feature_set::SVMFeatureSet::default();
959 $crate::with_mock_invoke_context_with_feature_set!(
960 $invoke_context,
961 $transaction_context,
962 feature_set,
963 $top_level_instructions,
964 $transaction_accounts
965 )
966 };
967 (
968 $invoke_context:ident,
969 $transaction_context:ident,
970 $transaction_accounts:expr $(,)?
971 ) => {
972 with_mock_invoke_context!(
973 $invoke_context,
974 $transaction_context,
975 1,
976 $transaction_accounts
977 );
978 };
979}
980
981#[cfg(feature = "dev-context-only-utils")]
982pub fn mock_compile_message<A>(
983 instruction: &Instruction,
984 accounts: &[(Pubkey, A)],
985 program_id: &Pubkey,
986 loader_key: &Pubkey,
987) -> (SanitizedMessage, Vec<(Pubkey, AccountSharedData)>)
988where
989 AccountSharedData: From<A>,
990 A: Clone,
991{
992 let message = Message::new(std::slice::from_ref(instruction), None);
993 let transaction_accounts: Vec<_> = message
994 .account_keys
995 .iter()
996 .map(|key| {
997 let account = accounts
998 .iter()
999 .find(|(k, _)| k == key)
1000 .map(|(_, a)| AccountSharedData::from(a.clone()))
1001 .unwrap_or_else(|| {
1002 if key == program_id {
1003 let mut account = AccountSharedData::new(0, 0, loader_key);
1004 account.set_executable(true);
1005 account
1006 } else {
1007 AccountSharedData::default()
1008 }
1009 });
1010 (*key, account)
1011 })
1012 .collect();
1013
1014 let sanitized_message = SanitizedMessage::Legacy(LegacyMessage::new(message, &HashSet::new()));
1015
1016 (sanitized_message, transaction_accounts)
1017}
1018
1019#[cfg(feature = "dev-context-only-utils")]
1020pub fn mock_process_instruction_with_feature_set<
1021 F: FnMut(&mut InvokeContext),
1022 G: FnMut(&mut InvokeContext),
1023>(
1024 program_id: &Pubkey,
1025 instruction_data: &[u8],
1026 mut accounts: Vec<KeyedAccountSharedData>,
1027 instruction_account_metas: Vec<AccountMeta>,
1028 expected_result: Result<(), InstructionError>,
1029 builtin: BuiltinFunctionRegisterer,
1030 mut pre_adjustments: F,
1031 mut post_adjustments: G,
1032 feature_set: &SVMFeatureSet,
1033) -> Vec<AccountSharedData> {
1034 let original_len = accounts.len();
1035 if !accounts
1036 .iter()
1037 .any(|(key, _)| *key == sysvar::epoch_schedule::id())
1038 {
1039 let mut account = AccountSharedData::new(1, solana_epoch_schedule::SIZE, &sysvar::id());
1040 wincode::serialize_into(account.data_as_mut_slice(), &EpochSchedule::default()).unwrap();
1041 accounts.push((sysvar::epoch_schedule::id(), account));
1042 }
1043
1044 let instruction =
1045 Instruction::new_with_bytes(*program_id, instruction_data, instruction_account_metas);
1046 let (sanitized_message, transaction_accounts) =
1047 mock_compile_message(&instruction, &accounts, program_id, &native_loader::id());
1048
1049 let program_owner = accounts
1050 .iter()
1051 .find(|(key, _)| key == program_id)
1052 .map(|(_, acct)| *acct.owner())
1053 .unwrap_or_else(native_loader::id);
1054 let is_builtin = native_loader::check_id(&program_owner);
1055
1056 with_mock_invoke_context_with_feature_set!(
1057 invoke_context,
1058 transaction_context,
1059 feature_set,
1060 1,
1061 transaction_accounts,
1062 &accounts
1063 );
1064
1065 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1066 program_cache_for_tx_batch.replenish(
1067 if is_builtin {
1068 *program_id
1069 } else {
1070 program_owner
1071 },
1072 Arc::new(ProgramCacheEntry::new_builtin(0, builtin)),
1073 );
1074 program_cache_for_tx_batch.set_slot_for_tests(
1075 invoke_context
1076 .environment_config
1077 .sysvar_cache()
1078 .get_clock()
1079 .map(|clock| clock.slot)
1080 .unwrap_or(1),
1081 );
1082 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1083
1084 pre_adjustments(&mut invoke_context);
1085
1086 invoke_context
1087 .prepare_top_level_instructions(&sanitized_message)
1088 .unwrap();
1089
1090 let result = invoke_context.process_instruction(&mut 0, &mut ExecuteTimings::default());
1091 assert_eq!(result, expected_result);
1092 post_adjustments(&mut invoke_context);
1093
1094 let txn_result_keys: Vec<_> = (0..transaction_context.get_number_of_accounts())
1095 .map(|i| *transaction_context.get_key_of_account_at_index(i).unwrap())
1096 .collect();
1097 let txn_result_accounts = transaction_context.deconstruct_without_keys().unwrap();
1098 let txn_result_map = txn_result_keys
1099 .into_iter()
1100 .zip(txn_result_accounts)
1101 .collect::<HashMap<Pubkey, AccountSharedData>>();
1102
1103 accounts
1104 .into_iter()
1105 .take(original_len)
1106 .map(|(key, original)| txn_result_map.get(&key).cloned().unwrap_or(original))
1107 .collect()
1108}
1109
1110#[cfg(feature = "dev-context-only-utils")]
1111pub fn mock_process_instruction<F: FnMut(&mut InvokeContext), G: FnMut(&mut InvokeContext)>(
1112 program_id: &Pubkey,
1113 instruction_data: &[u8],
1114 accounts: Vec<KeyedAccountSharedData>,
1115 instruction_account_metas: Vec<AccountMeta>,
1116 expected_result: Result<(), InstructionError>,
1117 builtin: BuiltinFunctionRegisterer,
1118 pre_adjustments: F,
1119 post_adjustments: G,
1120) -> Vec<AccountSharedData> {
1121 mock_process_instruction_with_feature_set(
1122 program_id,
1123 instruction_data,
1124 accounts,
1125 instruction_account_metas,
1126 expected_result,
1127 builtin,
1128 pre_adjustments,
1129 post_adjustments,
1130 &SVMFeatureSet::all_enabled(),
1131 )
1132}
1133
1134#[cfg(test)]
1135mod tests {
1136 use {
1137 super::*,
1138 crate::execution_budget::{
1139 DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, MAX_INSTRUCTION_STACK_DEPTH,
1140 MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268,
1141 },
1142 openssl::{
1143 ec::{EcGroup, EcKey},
1144 nid::Nid,
1145 },
1146 serde::{Deserialize, Serialize},
1147 solana_account::{Account, DUMMY_INHERITABLE_ACCOUNT_FIELDS, ReadableAccount},
1148 solana_ed25519_program::new_ed25519_instruction_with_signature,
1149 solana_keypair::{Address, Keypair},
1150 solana_message::AccountKeys,
1151 solana_precompile_error::PrecompileError,
1152 solana_rent::Rent,
1153 solana_sbpf::program::BuiltinFunctionDefinition,
1154 solana_sdk_ids::{ed25519_program, secp256k1_program, system_program},
1155 solana_secp256k1_program::{
1156 eth_address_from_pubkey, new_secp256k1_instruction_with_signature,
1157 },
1158 solana_secp256r1_program::{new_secp256r1_instruction_with_signature, sign_message},
1159 solana_signer::Signer,
1160 solana_svm_feature_set::SVMFeatureSet,
1161 solana_transaction::{Transaction, sanitized::SanitizedTransaction},
1162 solana_transaction_context::{MAX_ACCOUNTS_PER_INSTRUCTION, MAX_ACCOUNTS_PER_TRANSACTION},
1163 test_case::test_case,
1164 };
1165
1166 #[derive(Debug, Serialize, Deserialize)]
1167 enum MockInstruction {
1168 NoopSuccess,
1169 NoopFail,
1170 ModifyOwned,
1171 ModifyNotOwned,
1172 ModifyReadonly,
1173 UnbalancedPush,
1174 UnbalancedPop,
1175 ConsumeComputeUnits {
1176 compute_units_to_consume: u64,
1177 desired_result: Result<(), InstructionError>,
1178 },
1179 Resize {
1180 new_len: u64,
1181 },
1182 }
1183
1184 const MOCK_BUILTIN_COMPUTE_UNIT_COST: u64 = 1;
1185
1186 declare_process_instruction!(
1187 MockBuiltin,
1188 MOCK_BUILTIN_COMPUTE_UNIT_COST,
1189 |invoke_context| {
1190 let transaction_context = &invoke_context.transaction_context;
1191 let instruction_context = transaction_context.get_current_instruction_context()?;
1192 let instruction_data = instruction_context.get_instruction_data();
1193 let program_id = instruction_context.get_program_key()?;
1194 let instruction_accounts = (0..4)
1195 .map(|instruction_account_index| {
1196 InstructionAccount::new(instruction_account_index, false, false)
1197 })
1198 .collect::<Vec<_>>();
1199 assert_eq!(
1200 program_id,
1201 instruction_context
1202 .try_borrow_instruction_account(0)?
1203 .get_owner()
1204 );
1205 assert_ne!(
1206 instruction_context
1207 .try_borrow_instruction_account(1)?
1208 .get_owner(),
1209 instruction_context.get_key_of_instruction_account(0)?
1210 );
1211
1212 if let Ok(instruction) = bincode::deserialize(instruction_data) {
1213 match instruction {
1214 MockInstruction::NoopSuccess => (),
1215 MockInstruction::NoopFail => return Err(InstructionError::GenericError),
1216 MockInstruction::ModifyOwned => instruction_context
1217 .try_borrow_instruction_account(0)?
1218 .set_data_from_slice(&[1])?,
1219 MockInstruction::ModifyNotOwned => instruction_context
1220 .try_borrow_instruction_account(1)?
1221 .set_data_from_slice(&[1])?,
1222 MockInstruction::ModifyReadonly => instruction_context
1223 .try_borrow_instruction_account(2)?
1224 .set_data_from_slice(&[1])?,
1225 MockInstruction::UnbalancedPush => {
1226 instruction_context
1227 .try_borrow_instruction_account(0)?
1228 .checked_add_lamports(1)?;
1229 let program_id = *transaction_context.get_key_of_account_at_index(3)?;
1230 let metas = vec![
1231 AccountMeta::new_readonly(
1232 *transaction_context.get_key_of_account_at_index(0)?,
1233 false,
1234 ),
1235 AccountMeta::new_readonly(
1236 *transaction_context.get_key_of_account_at_index(1)?,
1237 false,
1238 ),
1239 ];
1240 let inner_instruction = Instruction::new_with_bincode(
1241 program_id,
1242 &MockInstruction::NoopSuccess,
1243 metas,
1244 );
1245 invoke_context
1246 .transaction_context
1247 .configure_top_level_instruction_for_tests(
1248 3,
1249 instruction_accounts,
1250 vec![],
1251 )
1252 .unwrap();
1253 let result = invoke_context.push();
1254 assert_eq!(result, Err(InstructionError::UnbalancedInstruction));
1255 result?;
1256 invoke_context
1257 .native_invoke_signed(inner_instruction, &[])
1258 .and(invoke_context.pop())?;
1259 }
1260 MockInstruction::UnbalancedPop => instruction_context
1261 .try_borrow_instruction_account(0)?
1262 .checked_add_lamports(1)?,
1263 MockInstruction::ConsumeComputeUnits {
1264 compute_units_to_consume,
1265 desired_result,
1266 } => {
1267 invoke_context
1268 .compute_meter
1269 .consume_checked(compute_units_to_consume)
1270 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
1271 return desired_result;
1272 }
1273 MockInstruction::Resize { new_len } => instruction_context
1274 .try_borrow_instruction_account(0)?
1275 .set_data_from_slice(&vec![0; new_len as usize])?,
1276 }
1277 } else {
1278 return Err(InstructionError::InvalidInstructionData);
1279 }
1280 Ok(())
1281 }
1282 );
1283
1284 #[test_case(false; "SIMD-0268 disabled")]
1285 #[test_case(true; "SIMD-0268 enabled")]
1286 fn test_instruction_stack_height(simd_0268_active: bool) {
1287 let feature_set = &SVMFeatureSet {
1288 raise_cpi_nesting_limit_to_8: simd_0268_active,
1289 ..SVMFeatureSet::all_enabled()
1290 };
1291 let max_depth = SVMTransactionExecutionBudget::new_with_defaults(simd_0268_active)
1292 .max_instruction_stack_depth;
1293 assert_eq!(
1294 max_depth,
1295 if simd_0268_active {
1296 MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268
1297 } else {
1298 MAX_INSTRUCTION_STACK_DEPTH
1299 },
1300 );
1301
1302 let mut invoke_stack = vec![];
1305 let mut transaction_accounts = vec![];
1306 let mut instruction_accounts = vec![];
1307 for index in 0..max_depth.saturating_add(1) {
1308 let program_id = solana_pubkey::new_rand();
1309 invoke_stack.push(program_id);
1310 transaction_accounts.push((
1311 solana_pubkey::new_rand(),
1312 AccountSharedData::new(1, 1, &program_id),
1313 ));
1314 instruction_accounts.push(InstructionAccount::new(
1315 index as IndexOfAccount,
1316 false,
1317 true,
1318 ));
1319 }
1320
1321 let first_program_account = transaction_accounts.len();
1324 for (index, program_id) in invoke_stack.iter().enumerate() {
1325 transaction_accounts.push((
1326 *program_id,
1327 AccountSharedData::new(1, 1, &solana_pubkey::Pubkey::default()),
1328 ));
1329 instruction_accounts.push(InstructionAccount::new(
1330 index as IndexOfAccount,
1331 false,
1332 false,
1333 ));
1334 }
1335 with_mock_invoke_context_with_feature_set!(
1336 invoke_context,
1337 transaction_context,
1338 feature_set,
1339 transaction_accounts,
1340 );
1341
1342 for depth in 0..max_depth {
1344 assert_eq!(invoke_context.get_stack_height(), depth);
1345 invoke_context
1346 .transaction_context
1347 .configure_top_level_instruction_for_tests(
1348 (first_program_account.saturating_add(depth)) as IndexOfAccount,
1349 instruction_accounts.clone(),
1350 vec![],
1351 )
1352 .unwrap();
1353 assert!(
1354 invoke_context.push().is_ok(),
1355 "push at depth {depth} should succeed (max_depth={max_depth})",
1356 );
1357 }
1358
1359 assert_eq!(invoke_context.get_stack_height(), max_depth);
1361 invoke_context
1362 .transaction_context
1363 .configure_top_level_instruction_for_tests(
1364 (first_program_account.saturating_add(max_depth)) as IndexOfAccount,
1365 instruction_accounts.clone(),
1366 vec![],
1367 )
1368 .unwrap();
1369 assert_eq!(invoke_context.push(), Err(InstructionError::CallDepth),);
1370
1371 assert_eq!(invoke_context.get_stack_height(), max_depth);
1373 }
1374
1375 #[test]
1376 fn test_max_instruction_trace_length_top_level() {
1377 const MAX_INSTRUCTIONS: usize = 8;
1378 let mut transaction_context = TransactionContext::new(
1379 vec![(
1380 Pubkey::new_unique(),
1381 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1382 )],
1383 Rent::default(),
1384 1,
1385 MAX_INSTRUCTIONS,
1386 MAX_INSTRUCTIONS,
1387 );
1388 for _ in 0..MAX_INSTRUCTIONS {
1389 transaction_context.push().unwrap();
1390 transaction_context
1391 .configure_top_level_instruction_for_tests(
1392 0,
1393 vec![InstructionAccount::new(0, false, false)],
1394 vec![],
1395 )
1396 .unwrap();
1397 transaction_context.pop().unwrap();
1398 }
1399 assert_eq!(
1400 transaction_context.push(),
1401 Err(InstructionError::MaxInstructionTraceLengthExceeded)
1402 );
1403 }
1404
1405 #[test]
1406 fn test_max_instruction_trace_length_cpi() {
1407 const MAX_INSTRUCTIONS: usize = 8;
1409 let mut transaction_context = TransactionContext::new(
1410 vec![(
1411 Pubkey::new_unique(),
1412 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1413 )],
1414 Rent::default(),
1415 256,
1416 MAX_INSTRUCTIONS,
1417 2,
1418 );
1419 let num_transaction_accounts = usize::from(transaction_context.get_number_of_accounts());
1420
1421 transaction_context
1422 .configure_instruction_at_index(
1423 0,
1424 0,
1425 vec![InstructionAccount::new(0, false, false)],
1426 vec![u16::MAX; num_transaction_accounts],
1427 Cow::Owned(Vec::new()),
1428 None,
1429 )
1430 .unwrap();
1431
1432 transaction_context
1433 .configure_instruction_at_index(
1434 1,
1435 0,
1436 vec![InstructionAccount::new(0, false, false)],
1437 vec![u16::MAX; num_transaction_accounts],
1438 Cow::Owned(Vec::new()),
1439 None,
1440 )
1441 .unwrap();
1442
1443 for _ in 0..MAX_INSTRUCTIONS {
1444 transaction_context.push().unwrap();
1445 transaction_context
1446 .configure_next_cpi_for_tests(
1447 0,
1448 vec![InstructionAccount::new(0, false, false)],
1449 Vec::new(),
1450 )
1451 .unwrap();
1452 }
1453
1454 assert_eq!(
1455 transaction_context.push(),
1456 Err(InstructionError::MaxInstructionTraceLengthExceeded)
1457 );
1458 }
1459
1460 #[test_case(MockInstruction::NoopSuccess, Ok(()); "NoopSuccess")]
1461 #[test_case(MockInstruction::NoopFail, Err(InstructionError::GenericError); "NoopFail")]
1462 #[test_case(MockInstruction::ModifyOwned, Ok(()); "ModifyOwned")]
1463 #[test_case(MockInstruction::ModifyNotOwned, Err(InstructionError::ExternalAccountDataModified); "ModifyNotOwned")]
1464 #[test_case(MockInstruction::ModifyReadonly, Err(InstructionError::ReadonlyDataModified); "ModifyReadonly")]
1465 #[test_case(MockInstruction::UnbalancedPush, Err(InstructionError::UnbalancedInstruction); "UnbalancedPush")]
1466 #[test_case(MockInstruction::UnbalancedPop, Err(InstructionError::UnbalancedInstruction); "UnbalancedPop")]
1467 fn test_process_instruction_account_modifications(
1468 instruction: MockInstruction,
1469 expected_result: Result<(), InstructionError>,
1470 ) {
1471 let callee_program_id = solana_pubkey::new_rand();
1472 let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1473 let not_owned_account = AccountSharedData::new(84, 1, &solana_pubkey::new_rand());
1474 let readonly_account = AccountSharedData::new(168, 1, &solana_pubkey::new_rand());
1475 let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1476 let mut program_account = AccountSharedData::new(1, 1, &native_loader::id());
1477 program_account.set_executable(true);
1478 let transaction_accounts = vec![
1479 (solana_pubkey::new_rand(), owned_account),
1480 (solana_pubkey::new_rand(), not_owned_account),
1481 (solana_pubkey::new_rand(), readonly_account),
1482 (callee_program_id, program_account),
1483 (solana_pubkey::new_rand(), loader_account),
1484 ];
1485 let metas = vec![
1486 AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1487 AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1488 AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1489 ];
1490 let instruction_accounts = (0..4)
1491 .map(|instruction_account_index| {
1492 InstructionAccount::new(
1493 instruction_account_index,
1494 false,
1495 instruction_account_index < 2,
1496 )
1497 })
1498 .collect::<Vec<_>>();
1499 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1500 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1501 program_cache_for_tx_batch.replenish(
1502 callee_program_id,
1503 Arc::new(ProgramCacheEntry::new_builtin(0, MockBuiltin::register)),
1504 );
1505 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1506
1507 invoke_context
1509 .transaction_context
1510 .configure_top_level_instruction_for_tests(4, instruction_accounts, vec![])
1511 .unwrap();
1512 invoke_context.push().unwrap();
1513 let inner_instruction =
1514 Instruction::new_with_bincode(callee_program_id, &instruction, metas);
1515 let result = invoke_context
1516 .native_invoke_signed(inner_instruction, &[])
1517 .and(invoke_context.pop());
1518 assert_eq!(result, expected_result);
1519 }
1520
1521 #[test_case(Ok(()); "Ok")]
1522 #[test_case(Err(InstructionError::GenericError); "GenericError")]
1523 fn test_process_instruction_compute_unit_consumption(
1524 expected_result: Result<(), InstructionError>,
1525 ) {
1526 let callee_program_id = solana_pubkey::new_rand();
1527 let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1528 let not_owned_account = AccountSharedData::new(84, 1, &solana_pubkey::new_rand());
1529 let readonly_account = AccountSharedData::new(168, 1, &solana_pubkey::new_rand());
1530 let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1531 let mut program_account = AccountSharedData::new(1, 1, &native_loader::id());
1532 program_account.set_executable(true);
1533 let transaction_accounts = vec![
1534 (solana_pubkey::new_rand(), owned_account),
1535 (solana_pubkey::new_rand(), not_owned_account),
1536 (solana_pubkey::new_rand(), readonly_account),
1537 (callee_program_id, program_account),
1538 (solana_pubkey::new_rand(), loader_account),
1539 ];
1540 let metas = vec![
1541 AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1542 AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1543 AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1544 ];
1545 let instruction_accounts = (0..4)
1546 .map(|instruction_account_index| {
1547 InstructionAccount::new(
1548 instruction_account_index,
1549 false,
1550 instruction_account_index < 2,
1551 )
1552 })
1553 .collect::<Vec<_>>();
1554 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1555 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1556 program_cache_for_tx_batch.replenish(
1557 callee_program_id,
1558 Arc::new(ProgramCacheEntry::new_builtin(0, MockBuiltin::register)),
1559 );
1560 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1561
1562 let compute_units_to_consume = 10;
1564 invoke_context
1565 .transaction_context
1566 .configure_top_level_instruction_for_tests(4, instruction_accounts, vec![])
1567 .unwrap();
1568 invoke_context.push().unwrap();
1569 let inner_instruction = Instruction::new_with_bincode(
1570 callee_program_id,
1571 &MockInstruction::ConsumeComputeUnits {
1572 compute_units_to_consume,
1573 desired_result: expected_result.clone(),
1574 },
1575 metas,
1576 );
1577 invoke_context
1578 .prepare_next_cpi_instruction(inner_instruction, &[])
1579 .unwrap();
1580
1581 let mut compute_units_consumed = 0;
1582 let result = invoke_context
1583 .process_instruction(&mut compute_units_consumed, &mut ExecuteTimings::default());
1584
1585 assert!(compute_units_consumed > 0);
1589 assert_eq!(
1590 compute_units_consumed,
1591 compute_units_to_consume.saturating_add(MOCK_BUILTIN_COMPUTE_UNIT_COST),
1592 );
1593 assert_eq!(result, expected_result);
1594
1595 invoke_context.pop().unwrap();
1596 }
1597
1598 #[test]
1599 fn test_invoke_context_compute_budget() {
1600 let transaction_accounts = vec![(solana_pubkey::new_rand(), AccountSharedData::default())];
1601 let execution_budget = SVMTransactionExecutionBudget {
1602 compute_unit_limit: u64::from(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT),
1603 ..SVMTransactionExecutionBudget::default()
1604 };
1605
1606 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1607 invoke_context.compute_budget = execution_budget;
1608
1609 invoke_context
1610 .transaction_context
1611 .configure_top_level_instruction_for_tests(0, vec![], vec![])
1612 .unwrap();
1613 invoke_context.push().unwrap();
1614 assert_eq!(*invoke_context.get_compute_budget(), execution_budget);
1615 invoke_context.pop().unwrap();
1616 }
1617
1618 #[test_case(0; "Resize the account to *the same size*, so not consuming any additional size")]
1619 #[test_case(1; "Resize the account larger")]
1620 #[test_case(-1; "Resize the account smaller")]
1621 fn test_process_instruction_accounts_resize_delta(resize_delta: i64) {
1622 let program_key = Pubkey::new_unique();
1623 let user_account_data_len = 123u64;
1624 let user_account =
1625 AccountSharedData::new(100, user_account_data_len as usize, &program_key);
1626 let dummy_account = AccountSharedData::new(10, 0, &program_key);
1627 let mut program_account = AccountSharedData::new(500, 500, &native_loader::id());
1628 program_account.set_executable(true);
1629 let transaction_accounts = vec![
1630 (Pubkey::new_unique(), user_account),
1631 (Pubkey::new_unique(), dummy_account),
1632 (program_key, program_account),
1633 ];
1634 let instruction_accounts = vec![
1635 InstructionAccount::new(0, false, true),
1636 InstructionAccount::new(1, false, false),
1637 ];
1638 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1639 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1640 program_cache_for_tx_batch.replenish(
1641 program_key,
1642 Arc::new(ProgramCacheEntry::new_builtin(0, MockBuiltin::register)),
1643 );
1644 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1645
1646 let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
1647 let instruction_data = bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1648
1649 invoke_context
1650 .transaction_context
1651 .configure_top_level_instruction_for_tests(2, instruction_accounts, instruction_data)
1652 .unwrap();
1653 let result = invoke_context.process_instruction(&mut 0, &mut ExecuteTimings::default());
1654
1655 assert!(result.is_ok());
1656 assert_eq!(
1657 invoke_context.transaction_context.accounts().resize_delta(),
1658 resize_delta
1659 );
1660 }
1661
1662 #[test]
1663 fn test_prepare_instruction_maximum_accounts() {
1664 const MAX_ACCOUNTS_REFERENCED: usize = u16::MAX as usize;
1665 let mut transaction_accounts: Vec<KeyedAccountSharedData> =
1666 Vec::with_capacity(MAX_ACCOUNTS_PER_TRANSACTION);
1667 let mut account_metas: Vec<AccountMeta> = Vec::with_capacity(MAX_ACCOUNTS_REFERENCED);
1668
1669 let fee_payer = Keypair::new();
1671 transaction_accounts.push((
1672 fee_payer.pubkey(),
1673 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1674 ));
1675 account_metas.push(AccountMeta::new(fee_payer.pubkey(), true));
1676
1677 let program_id = Pubkey::new_unique();
1678 let mut program_account = AccountSharedData::new(1, 1, &Pubkey::new_unique());
1679 program_account.set_executable(true);
1680 transaction_accounts.push((program_id, program_account));
1681 account_metas.push(AccountMeta::new_readonly(program_id, false));
1682
1683 for i in 2..MAX_ACCOUNTS_REFERENCED {
1684 if i < MAX_ACCOUNTS_PER_TRANSACTION {
1686 let key = Pubkey::new_unique();
1687 transaction_accounts
1688 .push((key, AccountSharedData::new(1, 1, &Pubkey::new_unique())));
1689 account_metas.push(AccountMeta::new_readonly(key, false));
1690 } else {
1691 let repeated_key = transaction_accounts
1692 .get(i % MAX_ACCOUNTS_PER_TRANSACTION)
1693 .unwrap()
1694 .0;
1695 account_metas.push(AccountMeta::new_readonly(repeated_key, false));
1696 }
1697 }
1698
1699 with_mock_invoke_context!(invoke_context, transaction_context, 2, transaction_accounts);
1700
1701 let instruction_1 = Instruction::new_with_bytes(program_id, &[20], account_metas.clone());
1702
1703 let instruction_2 = Instruction::new_with_bytes(
1704 program_id,
1705 &[20],
1706 account_metas.iter().rev().cloned().collect(),
1707 );
1708
1709 let transaction = Transaction::new_with_payer(
1710 &[instruction_1.clone(), instruction_2.clone()],
1711 Some(&fee_payer.pubkey()),
1712 );
1713
1714 let sanitized =
1715 SanitizedTransaction::try_from_legacy_transaction(transaction, &HashSet::new())
1716 .unwrap();
1717
1718 fn test_case_1(invoke_context: &InvokeContext) {
1719 let instruction_context = invoke_context
1720 .transaction_context
1721 .get_next_instruction_context()
1722 .unwrap();
1723 for index_in_instruction in 0..MAX_ACCOUNTS_REFERENCED as IndexOfAccount {
1724 let index_in_transaction = instruction_context
1725 .get_index_of_instruction_account_in_transaction(index_in_instruction)
1726 .unwrap();
1727 let other_ix_index = instruction_context
1728 .get_index_of_account_in_instruction(index_in_transaction)
1729 .unwrap();
1730 if (index_in_instruction as usize) < MAX_ACCOUNTS_PER_TRANSACTION {
1731 assert_eq!(index_in_instruction, index_in_transaction);
1732 assert_eq!(index_in_instruction, other_ix_index);
1733 } else {
1734 assert_eq!(
1735 index_in_instruction as usize % MAX_ACCOUNTS_PER_TRANSACTION,
1736 index_in_transaction as usize
1737 );
1738 assert_eq!(
1739 index_in_instruction as usize % MAX_ACCOUNTS_PER_TRANSACTION,
1740 other_ix_index as usize
1741 );
1742 }
1743 }
1744 }
1745
1746 fn test_case_2(invoke_context: &InvokeContext) {
1747 let instruction_context = invoke_context
1748 .transaction_context
1749 .get_next_instruction_context()
1750 .unwrap();
1751 for index_in_instruction in 0..MAX_ACCOUNTS_REFERENCED as IndexOfAccount {
1752 let index_in_transaction = instruction_context
1753 .get_index_of_instruction_account_in_transaction(index_in_instruction)
1754 .unwrap();
1755 let other_ix_index = instruction_context
1756 .get_index_of_account_in_instruction(index_in_transaction)
1757 .unwrap();
1758 assert_eq!(
1759 index_in_transaction,
1760 (MAX_ACCOUNTS_REFERENCED as u16)
1761 .saturating_sub(index_in_instruction)
1762 .saturating_sub(1)
1763 .overflowing_rem(MAX_ACCOUNTS_PER_TRANSACTION as u16)
1764 .0
1765 );
1766 if (index_in_instruction as usize) < MAX_ACCOUNTS_PER_TRANSACTION {
1767 assert_eq!(index_in_instruction, other_ix_index);
1768 } else {
1769 assert_eq!(
1770 index_in_instruction as usize % MAX_ACCOUNTS_PER_TRANSACTION,
1771 other_ix_index as usize
1772 );
1773 }
1774 }
1775 }
1776
1777 invoke_context
1778 .prepare_top_level_instructions(&sanitized)
1779 .unwrap();
1780
1781 test_case_1(&invoke_context);
1782
1783 invoke_context.transaction_context.push().unwrap();
1784 invoke_context.transaction_context.pop().unwrap();
1785
1786 test_case_2(&invoke_context);
1787
1788 invoke_context.transaction_context.push().unwrap();
1789 invoke_context
1790 .prepare_next_cpi_instruction(instruction_1, &[fee_payer.pubkey()])
1791 .unwrap();
1792 test_case_1(&invoke_context);
1793
1794 invoke_context.transaction_context.push().unwrap();
1795 invoke_context
1796 .prepare_next_cpi_instruction(instruction_2, &[fee_payer.pubkey()])
1797 .unwrap();
1798 test_case_2(&invoke_context);
1799 }
1800
1801 #[test]
1802 fn test_duplicated_accounts() {
1803 let mut transaction_accounts: Vec<KeyedAccountSharedData> =
1804 Vec::with_capacity(MAX_ACCOUNTS_PER_TRANSACTION);
1805 let mut account_metas: Vec<AccountMeta> =
1806 Vec::with_capacity(MAX_ACCOUNTS_PER_INSTRUCTION.saturating_sub(1));
1807
1808 let fee_payer = Keypair::new();
1810 transaction_accounts.push((
1811 fee_payer.pubkey(),
1812 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1813 ));
1814 account_metas.push(AccountMeta::new(fee_payer.pubkey(), true));
1815
1816 let program_id = Pubkey::new_unique();
1817 let mut program_account = AccountSharedData::new(1, 1, &Pubkey::new_unique());
1818 program_account.set_executable(true);
1819 transaction_accounts.push((program_id, program_account));
1820 account_metas.push(AccountMeta::new_readonly(program_id, false));
1821
1822 for i in 2..account_metas.capacity() {
1823 if i % 2 == 0 {
1824 let key = Pubkey::new_unique();
1825 transaction_accounts
1826 .push((key, AccountSharedData::new(1, 1, &Pubkey::new_unique())));
1827 account_metas.push(AccountMeta::new_readonly(key, false));
1828 } else {
1829 let last_key = transaction_accounts.last().unwrap().0;
1830 account_metas.push(AccountMeta::new_readonly(last_key, false));
1831 }
1832 }
1833
1834 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1835
1836 let instruction = Instruction::new_with_bytes(program_id, &[20], account_metas.clone());
1837
1838 let transaction = Transaction::new_with_payer(&[instruction], Some(&fee_payer.pubkey()));
1839
1840 let sanitized =
1841 SanitizedTransaction::try_from_legacy_transaction(transaction, &HashSet::new())
1842 .unwrap();
1843
1844 invoke_context
1845 .prepare_top_level_instructions(&sanitized)
1846 .unwrap();
1847
1848 {
1849 let instruction_context = invoke_context
1850 .transaction_context
1851 .get_next_instruction_context()
1852 .unwrap();
1853 for index_in_instruction in 2..account_metas.len() as IndexOfAccount {
1854 let is_duplicate = instruction_context
1855 .is_instruction_account_duplicate(index_in_instruction)
1856 .unwrap();
1857 if index_in_instruction % 2 == 0 {
1858 assert!(is_duplicate.is_none());
1859 } else {
1860 assert_eq!(is_duplicate, Some(index_in_instruction.saturating_sub(1)));
1861 }
1862 }
1863 }
1864
1865 invoke_context.transaction_context.push().unwrap();
1866
1867 let instruction = Instruction::new_with_bytes(
1868 program_id,
1869 &[20],
1870 account_metas.iter().cloned().rev().collect(),
1871 );
1872
1873 invoke_context
1874 .prepare_next_cpi_instruction(instruction, &[fee_payer.pubkey()])
1875 .unwrap();
1876 let instruction_context = invoke_context
1877 .transaction_context
1878 .get_next_instruction_context()
1879 .unwrap();
1880 for index_in_instruction in 2..account_metas.len().saturating_sub(1) as u16 {
1881 let is_duplicate = instruction_context
1882 .is_instruction_account_duplicate(index_in_instruction)
1883 .unwrap();
1884 if index_in_instruction % 2 == 0 {
1885 assert!(is_duplicate.is_none());
1886 } else {
1887 assert_eq!(is_duplicate, Some(index_in_instruction.saturating_sub(1)));
1888 }
1889 }
1890 }
1891
1892 const TEST_CALLER_PROGRAM_ID: Pubkey = Pubkey::new_from_array([1u8; 32]);
1894 const TEST_CALLEE_PROGRAM_ID: Pubkey = Pubkey::new_from_array([2u8; 32]);
1895 const TEST_WRONG_PROGRAM_ID: Pubkey = Pubkey::new_from_array([3u8; 32]);
1896 const TEST_MOCK_EXTRA_KEY: Pubkey = Pubkey::new_from_array([4u8; 32]);
1897 const TEST_ACCOUNT_KEY: Pubkey = Pubkey::new_from_array([5u8; 32]);
1898
1899 fn run_native_invoke_signed_test(
1908 target_key: Pubkey,
1909 target_is_signer: bool,
1910 inner_instruction: Instruction,
1911 signer_seeds: &[&[&[u8]]],
1912 ) -> Result<(), InstructionError> {
1913 let target_account = AccountSharedData::new(100, 0, &TEST_CALLEE_PROGRAM_ID);
1914 let mock_extra_account = AccountSharedData::new(0, 1, &system_program::id());
1915 let mut caller_program_account = AccountSharedData::new(1, 1, &native_loader::id());
1916 caller_program_account.set_executable(true);
1917 let mut callee_program_account = AccountSharedData::new(1, 1, &native_loader::id());
1918 callee_program_account.set_executable(true);
1919 let transaction_accounts = vec![
1920 (target_key, target_account),
1921 (TEST_CALLER_PROGRAM_ID, caller_program_account),
1922 (TEST_MOCK_EXTRA_KEY, mock_extra_account),
1923 (TEST_CALLEE_PROGRAM_ID, callee_program_account),
1924 ];
1925
1926 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1927 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1928 program_cache_for_tx_batch.replenish(
1929 TEST_CALLEE_PROGRAM_ID,
1930 Arc::new(ProgramCacheEntry::new_builtin(0, MockBuiltin::register)),
1931 );
1932 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1933
1934 let instruction_accounts = (0..4)
1935 .map(|i| InstructionAccount::new(i, i == 0 && target_is_signer, i < 2))
1936 .collect::<Vec<_>>();
1937 invoke_context
1938 .transaction_context
1939 .configure_top_level_instruction_for_tests(1, instruction_accounts, vec![])
1940 .unwrap();
1941 invoke_context.push().unwrap();
1942
1943 let result = invoke_context.native_invoke_signed(inner_instruction, signer_seeds);
1944 invoke_context.pop().unwrap();
1945 result
1946 }
1947
1948 #[test]
1950 fn test_native_invoke_signed_with_valid_pda_signer() {
1951 let (pda_key, bump_seed) =
1952 Pubkey::find_program_address(&[b"seed"], &TEST_CALLER_PROGRAM_ID);
1953 let instruction = Instruction::new_with_bincode(
1954 TEST_CALLEE_PROGRAM_ID,
1955 &MockInstruction::NoopSuccess,
1956 vec![
1957 AccountMeta::new(pda_key, true),
1958 AccountMeta::new_readonly(TEST_MOCK_EXTRA_KEY, false),
1959 ],
1960 );
1961 let result =
1962 run_native_invoke_signed_test(pda_key, false, instruction, &[&[b"seed", &[bump_seed]]]);
1963 assert!(
1964 result.is_ok(),
1965 "valid PDA signer should succeed: {result:?}"
1966 );
1967 }
1968
1969 #[test]
1973 fn test_native_invoke_signed_with_invalid_seeds() {
1974 let instruction = Instruction::new_with_bincode(
1975 TEST_CALLEE_PROGRAM_ID,
1976 &MockInstruction::NoopSuccess,
1977 vec![AccountMeta::new(TEST_ACCOUNT_KEY, true)],
1978 );
1979 let oversized_seed = [0u8; 33];
1980 let result = run_native_invoke_signed_test(
1981 TEST_ACCOUNT_KEY,
1982 false,
1983 instruction,
1984 &[&[&oversized_seed]],
1985 );
1986 assert_eq!(result, Err(InstructionError::Custom(0)));
1987 }
1988
1989 #[test]
1992 fn test_native_invoke_signed_pda_privilege_escalation_without_seeds() {
1993 let (pda_key, _bump_seed) =
1994 Pubkey::find_program_address(&[b"seed"], &TEST_CALLER_PROGRAM_ID);
1995 let instruction = Instruction::new_with_bincode(
1996 TEST_CALLEE_PROGRAM_ID,
1997 &MockInstruction::NoopSuccess,
1998 vec![AccountMeta::new(pda_key, true)],
1999 );
2000 let result = run_native_invoke_signed_test(pda_key, false, instruction, &[]);
2001 assert_eq!(result, Err(InstructionError::PrivilegeEscalation));
2002 }
2003
2004 #[test]
2007 fn test_native_invoke_signed_uses_caller_program_id_for_pda() {
2008 let (pda_key, bump_seed) = Pubkey::find_program_address(&[b"seed"], &TEST_WRONG_PROGRAM_ID);
2009 let instruction = Instruction::new_with_bincode(
2010 TEST_CALLEE_PROGRAM_ID,
2011 &MockInstruction::NoopSuccess,
2012 vec![AccountMeta::new(pda_key, true)],
2013 );
2014 let result =
2015 run_native_invoke_signed_test(pda_key, false, instruction, &[&[b"seed", &[bump_seed]]]);
2016 assert_eq!(result, Err(InstructionError::PrivilegeEscalation));
2017 }
2018
2019 #[test]
2021 fn test_native_invoke_signed_top_level_signer_needs_no_seeds() {
2022 let (pda_key, _bump_seed) =
2023 Pubkey::find_program_address(&[b"seed"], &TEST_CALLER_PROGRAM_ID);
2024 let instruction = Instruction::new_with_bincode(
2025 TEST_CALLEE_PROGRAM_ID,
2026 &MockInstruction::NoopSuccess,
2027 vec![
2028 AccountMeta::new(pda_key, true),
2029 AccountMeta::new_readonly(TEST_MOCK_EXTRA_KEY, false),
2030 ],
2031 );
2032 let result = run_native_invoke_signed_test(pda_key, true, instruction, &[]);
2033 assert!(
2034 result.is_ok(),
2035 "top-level signer should not need seeds: {result:?}"
2036 );
2037 }
2038
2039 #[test]
2040 fn test_compile_message() {
2041 let program_id = Pubkey::new_from_array([1u8; 32]);
2042 let writable = Pubkey::new_from_array([2u8; 32]);
2043 let loader_key = Pubkey::new_from_array([3u8; 32]);
2044
2045 let instruction = Instruction {
2046 program_id,
2047 accounts: vec![AccountMeta::new(writable, false)],
2048 data: vec![1, 2, 3],
2049 };
2050
2051 let accounts = vec![(
2052 writable,
2053 Account {
2054 lamports: 100,
2055 ..Account::default()
2056 },
2057 )];
2058
2059 let (message, tx_accounts) =
2060 mock_compile_message(&instruction, &accounts, &program_id, &loader_key);
2061
2062 assert_eq!(message.instructions().len(), 1);
2063 assert_eq!(tx_accounts.len(), 2);
2064 assert_eq!(tx_accounts.first().unwrap().0, writable);
2065 assert_eq!(tx_accounts.get(1).unwrap().0, program_id);
2066
2067 assert!(!message.is_signer(0));
2069 }
2070
2071 struct MockCallback {}
2072 impl InvokeContextCallback for MockCallback {}
2073
2074 fn create_loadable_account_for_test(name: &str) -> AccountSharedData {
2075 let (lamports, rent_epoch) = DUMMY_INHERITABLE_ACCOUNT_FIELDS;
2076 AccountSharedData::from(Account {
2077 lamports,
2078 owner: native_loader::id(),
2079 data: name.as_bytes().to_vec(),
2080 executable: true,
2081 rent_epoch,
2082 })
2083 }
2084
2085 fn new_sanitized_message(message: Message) -> SanitizedMessage {
2086 SanitizedMessage::try_from_legacy_message(message, &HashSet::new()).unwrap()
2087 }
2088
2089 #[test]
2090 fn test_process_message_readonly_handling() {
2091 #[derive(serde::Serialize, serde::Deserialize)]
2092 enum MockSystemInstruction {
2093 Correct,
2094 TransferLamports { lamports: u64 },
2095 ChangeData { data: u8 },
2096 }
2097
2098 declare_process_instruction!(MockBuiltin, 1, |invoke_context| {
2099 let transaction_context = &invoke_context.transaction_context;
2100 let instruction_context = transaction_context.get_current_instruction_context()?;
2101 let instruction_data = instruction_context.get_instruction_data();
2102 if let Ok(instruction) = bincode::deserialize(instruction_data) {
2103 match instruction {
2104 MockSystemInstruction::Correct => Ok(()),
2105 MockSystemInstruction::TransferLamports { lamports } => {
2106 instruction_context
2107 .try_borrow_instruction_account(0)?
2108 .checked_sub_lamports(lamports)?;
2109 instruction_context
2110 .try_borrow_instruction_account(1)?
2111 .checked_add_lamports(lamports)?;
2112 Ok(())
2113 }
2114 MockSystemInstruction::ChangeData { data } => {
2115 instruction_context
2116 .try_borrow_instruction_account(1)?
2117 .set_data_from_slice(&[data])?;
2118 Ok(())
2119 }
2120 }
2121 } else {
2122 Err(InstructionError::InvalidInstructionData)
2123 }
2124 });
2125
2126 let writable_pubkey = Pubkey::new_unique();
2127 let readonly_pubkey = Pubkey::new_unique();
2128 let mock_system_program_id = Pubkey::new_unique();
2129
2130 let accounts = vec![
2131 (
2132 writable_pubkey,
2133 AccountSharedData::new(100, 1, &mock_system_program_id),
2134 ),
2135 (
2136 readonly_pubkey,
2137 AccountSharedData::new(0, 1, &mock_system_program_id),
2138 ),
2139 (
2140 mock_system_program_id,
2141 create_loadable_account_for_test("mock_system_program"),
2142 ),
2143 ];
2144 let mut transaction_context =
2145 TransactionContext::new(accounts.clone(), Rent::default(), 1, 3, 1);
2146 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
2147 program_cache_for_tx_batch.replenish(
2148 mock_system_program_id,
2149 Arc::new(ProgramCacheEntry::new_builtin(0, MockBuiltin::register)),
2150 );
2151 let account_keys = (0..transaction_context.get_number_of_accounts())
2152 .map(|index| {
2153 *transaction_context
2154 .get_key_of_account_at_index(index)
2155 .unwrap()
2156 })
2157 .collect::<Vec<_>>();
2158 let account_metas = vec![
2159 AccountMeta::new(writable_pubkey, true),
2160 AccountMeta::new_readonly(readonly_pubkey, false),
2161 ];
2162
2163 let message = new_sanitized_message(Message::new_with_compiled_instructions(
2164 1,
2165 0,
2166 2,
2167 account_keys.clone(),
2168 Hash::default(),
2169 AccountKeys::new(&account_keys, None).compile_instructions(&[
2170 Instruction::new_with_bincode(
2171 mock_system_program_id,
2172 &MockSystemInstruction::Correct,
2173 account_metas.clone(),
2174 ),
2175 ]),
2176 ));
2177 let sysvar_cache = SysvarCache::default();
2178 let feature_set = SVMFeatureSet::all_enabled();
2179 let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2180 let environment_config = EnvironmentConfig::new(
2181 Hash::default(),
2182 0,
2183 false,
2184 &MockCallback {},
2185 &feature_set,
2186 &program_runtime_environments,
2187 &sysvar_cache,
2188 );
2189 let mut invoke_context = InvokeContext::new(
2190 &mut transaction_context,
2191 &mut program_cache_for_tx_batch,
2192 environment_config,
2193 None,
2194 SVMTransactionExecutionBudget::default(),
2195 SVMTransactionExecutionCost::default(),
2196 );
2197 let result =
2198 invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2199 assert!(result.is_ok());
2200 assert_eq!(
2201 transaction_context
2202 .accounts()
2203 .try_borrow(0)
2204 .unwrap()
2205 .lamports(),
2206 100
2207 );
2208 assert_eq!(
2209 transaction_context
2210 .accounts()
2211 .try_borrow(1)
2212 .unwrap()
2213 .lamports(),
2214 0
2215 );
2216
2217 let message = new_sanitized_message(Message::new_with_compiled_instructions(
2218 1,
2219 0,
2220 2,
2221 account_keys.clone(),
2222 Hash::default(),
2223 AccountKeys::new(&account_keys, None).compile_instructions(&[
2224 Instruction::new_with_bincode(
2225 mock_system_program_id,
2226 &MockSystemInstruction::TransferLamports { lamports: 50 },
2227 account_metas.clone(),
2228 ),
2229 ]),
2230 ));
2231 let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2232 let environment_config = EnvironmentConfig::new(
2233 Hash::default(),
2234 0,
2235 false,
2236 &MockCallback {},
2237 &feature_set,
2238 &program_runtime_environments,
2239 &sysvar_cache,
2240 );
2241 let mut transaction_context =
2242 TransactionContext::new(accounts.clone(), Rent::default(), 1, 3, 1);
2243 let mut invoke_context = InvokeContext::new(
2244 &mut transaction_context,
2245 &mut program_cache_for_tx_batch,
2246 environment_config,
2247 None,
2248 SVMTransactionExecutionBudget::default(),
2249 SVMTransactionExecutionCost::default(),
2250 );
2251 let result =
2252 invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2253 assert_eq!(result, Err((0, InstructionError::ReadonlyLamportChange)));
2254
2255 let message = new_sanitized_message(Message::new_with_compiled_instructions(
2256 1,
2257 0,
2258 2,
2259 account_keys.clone(),
2260 Hash::default(),
2261 AccountKeys::new(&account_keys, None).compile_instructions(&[
2262 Instruction::new_with_bincode(
2263 mock_system_program_id,
2264 &MockSystemInstruction::ChangeData { data: 50 },
2265 account_metas,
2266 ),
2267 ]),
2268 ));
2269 let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2270 let environment_config = EnvironmentConfig::new(
2271 Hash::default(),
2272 0,
2273 false,
2274 &MockCallback {},
2275 &feature_set,
2276 &program_runtime_environments,
2277 &sysvar_cache,
2278 );
2279 let mut transaction_context = TransactionContext::new(accounts, Rent::default(), 1, 3, 1);
2280 let mut invoke_context = InvokeContext::new(
2281 &mut transaction_context,
2282 &mut program_cache_for_tx_batch,
2283 environment_config,
2284 None,
2285 SVMTransactionExecutionBudget::default(),
2286 SVMTransactionExecutionCost::default(),
2287 );
2288 let result =
2289 invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2290 assert_eq!(result, Err((0, InstructionError::ReadonlyDataModified)));
2291 }
2292
2293 #[test]
2294 fn test_process_message_duplicate_accounts() {
2295 #[derive(serde::Serialize, serde::Deserialize)]
2296 enum MockSystemInstruction {
2297 BorrowFail,
2298 MultiBorrowMut,
2299 DoWork { lamports: u64, data: u8 },
2300 }
2301
2302 declare_process_instruction!(MockBuiltin, 1, |invoke_context| {
2303 let transaction_context = &invoke_context.transaction_context;
2304 let instruction_context = transaction_context.get_current_instruction_context()?;
2305 let instruction_data = instruction_context.get_instruction_data();
2306 let mut to_account = instruction_context.try_borrow_instruction_account(1)?;
2307 if let Ok(instruction) = bincode::deserialize(instruction_data) {
2308 match instruction {
2309 MockSystemInstruction::BorrowFail => {
2310 let from_account = instruction_context.try_borrow_instruction_account(0)?;
2311 let dup_account = instruction_context.try_borrow_instruction_account(2)?;
2312 if from_account.get_lamports() != dup_account.get_lamports() {
2313 return Err(InstructionError::InvalidArgument);
2314 }
2315 Ok(())
2316 }
2317 MockSystemInstruction::MultiBorrowMut => {
2318 let lamports_a = instruction_context
2319 .try_borrow_instruction_account(0)?
2320 .get_lamports();
2321 let lamports_b = instruction_context
2322 .try_borrow_instruction_account(2)?
2323 .get_lamports();
2324 if lamports_a != lamports_b {
2325 return Err(InstructionError::InvalidArgument);
2326 }
2327 Ok(())
2328 }
2329 MockSystemInstruction::DoWork { lamports, data } => {
2330 let mut dup_account =
2331 instruction_context.try_borrow_instruction_account(2)?;
2332 dup_account.checked_sub_lamports(lamports)?;
2333 to_account.checked_add_lamports(lamports)?;
2334 dup_account.set_data_from_slice(&[data])?;
2335 drop(dup_account);
2336 let mut from_account =
2337 instruction_context.try_borrow_instruction_account(0)?;
2338 from_account.checked_sub_lamports(lamports)?;
2339 to_account.checked_add_lamports(lamports)?;
2340 Ok(())
2341 }
2342 }
2343 } else {
2344 Err(InstructionError::InvalidInstructionData)
2345 }
2346 });
2347 let mock_program_id = Pubkey::from([2u8; 32]);
2348 let accounts = vec![
2349 (
2350 solana_pubkey::new_rand(),
2351 AccountSharedData::new(100, 1, &mock_program_id),
2352 ),
2353 (
2354 solana_pubkey::new_rand(),
2355 AccountSharedData::new(0, 1, &mock_program_id),
2356 ),
2357 (
2358 mock_program_id,
2359 create_loadable_account_for_test("mock_system_program"),
2360 ),
2361 ];
2362 let mut transaction_context =
2363 TransactionContext::new(accounts.clone(), Rent::default(), 1, 3, 1);
2364 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
2365 program_cache_for_tx_batch.replenish(
2366 mock_program_id,
2367 Arc::new(ProgramCacheEntry::new_builtin(0, MockBuiltin::register)),
2368 );
2369 let account_metas = vec![
2370 AccountMeta::new(
2371 *transaction_context.get_key_of_account_at_index(0).unwrap(),
2372 true,
2373 ),
2374 AccountMeta::new(
2375 *transaction_context.get_key_of_account_at_index(1).unwrap(),
2376 false,
2377 ),
2378 AccountMeta::new(
2379 *transaction_context.get_key_of_account_at_index(0).unwrap(),
2380 false,
2381 ),
2382 ];
2383
2384 let message = new_sanitized_message(Message::new(
2386 &[Instruction::new_with_bincode(
2387 mock_program_id,
2388 &MockSystemInstruction::BorrowFail,
2389 account_metas.clone(),
2390 )],
2391 Some(transaction_context.get_key_of_account_at_index(0).unwrap()),
2392 ));
2393 let sysvar_cache = SysvarCache::default();
2394 let feature_set = SVMFeatureSet::all_enabled();
2395 let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2396 let environment_config = EnvironmentConfig::new(
2397 Hash::default(),
2398 0,
2399 false,
2400 &MockCallback {},
2401 &feature_set,
2402 &program_runtime_environments,
2403 &sysvar_cache,
2404 );
2405 let mut invoke_context = InvokeContext::new(
2406 &mut transaction_context,
2407 &mut program_cache_for_tx_batch,
2408 environment_config,
2409 None,
2410 SVMTransactionExecutionBudget::default(),
2411 SVMTransactionExecutionCost::default(),
2412 );
2413 let result =
2414 invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2415 assert_eq!(result, Err((0, InstructionError::AccountBorrowFailed)));
2416
2417 let message = new_sanitized_message(Message::new(
2419 &[Instruction::new_with_bincode(
2420 mock_program_id,
2421 &MockSystemInstruction::MultiBorrowMut,
2422 account_metas.clone(),
2423 )],
2424 Some(transaction_context.get_key_of_account_at_index(0).unwrap()),
2425 ));
2426 let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2427 let environment_config = EnvironmentConfig::new(
2428 Hash::default(),
2429 0,
2430 false,
2431 &MockCallback {},
2432 &feature_set,
2433 &program_runtime_environments,
2434 &sysvar_cache,
2435 );
2436 let mut transaction_context =
2437 TransactionContext::new(accounts.clone(), Rent::default(), 1, 3, 1);
2438 let mut invoke_context = InvokeContext::new(
2439 &mut transaction_context,
2440 &mut program_cache_for_tx_batch,
2441 environment_config,
2442 None,
2443 SVMTransactionExecutionBudget::default(),
2444 SVMTransactionExecutionCost::default(),
2445 );
2446 let result =
2447 invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2448 assert!(result.is_ok());
2449
2450 let message = new_sanitized_message(Message::new(
2452 &[Instruction::new_with_bincode(
2453 mock_program_id,
2454 &MockSystemInstruction::DoWork {
2455 lamports: 10,
2456 data: 42,
2457 },
2458 account_metas,
2459 )],
2460 Some(transaction_context.get_key_of_account_at_index(0).unwrap()),
2461 ));
2462 let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2463 let environment_config = EnvironmentConfig::new(
2464 Hash::default(),
2465 0,
2466 false,
2467 &MockCallback {},
2468 &feature_set,
2469 &program_runtime_environments,
2470 &sysvar_cache,
2471 );
2472 let mut transaction_context = TransactionContext::new(accounts, Rent::default(), 1, 3, 1);
2473 let mut invoke_context = InvokeContext::new(
2474 &mut transaction_context,
2475 &mut program_cache_for_tx_batch,
2476 environment_config,
2477 None,
2478 SVMTransactionExecutionBudget::default(),
2479 SVMTransactionExecutionCost::default(),
2480 );
2481 let result =
2482 invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2483 assert!(result.is_ok());
2484 assert_eq!(
2485 transaction_context
2486 .accounts()
2487 .try_borrow(0)
2488 .unwrap()
2489 .lamports(),
2490 80
2491 );
2492 assert_eq!(
2493 transaction_context
2494 .accounts()
2495 .try_borrow(1)
2496 .unwrap()
2497 .lamports(),
2498 20
2499 );
2500 assert_eq!(
2501 transaction_context.accounts().try_borrow(0).unwrap().data(),
2502 &vec![42]
2503 );
2504 }
2505
2506 fn secp256k1_instruction_for_test() -> Instruction {
2507 let message = b"hello";
2508 let bytes: [u8; 32] = rand::random();
2509 let secret_key = libsecp256k1::SecretKey::parse(&bytes).unwrap();
2510 let pubkey = libsecp256k1::PublicKey::from_secret_key(&secret_key);
2511 let eth_address = eth_address_from_pubkey(&pubkey.serialize()[1..].try_into().unwrap());
2512 let (signature, recovery_id) =
2513 solana_secp256k1_program::sign_message(&secret_key.serialize(), &message[..]).unwrap();
2514 new_secp256k1_instruction_with_signature(
2515 &message[..],
2516 &signature,
2517 recovery_id,
2518 ð_address,
2519 )
2520 }
2521
2522 fn ed25519_instruction_for_test() -> Instruction {
2523 let keypair = Keypair::new();
2524 let signature = keypair.sign_message(b"hello");
2525 let pubkey = keypair.pubkey().to_bytes();
2526 new_ed25519_instruction_with_signature(b"hello", signature.as_array(), &pubkey)
2527 }
2528
2529 fn secp256r1_instruction_for_test() -> Instruction {
2530 let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap();
2531 let secret_key = EcKey::generate(&group).unwrap();
2532 let signature = sign_message(b"hello", &secret_key.private_key_to_der().unwrap()).unwrap();
2533 let mut ctx = openssl::bn::BigNumContext::new().unwrap();
2534 let pubkey = secret_key
2535 .public_key()
2536 .to_bytes(
2537 &group,
2538 openssl::ec::PointConversionForm::COMPRESSED,
2539 &mut ctx,
2540 )
2541 .unwrap();
2542 new_secp256r1_instruction_with_signature(b"hello", &signature, &pubkey.try_into().unwrap())
2543 }
2544
2545 #[test]
2546 fn test_precompile() {
2547 let mock_program_id = Pubkey::new_unique();
2548 declare_process_instruction!(MockBuiltin, 1, |_invoke_context| {
2549 Err(InstructionError::Custom(0xbabb1e))
2550 });
2551
2552 let mut secp256k1_account = AccountSharedData::new(1, 0, &native_loader::id());
2553 secp256k1_account.set_executable(true);
2554 let mut ed25519_account = AccountSharedData::new(1, 0, &native_loader::id());
2555 ed25519_account.set_executable(true);
2556 let mut secp256r1_account = AccountSharedData::new(1, 0, &native_loader::id());
2557 secp256r1_account.set_executable(true);
2558 let mut mock_program_account = AccountSharedData::new(1, 0, &native_loader::id());
2559 mock_program_account.set_executable(true);
2560
2561 let fee_payer = Pubkey::new_unique();
2562 let accounts_map: HashMap<Address, AccountSharedData> = HashMap::from([
2563 (
2564 fee_payer,
2565 AccountSharedData::new(1, 0, &system_program::id()),
2566 ),
2567 (secp256k1_program::id(), secp256k1_account),
2568 (ed25519_program::id(), ed25519_account),
2569 (solana_secp256r1_program::id(), secp256r1_account),
2570 (mock_program_id, mock_program_account),
2571 ]);
2572
2573 let message = new_sanitized_message(Message::new(
2574 &[
2575 secp256k1_instruction_for_test(),
2576 ed25519_instruction_for_test(),
2577 secp256r1_instruction_for_test(),
2578 Instruction::new_with_bytes(mock_program_id, &[], vec![]),
2579 ],
2580 Some(&fee_payer),
2581 ));
2582
2583 let accounts = message
2584 .account_keys()
2585 .iter()
2586 .map(|key| (*key, accounts_map.get(key).unwrap().clone()))
2587 .collect();
2588 let mut transaction_context = TransactionContext::new(accounts, Rent::default(), 1, 4, 4);
2589
2590 let sysvar_cache = SysvarCache::default();
2591 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
2592 program_cache_for_tx_batch.replenish(
2593 mock_program_id,
2594 Arc::new(ProgramCacheEntry::new_builtin(0, MockBuiltin::register)),
2595 );
2596
2597 struct MockCallback {}
2598 impl InvokeContextCallback for MockCallback {
2599 fn is_precompile(&self, program_id: &Pubkey) -> bool {
2600 program_id == &secp256k1_program::id()
2601 || program_id == &ed25519_program::id()
2602 || program_id == &solana_secp256r1_program::id()
2603 }
2604
2605 fn process_precompile(
2606 &self,
2607 program_id: &Pubkey,
2608 _data: &[u8],
2609 _instruction_datas: Vec<&[u8]>,
2610 ) -> std::result::Result<(), PrecompileError> {
2611 if self.is_precompile(program_id) {
2612 Ok(())
2613 } else {
2614 Err(PrecompileError::InvalidPublicKey)
2615 }
2616 }
2617 }
2618 let feature_set = SVMFeatureSet::all_enabled();
2619 let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2620 let environment_config = EnvironmentConfig::new(
2621 Hash::default(),
2622 0,
2623 false,
2624 &MockCallback {},
2625 &feature_set,
2626 &program_runtime_environments,
2627 &sysvar_cache,
2628 );
2629 let mut invoke_context = InvokeContext::new(
2630 &mut transaction_context,
2631 &mut program_cache_for_tx_batch,
2632 environment_config,
2633 None,
2634 SVMTransactionExecutionBudget::default(),
2635 SVMTransactionExecutionCost::default(),
2636 );
2637 let result =
2638 invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2639
2640 assert_eq!(result, Err((3, InstructionError::Custom(0xbabb1e))));
2641 assert_eq!(
2642 transaction_context.number_of_called_instructions_in_trace(),
2643 4
2644 );
2645 }
2646}