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