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