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