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