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::{DropOnBailOut, 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_with_feature_flags(
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 DropOnBailOut::Disabled,
896 );
897 let program_runtime_environments = ProgramRuntimeEnvironments::mock();
898 let environment_config = EnvironmentConfig::new(
899 Hash::default(),
900 0,
901 false,
902 &MockInvokeContextCallback {},
903 $feature_set,
904 &program_runtime_environments,
905 &sysvar_cache,
906 );
907 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
908 let mut $invoke_context = InvokeContext::new(
909 &mut $transaction_context,
910 &mut program_cache_for_tx_batch,
911 environment_config,
912 Some(LogCollector::new_ref()),
913 compute_budget,
914 SVMTransactionExecutionCost::default(),
915 );
916 };
917 (
918 $invoke_context:ident,
919 $transaction_context:ident,
920 $feature_set:ident,
921 $top_level_instructions:literal,
922 $transaction_accounts:expr $(,)?
923 ) => {
924 let transaction_accounts: Vec<(solana_pubkey::Pubkey, solana_account::AccountSharedData)> =
925 $transaction_accounts;
926 $crate::with_mock_invoke_context_with_feature_set!(
927 $invoke_context,
928 $transaction_context,
929 $feature_set,
930 $top_level_instructions,
931 transaction_accounts,
932 &transaction_accounts
933 );
934 };
935 (
936 $invoke_context:ident,
937 $transaction_context:ident,
938 $feature_set:ident,
939 $transaction_accounts:expr $(,)?
940 ) => {
941 $crate::with_mock_invoke_context_with_feature_set!(
942 $invoke_context,
943 $transaction_context,
944 $feature_set,
945 1,
946 $transaction_accounts
947 );
948 };
949}
950
951#[cfg(feature = "dev-context-only-utils")]
952#[macro_export]
953macro_rules! with_mock_invoke_context {
954 (
955 $invoke_context:ident,
956 $transaction_context:ident,
957 $top_level_instructions:literal,
958 $transaction_accounts:expr $(,)?
959 ) => {
960 let feature_set = &solana_svm_feature_set::SVMFeatureSet::default();
961 $crate::with_mock_invoke_context_with_feature_set!(
962 $invoke_context,
963 $transaction_context,
964 feature_set,
965 $top_level_instructions,
966 $transaction_accounts
967 )
968 };
969 (
970 $invoke_context:ident,
971 $transaction_context:ident,
972 $transaction_accounts:expr $(,)?
973 ) => {
974 with_mock_invoke_context!(
975 $invoke_context,
976 $transaction_context,
977 1,
978 $transaction_accounts
979 );
980 };
981}
982
983#[cfg(feature = "dev-context-only-utils")]
984pub fn mock_compile_message<A>(
985 instruction: &Instruction,
986 accounts: &[(Pubkey, A)],
987 program_id: &Pubkey,
988 loader_key: &Pubkey,
989) -> (SanitizedMessage, Vec<(Pubkey, AccountSharedData)>)
990where
991 AccountSharedData: From<A>,
992 A: Clone,
993{
994 let message = Message::new(std::slice::from_ref(instruction), None);
995 let transaction_accounts: Vec<_> = message
996 .account_keys
997 .iter()
998 .map(|key| {
999 let account = accounts
1000 .iter()
1001 .find(|(k, _)| k == key)
1002 .map(|(_, a)| AccountSharedData::from(a.clone()))
1003 .unwrap_or_else(|| {
1004 if key == program_id {
1005 let mut account = AccountSharedData::new(0, 0, loader_key);
1006 account.set_executable(true);
1007 account
1008 } else {
1009 AccountSharedData::default()
1010 }
1011 });
1012 (*key, account)
1013 })
1014 .collect();
1015
1016 let sanitized_message = SanitizedMessage::Legacy(LegacyMessage::new(message, &HashSet::new()));
1017
1018 (sanitized_message, transaction_accounts)
1019}
1020
1021#[cfg(feature = "dev-context-only-utils")]
1022pub fn mock_process_instruction_with_feature_set<
1023 F: FnMut(&mut InvokeContext),
1024 G: FnMut(&mut InvokeContext),
1025>(
1026 program_id: &Pubkey,
1027 instruction_data: &[u8],
1028 mut accounts: Vec<KeyedAccountSharedData>,
1029 instruction_account_metas: Vec<AccountMeta>,
1030 expected_result: Result<(), InstructionError>,
1031 builtin: BuiltinFunctionRegisterer,
1032 mut pre_adjustments: F,
1033 mut post_adjustments: G,
1034 feature_set: &SVMFeatureSet,
1035) -> Vec<AccountSharedData> {
1036 let original_len = accounts.len();
1037 if !accounts
1038 .iter()
1039 .any(|(key, _)| *key == sysvar::epoch_schedule::id())
1040 {
1041 let mut account = AccountSharedData::new(1, solana_epoch_schedule::SIZE, &sysvar::id());
1042 wincode::serialize_into(account.data_as_mut_slice(), &EpochSchedule::default()).unwrap();
1043 accounts.push((sysvar::epoch_schedule::id(), account));
1044 }
1045
1046 let instruction =
1047 Instruction::new_with_bytes(*program_id, instruction_data, instruction_account_metas);
1048 let (sanitized_message, transaction_accounts) =
1049 mock_compile_message(&instruction, &accounts, program_id, &native_loader::id());
1050
1051 let program_owner = accounts
1052 .iter()
1053 .find(|(key, _)| key == program_id)
1054 .map(|(_, acct)| *acct.owner())
1055 .unwrap_or_else(native_loader::id);
1056 let is_builtin = native_loader::check_id(&program_owner);
1057
1058 with_mock_invoke_context_with_feature_set!(
1059 invoke_context,
1060 transaction_context,
1061 feature_set,
1062 1,
1063 transaction_accounts,
1064 &accounts
1065 );
1066
1067 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1068 program_cache_for_tx_batch.replenish(
1069 if is_builtin {
1070 *program_id
1071 } else {
1072 program_owner
1073 },
1074 Arc::new(ProgramCacheEntry::new_builtin(builtin)),
1075 );
1076 program_cache_for_tx_batch.set_slot_for_tests(
1077 invoke_context
1078 .environment_config
1079 .sysvar_cache()
1080 .get_clock()
1081 .map(|clock| clock.slot)
1082 .unwrap_or(1),
1083 );
1084 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1085
1086 pre_adjustments(&mut invoke_context);
1087
1088 invoke_context
1089 .prepare_top_level_instructions(&sanitized_message)
1090 .unwrap();
1091
1092 let result = invoke_context.process_instruction(&mut 0, &mut ExecuteTimings::default());
1093 assert_eq!(result, expected_result);
1094 post_adjustments(&mut invoke_context);
1095
1096 let txn_result_keys: Vec<_> = (0..transaction_context.get_number_of_accounts())
1097 .map(|i| *transaction_context.get_key_of_account_at_index(i).unwrap())
1098 .collect();
1099 let txn_result_accounts = transaction_context.deconstruct_without_keys().unwrap();
1100 let txn_result_map = txn_result_keys
1101 .into_iter()
1102 .zip(txn_result_accounts)
1103 .collect::<HashMap<Pubkey, AccountSharedData>>();
1104
1105 accounts
1106 .into_iter()
1107 .take(original_len)
1108 .map(|(key, original)| txn_result_map.get(&key).cloned().unwrap_or(original))
1109 .collect()
1110}
1111
1112#[cfg(feature = "dev-context-only-utils")]
1113pub fn mock_process_instruction<F: FnMut(&mut InvokeContext), G: FnMut(&mut InvokeContext)>(
1114 program_id: &Pubkey,
1115 instruction_data: &[u8],
1116 accounts: Vec<KeyedAccountSharedData>,
1117 instruction_account_metas: Vec<AccountMeta>,
1118 expected_result: Result<(), InstructionError>,
1119 builtin: BuiltinFunctionRegisterer,
1120 pre_adjustments: F,
1121 post_adjustments: G,
1122) -> Vec<AccountSharedData> {
1123 mock_process_instruction_with_feature_set(
1124 program_id,
1125 instruction_data,
1126 accounts,
1127 instruction_account_metas,
1128 expected_result,
1129 builtin,
1130 pre_adjustments,
1131 post_adjustments,
1132 &SVMFeatureSet::all_enabled(),
1133 )
1134}
1135
1136#[cfg(test)]
1137mod tests {
1138 use {
1139 super::*,
1140 crate::execution_budget::{
1141 DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, MAX_INSTRUCTION_STACK_DEPTH,
1142 MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268,
1143 },
1144 openssl::{
1145 ec::{EcGroup, EcKey},
1146 nid::Nid,
1147 },
1148 serde::{Deserialize, Serialize},
1149 solana_account::{Account, DUMMY_INHERITABLE_ACCOUNT_FIELDS, ReadableAccount},
1150 solana_ed25519_program::new_ed25519_instruction_with_signature,
1151 solana_keypair::{Address, Keypair},
1152 solana_message::AccountKeys,
1153 solana_precompile_error::PrecompileError,
1154 solana_rent::Rent,
1155 solana_sbpf::program::BuiltinFunctionDefinition,
1156 solana_sdk_ids::{ed25519_program, secp256k1_program, system_program},
1157 solana_secp256k1_program::{
1158 eth_address_from_pubkey, new_secp256k1_instruction_with_signature,
1159 },
1160 solana_secp256r1_program::{new_secp256r1_instruction_with_signature, sign_message},
1161 solana_signer::Signer,
1162 solana_svm_feature_set::SVMFeatureSet,
1163 solana_transaction::{Transaction, sanitized::SanitizedTransaction},
1164 solana_transaction_context::{MAX_ACCOUNTS_PER_INSTRUCTION, MAX_ACCOUNTS_PER_TRANSACTION},
1165 test_case::test_case,
1166 };
1167
1168 #[derive(Debug, Serialize, Deserialize)]
1169 enum MockInstruction {
1170 NoopSuccess,
1171 NoopFail,
1172 ModifyOwned,
1173 ModifyNotOwned,
1174 ModifyReadonly,
1175 UnbalancedPush,
1176 UnbalancedPop,
1177 ConsumeComputeUnits {
1178 compute_units_to_consume: u64,
1179 desired_result: Result<(), InstructionError>,
1180 },
1181 Resize {
1182 new_len: u64,
1183 },
1184 }
1185
1186 const MOCK_BUILTIN_COMPUTE_UNIT_COST: u64 = 1;
1187
1188 declare_process_instruction!(
1189 MockBuiltin,
1190 MOCK_BUILTIN_COMPUTE_UNIT_COST,
1191 |invoke_context| {
1192 let transaction_context = &invoke_context.transaction_context;
1193 let instruction_context = transaction_context.get_current_instruction_context()?;
1194 let instruction_data = instruction_context.get_instruction_data();
1195 let program_id = instruction_context.get_program_key()?;
1196 let instruction_accounts = (0..4)
1197 .map(|instruction_account_index| {
1198 InstructionAccount::new(instruction_account_index, false, false)
1199 })
1200 .collect::<Vec<_>>();
1201 assert_eq!(
1202 program_id,
1203 instruction_context
1204 .try_borrow_instruction_account(0)?
1205 .get_owner()
1206 );
1207 assert_ne!(
1208 instruction_context
1209 .try_borrow_instruction_account(1)?
1210 .get_owner(),
1211 instruction_context.get_key_of_instruction_account(0)?
1212 );
1213
1214 if let Ok(instruction) = bincode::deserialize(instruction_data) {
1215 match instruction {
1216 MockInstruction::NoopSuccess => (),
1217 MockInstruction::NoopFail => return Err(InstructionError::GenericError),
1218 MockInstruction::ModifyOwned => instruction_context
1219 .try_borrow_instruction_account(0)?
1220 .set_data_from_slice(&[1])?,
1221 MockInstruction::ModifyNotOwned => instruction_context
1222 .try_borrow_instruction_account(1)?
1223 .set_data_from_slice(&[1])?,
1224 MockInstruction::ModifyReadonly => instruction_context
1225 .try_borrow_instruction_account(2)?
1226 .set_data_from_slice(&[1])?,
1227 MockInstruction::UnbalancedPush => {
1228 instruction_context
1229 .try_borrow_instruction_account(0)?
1230 .checked_add_lamports(1)?;
1231 let program_id = *transaction_context.get_key_of_account_at_index(3)?;
1232 let metas = vec![
1233 AccountMeta::new_readonly(
1234 *transaction_context.get_key_of_account_at_index(0)?,
1235 false,
1236 ),
1237 AccountMeta::new_readonly(
1238 *transaction_context.get_key_of_account_at_index(1)?,
1239 false,
1240 ),
1241 ];
1242 let inner_instruction = Instruction::new_with_bincode(
1243 program_id,
1244 &MockInstruction::NoopSuccess,
1245 metas,
1246 );
1247 invoke_context
1248 .transaction_context
1249 .configure_top_level_instruction_for_tests(
1250 3,
1251 instruction_accounts,
1252 vec![],
1253 )
1254 .unwrap();
1255 let result = invoke_context.push();
1256 assert_eq!(result, Err(InstructionError::UnbalancedInstruction));
1257 result?;
1258 invoke_context
1259 .native_invoke_signed(inner_instruction, &[])
1260 .and(invoke_context.pop())?;
1261 }
1262 MockInstruction::UnbalancedPop => instruction_context
1263 .try_borrow_instruction_account(0)?
1264 .checked_add_lamports(1)?,
1265 MockInstruction::ConsumeComputeUnits {
1266 compute_units_to_consume,
1267 desired_result,
1268 } => {
1269 invoke_context
1270 .compute_meter
1271 .consume_checked(compute_units_to_consume)
1272 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
1273 return desired_result;
1274 }
1275 MockInstruction::Resize { new_len } => instruction_context
1276 .try_borrow_instruction_account(0)?
1277 .set_data_from_slice(&vec![0; new_len as usize])?,
1278 }
1279 } else {
1280 return Err(InstructionError::InvalidInstructionData);
1281 }
1282 Ok(())
1283 }
1284 );
1285
1286 #[test_case(false; "SIMD-0268 disabled")]
1287 #[test_case(true; "SIMD-0268 enabled")]
1288 fn test_instruction_stack_height(simd_0268_active: bool) {
1289 let feature_set = &SVMFeatureSet {
1290 raise_cpi_nesting_limit_to_8: simd_0268_active,
1291 ..SVMFeatureSet::all_enabled()
1292 };
1293 let max_depth = SVMTransactionExecutionBudget::new_with_defaults(simd_0268_active)
1294 .max_instruction_stack_depth;
1295 assert_eq!(
1296 max_depth,
1297 if simd_0268_active {
1298 MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268
1299 } else {
1300 MAX_INSTRUCTION_STACK_DEPTH
1301 },
1302 );
1303
1304 let mut invoke_stack = vec![];
1307 let mut transaction_accounts = vec![];
1308 let mut instruction_accounts = vec![];
1309 for index in 0..max_depth.saturating_add(1) {
1310 let program_id = solana_pubkey::new_rand();
1311 invoke_stack.push(program_id);
1312 transaction_accounts.push((
1313 solana_pubkey::new_rand(),
1314 AccountSharedData::new(1, 1, &program_id),
1315 ));
1316 instruction_accounts.push(InstructionAccount::new(
1317 index as IndexOfAccount,
1318 false,
1319 true,
1320 ));
1321 }
1322
1323 let first_program_account = transaction_accounts.len();
1326 for (index, program_id) in invoke_stack.iter().enumerate() {
1327 transaction_accounts.push((
1328 *program_id,
1329 AccountSharedData::new(1, 1, &solana_pubkey::Pubkey::default()),
1330 ));
1331 instruction_accounts.push(InstructionAccount::new(
1332 index as IndexOfAccount,
1333 false,
1334 false,
1335 ));
1336 }
1337 with_mock_invoke_context_with_feature_set!(
1338 invoke_context,
1339 transaction_context,
1340 feature_set,
1341 transaction_accounts,
1342 );
1343
1344 for depth in 0..max_depth {
1346 assert_eq!(invoke_context.get_stack_height(), depth);
1347 invoke_context
1348 .transaction_context
1349 .configure_top_level_instruction_for_tests(
1350 (first_program_account.saturating_add(depth)) as IndexOfAccount,
1351 instruction_accounts.clone(),
1352 vec![],
1353 )
1354 .unwrap();
1355 assert!(
1356 invoke_context.push().is_ok(),
1357 "push at depth {depth} should succeed (max_depth={max_depth})",
1358 );
1359 }
1360
1361 assert_eq!(invoke_context.get_stack_height(), max_depth);
1363 invoke_context
1364 .transaction_context
1365 .configure_top_level_instruction_for_tests(
1366 (first_program_account.saturating_add(max_depth)) as IndexOfAccount,
1367 instruction_accounts.clone(),
1368 vec![],
1369 )
1370 .unwrap();
1371 assert_eq!(invoke_context.push(), Err(InstructionError::CallDepth),);
1372
1373 assert_eq!(invoke_context.get_stack_height(), max_depth);
1375 }
1376
1377 #[test]
1378 fn test_max_instruction_trace_length_top_level() {
1379 const MAX_INSTRUCTIONS: usize = 8;
1380 let mut transaction_context = TransactionContext::new(
1381 vec![(
1382 Pubkey::new_unique(),
1383 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1384 )],
1385 Rent::default(),
1386 1,
1387 MAX_INSTRUCTIONS,
1388 MAX_INSTRUCTIONS,
1389 );
1390 for _ in 0..MAX_INSTRUCTIONS {
1391 transaction_context.push().unwrap();
1392 transaction_context
1393 .configure_top_level_instruction_for_tests(
1394 0,
1395 vec![InstructionAccount::new(0, false, false)],
1396 vec![],
1397 )
1398 .unwrap();
1399 transaction_context.pop().unwrap();
1400 }
1401 assert_eq!(
1402 transaction_context.push(),
1403 Err(InstructionError::MaxInstructionTraceLengthExceeded)
1404 );
1405 }
1406
1407 #[test]
1408 fn test_max_instruction_trace_length_cpi() {
1409 const MAX_INSTRUCTIONS: usize = 8;
1411 let mut transaction_context = TransactionContext::new(
1412 vec![(
1413 Pubkey::new_unique(),
1414 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1415 )],
1416 Rent::default(),
1417 256,
1418 MAX_INSTRUCTIONS,
1419 2,
1420 );
1421 let num_transaction_accounts = usize::from(transaction_context.get_number_of_accounts());
1422
1423 transaction_context
1424 .configure_instruction_at_index(
1425 0,
1426 0,
1427 vec![InstructionAccount::new(0, false, false)],
1428 vec![u8::MAX; num_transaction_accounts],
1429 Cow::Owned(Vec::new()),
1430 None,
1431 )
1432 .unwrap();
1433
1434 transaction_context
1435 .configure_instruction_at_index(
1436 1,
1437 0,
1438 vec![InstructionAccount::new(0, false, false)],
1439 vec![u8::MAX; num_transaction_accounts],
1440 Cow::Owned(Vec::new()),
1441 None,
1442 )
1443 .unwrap();
1444
1445 for _ in 0..MAX_INSTRUCTIONS {
1446 transaction_context.push().unwrap();
1447 transaction_context
1448 .configure_next_cpi_for_tests(
1449 0,
1450 vec![InstructionAccount::new(0, false, false)],
1451 Vec::new(),
1452 )
1453 .unwrap();
1454 }
1455
1456 assert_eq!(
1457 transaction_context.push(),
1458 Err(InstructionError::MaxInstructionTraceLengthExceeded)
1459 );
1460 }
1461
1462 #[test_case(MockInstruction::NoopSuccess, Ok(()); "NoopSuccess")]
1463 #[test_case(MockInstruction::NoopFail, Err(InstructionError::GenericError); "NoopFail")]
1464 #[test_case(MockInstruction::ModifyOwned, Ok(()); "ModifyOwned")]
1465 #[test_case(MockInstruction::ModifyNotOwned, Err(InstructionError::ExternalAccountDataModified); "ModifyNotOwned")]
1466 #[test_case(MockInstruction::ModifyReadonly, Err(InstructionError::ReadonlyDataModified); "ModifyReadonly")]
1467 #[test_case(MockInstruction::UnbalancedPush, Err(InstructionError::UnbalancedInstruction); "UnbalancedPush")]
1468 #[test_case(MockInstruction::UnbalancedPop, Err(InstructionError::UnbalancedInstruction); "UnbalancedPop")]
1469 fn test_process_instruction_account_modifications(
1470 instruction: MockInstruction,
1471 expected_result: Result<(), InstructionError>,
1472 ) {
1473 let callee_program_id = solana_pubkey::new_rand();
1474 let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1475 let not_owned_account = AccountSharedData::new(84, 1, &solana_pubkey::new_rand());
1476 let readonly_account = AccountSharedData::new(168, 1, &solana_pubkey::new_rand());
1477 let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1478 let mut program_account = AccountSharedData::new(1, 1, &native_loader::id());
1479 program_account.set_executable(true);
1480 let transaction_accounts = vec![
1481 (solana_pubkey::new_rand(), owned_account),
1482 (solana_pubkey::new_rand(), not_owned_account),
1483 (solana_pubkey::new_rand(), readonly_account),
1484 (callee_program_id, program_account),
1485 (solana_pubkey::new_rand(), loader_account),
1486 ];
1487 let metas = vec![
1488 AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1489 AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1490 AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1491 ];
1492 let instruction_accounts = (0..4)
1493 .map(|instruction_account_index| {
1494 InstructionAccount::new(
1495 instruction_account_index,
1496 false,
1497 instruction_account_index < 2,
1498 )
1499 })
1500 .collect::<Vec<_>>();
1501 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1502 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1503 program_cache_for_tx_batch.replenish(
1504 callee_program_id,
1505 Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
1506 );
1507 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1508
1509 invoke_context
1511 .transaction_context
1512 .configure_top_level_instruction_for_tests(4, instruction_accounts, vec![])
1513 .unwrap();
1514 invoke_context.push().unwrap();
1515 let inner_instruction =
1516 Instruction::new_with_bincode(callee_program_id, &instruction, metas);
1517 let result = invoke_context
1518 .native_invoke_signed(inner_instruction, &[])
1519 .and(invoke_context.pop());
1520 assert_eq!(result, expected_result);
1521 }
1522
1523 #[test_case(Ok(()); "Ok")]
1524 #[test_case(Err(InstructionError::GenericError); "GenericError")]
1525 fn test_process_instruction_compute_unit_consumption(
1526 expected_result: Result<(), InstructionError>,
1527 ) {
1528 let callee_program_id = solana_pubkey::new_rand();
1529 let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1530 let not_owned_account = AccountSharedData::new(84, 1, &solana_pubkey::new_rand());
1531 let readonly_account = AccountSharedData::new(168, 1, &solana_pubkey::new_rand());
1532 let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1533 let mut program_account = AccountSharedData::new(1, 1, &native_loader::id());
1534 program_account.set_executable(true);
1535 let transaction_accounts = vec![
1536 (solana_pubkey::new_rand(), owned_account),
1537 (solana_pubkey::new_rand(), not_owned_account),
1538 (solana_pubkey::new_rand(), readonly_account),
1539 (callee_program_id, program_account),
1540 (solana_pubkey::new_rand(), loader_account),
1541 ];
1542 let metas = vec![
1543 AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1544 AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1545 AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1546 ];
1547 let instruction_accounts = (0..4)
1548 .map(|instruction_account_index| {
1549 InstructionAccount::new(
1550 instruction_account_index,
1551 false,
1552 instruction_account_index < 2,
1553 )
1554 })
1555 .collect::<Vec<_>>();
1556 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1557 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1558 program_cache_for_tx_batch.replenish(
1559 callee_program_id,
1560 Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
1561 );
1562 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1563
1564 let compute_units_to_consume = 10;
1566 invoke_context
1567 .transaction_context
1568 .configure_top_level_instruction_for_tests(4, instruction_accounts, vec![])
1569 .unwrap();
1570 invoke_context.push().unwrap();
1571 let inner_instruction = Instruction::new_with_bincode(
1572 callee_program_id,
1573 &MockInstruction::ConsumeComputeUnits {
1574 compute_units_to_consume,
1575 desired_result: expected_result.clone(),
1576 },
1577 metas,
1578 );
1579 invoke_context
1580 .prepare_next_cpi_instruction(inner_instruction, &[])
1581 .unwrap();
1582
1583 let mut compute_units_consumed = 0;
1584 let result = invoke_context
1585 .process_instruction(&mut compute_units_consumed, &mut ExecuteTimings::default());
1586
1587 assert!(compute_units_consumed > 0);
1591 assert_eq!(
1592 compute_units_consumed,
1593 compute_units_to_consume.saturating_add(MOCK_BUILTIN_COMPUTE_UNIT_COST),
1594 );
1595 assert_eq!(result, expected_result);
1596
1597 invoke_context.pop().unwrap();
1598 }
1599
1600 #[test]
1601 fn test_invoke_context_compute_budget() {
1602 let transaction_accounts = vec![(solana_pubkey::new_rand(), AccountSharedData::default())];
1603 let execution_budget = SVMTransactionExecutionBudget {
1604 compute_unit_limit: u64::from(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT),
1605 ..SVMTransactionExecutionBudget::default()
1606 };
1607
1608 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1609 invoke_context.compute_budget = execution_budget;
1610
1611 invoke_context
1612 .transaction_context
1613 .configure_top_level_instruction_for_tests(0, vec![], vec![])
1614 .unwrap();
1615 invoke_context.push().unwrap();
1616 assert_eq!(*invoke_context.get_compute_budget(), execution_budget);
1617 invoke_context.pop().unwrap();
1618 }
1619
1620 #[test_case(0; "Resize the account to *the same size*, so not consuming any additional size")]
1621 #[test_case(1; "Resize the account larger")]
1622 #[test_case(-1; "Resize the account smaller")]
1623 fn test_process_instruction_accounts_resize_delta(resize_delta: i64) {
1624 let program_key = Pubkey::new_unique();
1625 let user_account_data_len = 123u64;
1626 let user_account =
1627 AccountSharedData::new(100, user_account_data_len as usize, &program_key);
1628 let dummy_account = AccountSharedData::new(10, 0, &program_key);
1629 let mut program_account = AccountSharedData::new(500, 500, &native_loader::id());
1630 program_account.set_executable(true);
1631 let transaction_accounts = vec![
1632 (Pubkey::new_unique(), user_account),
1633 (Pubkey::new_unique(), dummy_account),
1634 (program_key, program_account),
1635 ];
1636 let instruction_accounts = vec![
1637 InstructionAccount::new(0, false, true),
1638 InstructionAccount::new(1, false, false),
1639 ];
1640 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1641 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1642 program_cache_for_tx_batch.replenish(
1643 program_key,
1644 Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
1645 );
1646 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1647
1648 let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
1649 let instruction_data = bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1650
1651 invoke_context
1652 .transaction_context
1653 .configure_top_level_instruction_for_tests(2, instruction_accounts, instruction_data)
1654 .unwrap();
1655 let result = invoke_context.process_instruction(&mut 0, &mut ExecuteTimings::default());
1656
1657 assert!(result.is_ok());
1658 assert_eq!(
1659 invoke_context.transaction_context.accounts().resize_delta(),
1660 resize_delta
1661 );
1662 }
1663
1664 #[test]
1665 fn test_prepare_instruction_maximum_accounts() {
1666 const MAX_ACCOUNTS_REFERENCED: usize = u16::MAX as usize;
1667 let mut transaction_accounts: Vec<KeyedAccountSharedData> =
1668 Vec::with_capacity(MAX_ACCOUNTS_PER_TRANSACTION);
1669 let mut account_metas: Vec<AccountMeta> = Vec::with_capacity(MAX_ACCOUNTS_REFERENCED);
1670
1671 let fee_payer = Keypair::new();
1673 transaction_accounts.push((
1674 fee_payer.pubkey(),
1675 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1676 ));
1677 account_metas.push(AccountMeta::new(fee_payer.pubkey(), true));
1678
1679 let program_id = Pubkey::new_unique();
1680 let mut program_account = AccountSharedData::new(1, 1, &Pubkey::new_unique());
1681 program_account.set_executable(true);
1682 transaction_accounts.push((program_id, program_account));
1683 account_metas.push(AccountMeta::new_readonly(program_id, false));
1684
1685 for i in 2..MAX_ACCOUNTS_REFERENCED {
1686 if i < MAX_ACCOUNTS_PER_TRANSACTION {
1688 let key = Pubkey::new_unique();
1689 transaction_accounts
1690 .push((key, AccountSharedData::new(1, 1, &Pubkey::new_unique())));
1691 account_metas.push(AccountMeta::new_readonly(key, false));
1692 } else {
1693 let repeated_key = transaction_accounts
1694 .get(i % MAX_ACCOUNTS_PER_TRANSACTION)
1695 .unwrap()
1696 .0;
1697 account_metas.push(AccountMeta::new_readonly(repeated_key, false));
1698 }
1699 }
1700
1701 with_mock_invoke_context!(invoke_context, transaction_context, 2, transaction_accounts);
1702
1703 let instruction_1 = Instruction::new_with_bytes(program_id, &[20], account_metas.clone());
1704
1705 let instruction_2 = Instruction::new_with_bytes(
1706 program_id,
1707 &[20],
1708 account_metas.iter().rev().cloned().collect(),
1709 );
1710
1711 let transaction = Transaction::new_with_payer(
1712 &[instruction_1.clone(), instruction_2.clone()],
1713 Some(&fee_payer.pubkey()),
1714 );
1715
1716 let sanitized =
1717 SanitizedTransaction::try_from_legacy_transaction(transaction, &HashSet::new())
1718 .unwrap();
1719
1720 fn test_case_1(invoke_context: &InvokeContext) {
1721 let instruction_context = invoke_context
1722 .transaction_context
1723 .get_next_instruction_context()
1724 .unwrap();
1725 for index_in_instruction in 0..MAX_ACCOUNTS_REFERENCED as IndexOfAccount {
1726 let index_in_transaction = instruction_context
1727 .get_index_of_instruction_account_in_transaction(index_in_instruction)
1728 .unwrap();
1729 let other_ix_index = instruction_context
1730 .get_index_of_account_in_instruction(index_in_transaction)
1731 .unwrap();
1732 if (index_in_instruction as usize) < MAX_ACCOUNTS_PER_TRANSACTION {
1733 assert_eq!(index_in_instruction, index_in_transaction);
1734 assert_eq!(index_in_instruction, other_ix_index);
1735 } else {
1736 assert_eq!(
1737 index_in_instruction as usize % MAX_ACCOUNTS_PER_TRANSACTION,
1738 index_in_transaction as usize
1739 );
1740 assert_eq!(
1741 index_in_instruction as usize % MAX_ACCOUNTS_PER_TRANSACTION,
1742 other_ix_index as usize
1743 );
1744 }
1745 }
1746 }
1747
1748 fn test_case_2(invoke_context: &InvokeContext) {
1749 let instruction_context = invoke_context
1750 .transaction_context
1751 .get_next_instruction_context()
1752 .unwrap();
1753 for index_in_instruction in 0..MAX_ACCOUNTS_REFERENCED as IndexOfAccount {
1754 let index_in_transaction = instruction_context
1755 .get_index_of_instruction_account_in_transaction(index_in_instruction)
1756 .unwrap();
1757 let other_ix_index = instruction_context
1758 .get_index_of_account_in_instruction(index_in_transaction)
1759 .unwrap();
1760 assert_eq!(
1761 index_in_transaction,
1762 (MAX_ACCOUNTS_REFERENCED as u16)
1763 .saturating_sub(index_in_instruction)
1764 .saturating_sub(1)
1765 .overflowing_rem(MAX_ACCOUNTS_PER_TRANSACTION as u16)
1766 .0
1767 );
1768 if (index_in_instruction as usize) < MAX_ACCOUNTS_PER_TRANSACTION {
1769 assert_eq!(index_in_instruction, other_ix_index);
1770 } else {
1771 assert_eq!(
1772 index_in_instruction as usize % MAX_ACCOUNTS_PER_TRANSACTION,
1773 other_ix_index as usize
1774 );
1775 }
1776 }
1777 }
1778
1779 invoke_context
1780 .prepare_top_level_instructions(&sanitized)
1781 .unwrap();
1782
1783 test_case_1(&invoke_context);
1784
1785 invoke_context.transaction_context.push().unwrap();
1786 invoke_context.transaction_context.pop().unwrap();
1787
1788 test_case_2(&invoke_context);
1789
1790 invoke_context.transaction_context.push().unwrap();
1791 invoke_context
1792 .prepare_next_cpi_instruction(instruction_1, &[fee_payer.pubkey()])
1793 .unwrap();
1794 test_case_1(&invoke_context);
1795
1796 invoke_context.transaction_context.push().unwrap();
1797 invoke_context
1798 .prepare_next_cpi_instruction(instruction_2, &[fee_payer.pubkey()])
1799 .unwrap();
1800 test_case_2(&invoke_context);
1801 }
1802
1803 #[test]
1804 fn test_duplicated_accounts() {
1805 let mut transaction_accounts: Vec<KeyedAccountSharedData> =
1806 Vec::with_capacity(MAX_ACCOUNTS_PER_TRANSACTION);
1807 let mut account_metas: Vec<AccountMeta> =
1808 Vec::with_capacity(MAX_ACCOUNTS_PER_INSTRUCTION.saturating_sub(1));
1809
1810 let fee_payer = Keypair::new();
1812 transaction_accounts.push((
1813 fee_payer.pubkey(),
1814 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1815 ));
1816 account_metas.push(AccountMeta::new(fee_payer.pubkey(), true));
1817
1818 let program_id = Pubkey::new_unique();
1819 let mut program_account = AccountSharedData::new(1, 1, &Pubkey::new_unique());
1820 program_account.set_executable(true);
1821 transaction_accounts.push((program_id, program_account));
1822 account_metas.push(AccountMeta::new_readonly(program_id, false));
1823
1824 for i in 2..account_metas.capacity() {
1825 if i % 2 == 0 {
1826 let key = Pubkey::new_unique();
1827 transaction_accounts
1828 .push((key, AccountSharedData::new(1, 1, &Pubkey::new_unique())));
1829 account_metas.push(AccountMeta::new_readonly(key, false));
1830 } else {
1831 let last_key = transaction_accounts.last().unwrap().0;
1832 account_metas.push(AccountMeta::new_readonly(last_key, false));
1833 }
1834 }
1835
1836 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1837
1838 let instruction = Instruction::new_with_bytes(program_id, &[20], account_metas.clone());
1839
1840 let transaction = Transaction::new_with_payer(&[instruction], Some(&fee_payer.pubkey()));
1841
1842 let sanitized =
1843 SanitizedTransaction::try_from_legacy_transaction(transaction, &HashSet::new())
1844 .unwrap();
1845
1846 invoke_context
1847 .prepare_top_level_instructions(&sanitized)
1848 .unwrap();
1849
1850 {
1851 let instruction_context = invoke_context
1852 .transaction_context
1853 .get_next_instruction_context()
1854 .unwrap();
1855 for index_in_instruction in 2..account_metas.len() as IndexOfAccount {
1856 let is_duplicate = instruction_context
1857 .is_instruction_account_duplicate(index_in_instruction)
1858 .unwrap();
1859 if index_in_instruction % 2 == 0 {
1860 assert!(is_duplicate.is_none());
1861 } else {
1862 assert_eq!(is_duplicate, Some(index_in_instruction.saturating_sub(1)));
1863 }
1864 }
1865 }
1866
1867 invoke_context.transaction_context.push().unwrap();
1868
1869 let instruction = Instruction::new_with_bytes(
1870 program_id,
1871 &[20],
1872 account_metas.iter().cloned().rev().collect(),
1873 );
1874
1875 invoke_context
1876 .prepare_next_cpi_instruction(instruction, &[fee_payer.pubkey()])
1877 .unwrap();
1878 let instruction_context = invoke_context
1879 .transaction_context
1880 .get_next_instruction_context()
1881 .unwrap();
1882 for index_in_instruction in 2..account_metas.len().saturating_sub(1) as u16 {
1883 let is_duplicate = instruction_context
1884 .is_instruction_account_duplicate(index_in_instruction)
1885 .unwrap();
1886 if index_in_instruction % 2 == 0 {
1887 assert!(is_duplicate.is_none());
1888 } else {
1889 assert_eq!(is_duplicate, Some(index_in_instruction.saturating_sub(1)));
1890 }
1891 }
1892 }
1893
1894 const TEST_CALLER_PROGRAM_ID: Pubkey = Pubkey::new_from_array([1u8; 32]);
1896 const TEST_CALLEE_PROGRAM_ID: Pubkey = Pubkey::new_from_array([2u8; 32]);
1897 const TEST_WRONG_PROGRAM_ID: Pubkey = Pubkey::new_from_array([3u8; 32]);
1898 const TEST_MOCK_EXTRA_KEY: Pubkey = Pubkey::new_from_array([4u8; 32]);
1899 const TEST_ACCOUNT_KEY: Pubkey = Pubkey::new_from_array([5u8; 32]);
1900
1901 fn run_native_invoke_signed_test(
1910 target_key: Pubkey,
1911 target_is_signer: bool,
1912 inner_instruction: Instruction,
1913 signer_seeds: &[&[&[u8]]],
1914 ) -> Result<(), InstructionError> {
1915 let target_account = AccountSharedData::new(100, 0, &TEST_CALLEE_PROGRAM_ID);
1916 let mock_extra_account = AccountSharedData::new(0, 1, &system_program::id());
1917 let mut caller_program_account = AccountSharedData::new(1, 1, &native_loader::id());
1918 caller_program_account.set_executable(true);
1919 let mut callee_program_account = AccountSharedData::new(1, 1, &native_loader::id());
1920 callee_program_account.set_executable(true);
1921 let transaction_accounts = vec![
1922 (target_key, target_account),
1923 (TEST_CALLER_PROGRAM_ID, caller_program_account),
1924 (TEST_MOCK_EXTRA_KEY, mock_extra_account),
1925 (TEST_CALLEE_PROGRAM_ID, callee_program_account),
1926 ];
1927
1928 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1929 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1930 program_cache_for_tx_batch.replenish(
1931 TEST_CALLEE_PROGRAM_ID,
1932 Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
1933 );
1934 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1935
1936 let instruction_accounts = (0..4)
1937 .map(|i| InstructionAccount::new(i, i == 0 && target_is_signer, i < 2))
1938 .collect::<Vec<_>>();
1939 invoke_context
1940 .transaction_context
1941 .configure_top_level_instruction_for_tests(1, instruction_accounts, vec![])
1942 .unwrap();
1943 invoke_context.push().unwrap();
1944
1945 let result = invoke_context.native_invoke_signed(inner_instruction, signer_seeds);
1946 invoke_context.pop().unwrap();
1947 result
1948 }
1949
1950 #[test]
1952 fn test_native_invoke_signed_with_valid_pda_signer() {
1953 let (pda_key, bump_seed) =
1954 Pubkey::find_program_address(&[b"seed"], &TEST_CALLER_PROGRAM_ID);
1955 let instruction = Instruction::new_with_bincode(
1956 TEST_CALLEE_PROGRAM_ID,
1957 &MockInstruction::NoopSuccess,
1958 vec![
1959 AccountMeta::new(pda_key, true),
1960 AccountMeta::new_readonly(TEST_MOCK_EXTRA_KEY, false),
1961 ],
1962 );
1963 let result =
1964 run_native_invoke_signed_test(pda_key, false, instruction, &[&[b"seed", &[bump_seed]]]);
1965 assert!(
1966 result.is_ok(),
1967 "valid PDA signer should succeed: {result:?}"
1968 );
1969 }
1970
1971 #[test]
1975 fn test_native_invoke_signed_with_invalid_seeds() {
1976 let instruction = Instruction::new_with_bincode(
1977 TEST_CALLEE_PROGRAM_ID,
1978 &MockInstruction::NoopSuccess,
1979 vec![AccountMeta::new(TEST_ACCOUNT_KEY, true)],
1980 );
1981 let oversized_seed = [0u8; 33];
1982 let result = run_native_invoke_signed_test(
1983 TEST_ACCOUNT_KEY,
1984 false,
1985 instruction,
1986 &[&[&oversized_seed]],
1987 );
1988 assert_eq!(result, Err(InstructionError::Custom(0)));
1989 }
1990
1991 #[test]
1994 fn test_native_invoke_signed_pda_privilege_escalation_without_seeds() {
1995 let (pda_key, _bump_seed) =
1996 Pubkey::find_program_address(&[b"seed"], &TEST_CALLER_PROGRAM_ID);
1997 let instruction = Instruction::new_with_bincode(
1998 TEST_CALLEE_PROGRAM_ID,
1999 &MockInstruction::NoopSuccess,
2000 vec![AccountMeta::new(pda_key, true)],
2001 );
2002 let result = run_native_invoke_signed_test(pda_key, false, instruction, &[]);
2003 assert_eq!(result, Err(InstructionError::PrivilegeEscalation));
2004 }
2005
2006 #[test]
2009 fn test_native_invoke_signed_uses_caller_program_id_for_pda() {
2010 let (pda_key, bump_seed) = Pubkey::find_program_address(&[b"seed"], &TEST_WRONG_PROGRAM_ID);
2011 let instruction = Instruction::new_with_bincode(
2012 TEST_CALLEE_PROGRAM_ID,
2013 &MockInstruction::NoopSuccess,
2014 vec![AccountMeta::new(pda_key, true)],
2015 );
2016 let result =
2017 run_native_invoke_signed_test(pda_key, false, instruction, &[&[b"seed", &[bump_seed]]]);
2018 assert_eq!(result, Err(InstructionError::PrivilegeEscalation));
2019 }
2020
2021 #[test]
2023 fn test_native_invoke_signed_top_level_signer_needs_no_seeds() {
2024 let (pda_key, _bump_seed) =
2025 Pubkey::find_program_address(&[b"seed"], &TEST_CALLER_PROGRAM_ID);
2026 let instruction = Instruction::new_with_bincode(
2027 TEST_CALLEE_PROGRAM_ID,
2028 &MockInstruction::NoopSuccess,
2029 vec![
2030 AccountMeta::new(pda_key, true),
2031 AccountMeta::new_readonly(TEST_MOCK_EXTRA_KEY, false),
2032 ],
2033 );
2034 let result = run_native_invoke_signed_test(pda_key, true, instruction, &[]);
2035 assert!(
2036 result.is_ok(),
2037 "top-level signer should not need seeds: {result:?}"
2038 );
2039 }
2040
2041 #[test]
2042 fn test_compile_message() {
2043 let program_id = Pubkey::new_from_array([1u8; 32]);
2044 let writable = Pubkey::new_from_array([2u8; 32]);
2045 let loader_key = Pubkey::new_from_array([3u8; 32]);
2046
2047 let instruction = Instruction {
2048 program_id,
2049 accounts: vec![AccountMeta::new(writable, false)],
2050 data: vec![1, 2, 3],
2051 };
2052
2053 let accounts = vec![(
2054 writable,
2055 Account {
2056 lamports: 100,
2057 ..Account::default()
2058 },
2059 )];
2060
2061 let (message, tx_accounts) =
2062 mock_compile_message(&instruction, &accounts, &program_id, &loader_key);
2063
2064 assert_eq!(message.instructions().len(), 1);
2065 assert_eq!(tx_accounts.len(), 2);
2066 assert_eq!(tx_accounts.first().unwrap().0, writable);
2067 assert_eq!(tx_accounts.get(1).unwrap().0, program_id);
2068
2069 assert!(!message.is_signer(0));
2071 }
2072
2073 struct MockCallback {}
2074 impl InvokeContextCallback for MockCallback {}
2075
2076 fn create_loadable_account_for_test(name: &str) -> AccountSharedData {
2077 let (lamports, rent_epoch) = DUMMY_INHERITABLE_ACCOUNT_FIELDS;
2078 AccountSharedData::from(Account {
2079 lamports,
2080 owner: native_loader::id(),
2081 data: name.as_bytes().to_vec(),
2082 executable: true,
2083 rent_epoch,
2084 })
2085 }
2086
2087 fn new_sanitized_message(message: Message) -> SanitizedMessage {
2088 SanitizedMessage::try_from_legacy_message(message, &HashSet::new()).unwrap()
2089 }
2090
2091 #[test]
2092 fn test_process_message_readonly_handling() {
2093 #[derive(serde::Serialize, serde::Deserialize)]
2094 enum MockSystemInstruction {
2095 Correct,
2096 TransferLamports { lamports: u64 },
2097 ChangeData { data: u8 },
2098 }
2099
2100 declare_process_instruction!(MockBuiltin, 1, |invoke_context| {
2101 let transaction_context = &invoke_context.transaction_context;
2102 let instruction_context = transaction_context.get_current_instruction_context()?;
2103 let instruction_data = instruction_context.get_instruction_data();
2104 if let Ok(instruction) = bincode::deserialize(instruction_data) {
2105 match instruction {
2106 MockSystemInstruction::Correct => Ok(()),
2107 MockSystemInstruction::TransferLamports { lamports } => {
2108 instruction_context
2109 .try_borrow_instruction_account(0)?
2110 .checked_sub_lamports(lamports)?;
2111 instruction_context
2112 .try_borrow_instruction_account(1)?
2113 .checked_add_lamports(lamports)?;
2114 Ok(())
2115 }
2116 MockSystemInstruction::ChangeData { data } => {
2117 instruction_context
2118 .try_borrow_instruction_account(1)?
2119 .set_data_from_slice(&[data])?;
2120 Ok(())
2121 }
2122 }
2123 } else {
2124 Err(InstructionError::InvalidInstructionData)
2125 }
2126 });
2127
2128 let writable_pubkey = Pubkey::new_unique();
2129 let readonly_pubkey = Pubkey::new_unique();
2130 let mock_system_program_id = Pubkey::new_unique();
2131
2132 let accounts = vec![
2133 (
2134 writable_pubkey,
2135 AccountSharedData::new(100, 1, &mock_system_program_id),
2136 ),
2137 (
2138 readonly_pubkey,
2139 AccountSharedData::new(0, 1, &mock_system_program_id),
2140 ),
2141 (
2142 mock_system_program_id,
2143 create_loadable_account_for_test("mock_system_program"),
2144 ),
2145 ];
2146 let mut transaction_context =
2147 TransactionContext::new(accounts.clone(), Rent::default(), 1, 3, 1);
2148 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
2149 program_cache_for_tx_batch.replenish(
2150 mock_system_program_id,
2151 Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
2152 );
2153 let account_keys = (0..transaction_context.get_number_of_accounts())
2154 .map(|index| {
2155 *transaction_context
2156 .get_key_of_account_at_index(index)
2157 .unwrap()
2158 })
2159 .collect::<Vec<_>>();
2160 let account_metas = vec![
2161 AccountMeta::new(writable_pubkey, true),
2162 AccountMeta::new_readonly(readonly_pubkey, false),
2163 ];
2164
2165 let message = new_sanitized_message(Message::new_with_compiled_instructions(
2166 1,
2167 0,
2168 2,
2169 account_keys.clone(),
2170 Hash::default(),
2171 AccountKeys::new(&account_keys, None).compile_instructions(&[
2172 Instruction::new_with_bincode(
2173 mock_system_program_id,
2174 &MockSystemInstruction::Correct,
2175 account_metas.clone(),
2176 ),
2177 ]),
2178 ));
2179 let sysvar_cache = SysvarCache::default();
2180 let feature_set = SVMFeatureSet::all_enabled();
2181 let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2182 let environment_config = EnvironmentConfig::new(
2183 Hash::default(),
2184 0,
2185 false,
2186 &MockCallback {},
2187 &feature_set,
2188 &program_runtime_environments,
2189 &sysvar_cache,
2190 );
2191 let mut invoke_context = InvokeContext::new(
2192 &mut transaction_context,
2193 &mut program_cache_for_tx_batch,
2194 environment_config,
2195 None,
2196 SVMTransactionExecutionBudget::default(),
2197 SVMTransactionExecutionCost::default(),
2198 );
2199 let result =
2200 invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2201 assert!(result.is_ok());
2202 assert_eq!(
2203 transaction_context
2204 .accounts()
2205 .try_borrow(0)
2206 .unwrap()
2207 .lamports(),
2208 100
2209 );
2210 assert_eq!(
2211 transaction_context
2212 .accounts()
2213 .try_borrow(1)
2214 .unwrap()
2215 .lamports(),
2216 0
2217 );
2218
2219 let message = new_sanitized_message(Message::new_with_compiled_instructions(
2220 1,
2221 0,
2222 2,
2223 account_keys.clone(),
2224 Hash::default(),
2225 AccountKeys::new(&account_keys, None).compile_instructions(&[
2226 Instruction::new_with_bincode(
2227 mock_system_program_id,
2228 &MockSystemInstruction::TransferLamports { lamports: 50 },
2229 account_metas.clone(),
2230 ),
2231 ]),
2232 ));
2233 let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2234 let environment_config = EnvironmentConfig::new(
2235 Hash::default(),
2236 0,
2237 false,
2238 &MockCallback {},
2239 &feature_set,
2240 &program_runtime_environments,
2241 &sysvar_cache,
2242 );
2243 let mut transaction_context =
2244 TransactionContext::new(accounts.clone(), Rent::default(), 1, 3, 1);
2245 let mut invoke_context = InvokeContext::new(
2246 &mut transaction_context,
2247 &mut program_cache_for_tx_batch,
2248 environment_config,
2249 None,
2250 SVMTransactionExecutionBudget::default(),
2251 SVMTransactionExecutionCost::default(),
2252 );
2253 let result =
2254 invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2255 assert_eq!(result, Err((0, InstructionError::ReadonlyLamportChange)));
2256
2257 let message = new_sanitized_message(Message::new_with_compiled_instructions(
2258 1,
2259 0,
2260 2,
2261 account_keys.clone(),
2262 Hash::default(),
2263 AccountKeys::new(&account_keys, None).compile_instructions(&[
2264 Instruction::new_with_bincode(
2265 mock_system_program_id,
2266 &MockSystemInstruction::ChangeData { data: 50 },
2267 account_metas,
2268 ),
2269 ]),
2270 ));
2271 let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2272 let environment_config = EnvironmentConfig::new(
2273 Hash::default(),
2274 0,
2275 false,
2276 &MockCallback {},
2277 &feature_set,
2278 &program_runtime_environments,
2279 &sysvar_cache,
2280 );
2281 let mut transaction_context = TransactionContext::new(accounts, Rent::default(), 1, 3, 1);
2282 let mut invoke_context = InvokeContext::new(
2283 &mut transaction_context,
2284 &mut program_cache_for_tx_batch,
2285 environment_config,
2286 None,
2287 SVMTransactionExecutionBudget::default(),
2288 SVMTransactionExecutionCost::default(),
2289 );
2290 let result =
2291 invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2292 assert_eq!(result, Err((0, InstructionError::ReadonlyDataModified)));
2293 }
2294
2295 #[test]
2296 fn test_process_message_duplicate_accounts() {
2297 #[derive(serde::Serialize, serde::Deserialize)]
2298 enum MockSystemInstruction {
2299 BorrowFail,
2300 MultiBorrowMut,
2301 DoWork { lamports: u64, data: u8 },
2302 }
2303
2304 declare_process_instruction!(MockBuiltin, 1, |invoke_context| {
2305 let transaction_context = &invoke_context.transaction_context;
2306 let instruction_context = transaction_context.get_current_instruction_context()?;
2307 let instruction_data = instruction_context.get_instruction_data();
2308 let mut to_account = instruction_context.try_borrow_instruction_account(1)?;
2309 if let Ok(instruction) = bincode::deserialize(instruction_data) {
2310 match instruction {
2311 MockSystemInstruction::BorrowFail => {
2312 let from_account = instruction_context.try_borrow_instruction_account(0)?;
2313 let dup_account = instruction_context.try_borrow_instruction_account(2)?;
2314 if from_account.get_lamports() != dup_account.get_lamports() {
2315 return Err(InstructionError::InvalidArgument);
2316 }
2317 Ok(())
2318 }
2319 MockSystemInstruction::MultiBorrowMut => {
2320 let lamports_a = instruction_context
2321 .try_borrow_instruction_account(0)?
2322 .get_lamports();
2323 let lamports_b = instruction_context
2324 .try_borrow_instruction_account(2)?
2325 .get_lamports();
2326 if lamports_a != lamports_b {
2327 return Err(InstructionError::InvalidArgument);
2328 }
2329 Ok(())
2330 }
2331 MockSystemInstruction::DoWork { lamports, data } => {
2332 let mut dup_account =
2333 instruction_context.try_borrow_instruction_account(2)?;
2334 dup_account.checked_sub_lamports(lamports)?;
2335 to_account.checked_add_lamports(lamports)?;
2336 dup_account.set_data_from_slice(&[data])?;
2337 drop(dup_account);
2338 let mut from_account =
2339 instruction_context.try_borrow_instruction_account(0)?;
2340 from_account.checked_sub_lamports(lamports)?;
2341 to_account.checked_add_lamports(lamports)?;
2342 Ok(())
2343 }
2344 }
2345 } else {
2346 Err(InstructionError::InvalidInstructionData)
2347 }
2348 });
2349 let mock_program_id = Pubkey::from([2u8; 32]);
2350 let accounts = vec![
2351 (
2352 solana_pubkey::new_rand(),
2353 AccountSharedData::new(100, 1, &mock_program_id),
2354 ),
2355 (
2356 solana_pubkey::new_rand(),
2357 AccountSharedData::new(0, 1, &mock_program_id),
2358 ),
2359 (
2360 mock_program_id,
2361 create_loadable_account_for_test("mock_system_program"),
2362 ),
2363 ];
2364 let mut transaction_context =
2365 TransactionContext::new(accounts.clone(), Rent::default(), 1, 3, 1);
2366 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
2367 program_cache_for_tx_batch.replenish(
2368 mock_program_id,
2369 Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
2370 );
2371 let account_metas = vec![
2372 AccountMeta::new(
2373 *transaction_context.get_key_of_account_at_index(0).unwrap(),
2374 true,
2375 ),
2376 AccountMeta::new(
2377 *transaction_context.get_key_of_account_at_index(1).unwrap(),
2378 false,
2379 ),
2380 AccountMeta::new(
2381 *transaction_context.get_key_of_account_at_index(0).unwrap(),
2382 false,
2383 ),
2384 ];
2385
2386 let message = new_sanitized_message(Message::new(
2388 &[Instruction::new_with_bincode(
2389 mock_program_id,
2390 &MockSystemInstruction::BorrowFail,
2391 account_metas.clone(),
2392 )],
2393 Some(transaction_context.get_key_of_account_at_index(0).unwrap()),
2394 ));
2395 let sysvar_cache = SysvarCache::default();
2396 let feature_set = SVMFeatureSet::all_enabled();
2397 let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2398 let environment_config = EnvironmentConfig::new(
2399 Hash::default(),
2400 0,
2401 false,
2402 &MockCallback {},
2403 &feature_set,
2404 &program_runtime_environments,
2405 &sysvar_cache,
2406 );
2407 let mut invoke_context = InvokeContext::new(
2408 &mut transaction_context,
2409 &mut program_cache_for_tx_batch,
2410 environment_config,
2411 None,
2412 SVMTransactionExecutionBudget::default(),
2413 SVMTransactionExecutionCost::default(),
2414 );
2415 let result =
2416 invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2417 assert_eq!(result, Err((0, InstructionError::AccountBorrowFailed)));
2418
2419 let message = new_sanitized_message(Message::new(
2421 &[Instruction::new_with_bincode(
2422 mock_program_id,
2423 &MockSystemInstruction::MultiBorrowMut,
2424 account_metas.clone(),
2425 )],
2426 Some(transaction_context.get_key_of_account_at_index(0).unwrap()),
2427 ));
2428 let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2429 let environment_config = EnvironmentConfig::new(
2430 Hash::default(),
2431 0,
2432 false,
2433 &MockCallback {},
2434 &feature_set,
2435 &program_runtime_environments,
2436 &sysvar_cache,
2437 );
2438 let mut transaction_context =
2439 TransactionContext::new(accounts.clone(), Rent::default(), 1, 3, 1);
2440 let mut invoke_context = InvokeContext::new(
2441 &mut transaction_context,
2442 &mut program_cache_for_tx_batch,
2443 environment_config,
2444 None,
2445 SVMTransactionExecutionBudget::default(),
2446 SVMTransactionExecutionCost::default(),
2447 );
2448 let result =
2449 invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2450 assert!(result.is_ok());
2451
2452 let message = new_sanitized_message(Message::new(
2454 &[Instruction::new_with_bincode(
2455 mock_program_id,
2456 &MockSystemInstruction::DoWork {
2457 lamports: 10,
2458 data: 42,
2459 },
2460 account_metas,
2461 )],
2462 Some(transaction_context.get_key_of_account_at_index(0).unwrap()),
2463 ));
2464 let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2465 let environment_config = EnvironmentConfig::new(
2466 Hash::default(),
2467 0,
2468 false,
2469 &MockCallback {},
2470 &feature_set,
2471 &program_runtime_environments,
2472 &sysvar_cache,
2473 );
2474 let mut transaction_context = TransactionContext::new(accounts, Rent::default(), 1, 3, 1);
2475 let mut invoke_context = InvokeContext::new(
2476 &mut transaction_context,
2477 &mut program_cache_for_tx_batch,
2478 environment_config,
2479 None,
2480 SVMTransactionExecutionBudget::default(),
2481 SVMTransactionExecutionCost::default(),
2482 );
2483 let result =
2484 invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2485 assert!(result.is_ok());
2486 assert_eq!(
2487 transaction_context
2488 .accounts()
2489 .try_borrow(0)
2490 .unwrap()
2491 .lamports(),
2492 80
2493 );
2494 assert_eq!(
2495 transaction_context
2496 .accounts()
2497 .try_borrow(1)
2498 .unwrap()
2499 .lamports(),
2500 20
2501 );
2502 assert_eq!(
2503 transaction_context.accounts().try_borrow(0).unwrap().data(),
2504 &vec![42]
2505 );
2506 }
2507
2508 fn secp256k1_instruction_for_test() -> Instruction {
2509 let message = b"hello";
2510 let bytes: [u8; 32] = rand::random();
2511 let secret_key = libsecp256k1::SecretKey::parse(&bytes).unwrap();
2512 let pubkey = libsecp256k1::PublicKey::from_secret_key(&secret_key);
2513 let eth_address = eth_address_from_pubkey(&pubkey.serialize()[1..].try_into().unwrap());
2514 let (signature, recovery_id) =
2515 solana_secp256k1_program::sign_message(&secret_key.serialize(), &message[..]).unwrap();
2516 new_secp256k1_instruction_with_signature(
2517 &message[..],
2518 &signature,
2519 recovery_id,
2520 ð_address,
2521 )
2522 }
2523
2524 fn ed25519_instruction_for_test() -> Instruction {
2525 let keypair = Keypair::new();
2526 let signature = keypair.sign_message(b"hello");
2527 let pubkey = keypair.pubkey().to_bytes();
2528 new_ed25519_instruction_with_signature(b"hello", signature.as_array(), &pubkey)
2529 }
2530
2531 fn secp256r1_instruction_for_test() -> Instruction {
2532 let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap();
2533 let secret_key = EcKey::generate(&group).unwrap();
2534 let signature = sign_message(b"hello", &secret_key.private_key_to_der().unwrap()).unwrap();
2535 let mut ctx = openssl::bn::BigNumContext::new().unwrap();
2536 let pubkey = secret_key
2537 .public_key()
2538 .to_bytes(
2539 &group,
2540 openssl::ec::PointConversionForm::COMPRESSED,
2541 &mut ctx,
2542 )
2543 .unwrap();
2544 new_secp256r1_instruction_with_signature(b"hello", &signature, &pubkey.try_into().unwrap())
2545 }
2546
2547 #[test]
2548 fn test_precompile() {
2549 let mock_program_id = Pubkey::new_unique();
2550 declare_process_instruction!(MockBuiltin, 1, |_invoke_context| {
2551 Err(InstructionError::Custom(0xbabb1e))
2552 });
2553
2554 let mut secp256k1_account = AccountSharedData::new(1, 0, &native_loader::id());
2555 secp256k1_account.set_executable(true);
2556 let mut ed25519_account = AccountSharedData::new(1, 0, &native_loader::id());
2557 ed25519_account.set_executable(true);
2558 let mut secp256r1_account = AccountSharedData::new(1, 0, &native_loader::id());
2559 secp256r1_account.set_executable(true);
2560 let mut mock_program_account = AccountSharedData::new(1, 0, &native_loader::id());
2561 mock_program_account.set_executable(true);
2562
2563 let fee_payer = Pubkey::new_unique();
2564 let accounts_map: HashMap<Address, AccountSharedData> = HashMap::from([
2565 (
2566 fee_payer,
2567 AccountSharedData::new(1, 0, &system_program::id()),
2568 ),
2569 (secp256k1_program::id(), secp256k1_account),
2570 (ed25519_program::id(), ed25519_account),
2571 (solana_secp256r1_program::id(), secp256r1_account),
2572 (mock_program_id, mock_program_account),
2573 ]);
2574
2575 let message = new_sanitized_message(Message::new(
2576 &[
2577 secp256k1_instruction_for_test(),
2578 ed25519_instruction_for_test(),
2579 secp256r1_instruction_for_test(),
2580 Instruction::new_with_bytes(mock_program_id, &[], vec![]),
2581 ],
2582 Some(&fee_payer),
2583 ));
2584
2585 let accounts = message
2586 .account_keys()
2587 .iter()
2588 .map(|key| (*key, accounts_map.get(key).unwrap().clone()))
2589 .collect();
2590 let mut transaction_context = TransactionContext::new(accounts, Rent::default(), 1, 4, 4);
2591
2592 let sysvar_cache = SysvarCache::default();
2593 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
2594 program_cache_for_tx_batch.replenish(
2595 mock_program_id,
2596 Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
2597 );
2598
2599 struct MockCallback {}
2600 impl InvokeContextCallback for MockCallback {
2601 fn is_precompile(&self, program_id: &Pubkey) -> bool {
2602 program_id == &secp256k1_program::id()
2603 || program_id == &ed25519_program::id()
2604 || program_id == &solana_secp256r1_program::id()
2605 }
2606
2607 fn process_precompile(
2608 &self,
2609 program_id: &Pubkey,
2610 _data: &[u8],
2611 _instruction_datas: Vec<&[u8]>,
2612 ) -> std::result::Result<(), PrecompileError> {
2613 if self.is_precompile(program_id) {
2614 Ok(())
2615 } else {
2616 Err(PrecompileError::InvalidPublicKey)
2617 }
2618 }
2619 }
2620 let feature_set = SVMFeatureSet::all_enabled();
2621 let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2622 let environment_config = EnvironmentConfig::new(
2623 Hash::default(),
2624 0,
2625 false,
2626 &MockCallback {},
2627 &feature_set,
2628 &program_runtime_environments,
2629 &sysvar_cache,
2630 );
2631 let mut invoke_context = InvokeContext::new(
2632 &mut transaction_context,
2633 &mut program_cache_for_tx_batch,
2634 environment_config,
2635 None,
2636 SVMTransactionExecutionBudget::default(),
2637 SVMTransactionExecutionCost::default(),
2638 );
2639 let result =
2640 invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2641
2642 assert_eq!(result, Err((3, InstructionError::Custom(0xbabb1e))));
2643 assert_eq!(
2644 transaction_context.number_of_called_instructions_in_trace(),
2645 4
2646 );
2647 }
2648}