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