1use {
2 crate::{
3 execution_budget::{SVMTransactionExecutionBudget, SVMTransactionExecutionCost},
4 loaded_programs::{
5 ProgramCacheEntry, ProgramCacheEntryType, ProgramCacheForTxBatch,
6 ProgramRuntimeEnvironments,
7 },
8 stable_log,
9 sysvar_cache::SysvarCache,
10 },
11 solana_account::{create_account_shared_data_for_test, AccountSharedData},
12 solana_epoch_schedule::EpochSchedule,
13 solana_hash::Hash,
14 solana_instruction::{error::InstructionError, AccountMeta, Instruction},
15 solana_pubkey::Pubkey,
16 solana_sbpf::{
17 ebpf::MM_HEAP_START,
18 elf::Executable as GenericExecutable,
19 error::{EbpfError, ProgramResult},
20 memory_region::MemoryMapping,
21 program::{BuiltinFunction, SBPFVersion},
22 vm::{Config, ContextObject, EbpfVm},
23 },
24 solana_sdk_ids::{
25 bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, loader_v4, native_loader, sysvar,
26 },
27 solana_svm_callback::InvokeContextCallback,
28 solana_svm_feature_set::SVMFeatureSet,
29 solana_svm_log_collector::{ic_msg, LogCollector},
30 solana_svm_measure::measure::Measure,
31 solana_svm_timings::{ExecuteDetailsTimings, ExecuteTimings},
32 solana_svm_transaction::{instruction::SVMInstruction, svm_message::SVMMessage},
33 solana_svm_type_overrides::sync::Arc,
34 solana_transaction_context::{
35 transaction_accounts::KeyedAccountSharedData, IndexOfAccount, InstructionAccount,
36 InstructionContext, TransactionContext, MAX_ACCOUNTS_PER_TRANSACTION,
37 },
38 std::{
39 alloc::Layout,
40 borrow::Cow,
41 cell::RefCell,
42 fmt::{self, Debug},
43 rc::Rc,
44 },
45};
46
47pub type BuiltinFunctionWithContext = BuiltinFunction<InvokeContext<'static, 'static>>;
48pub type Executable = GenericExecutable<InvokeContext<'static, 'static>>;
49pub type RegisterTrace<'a> = &'a [[u64; 12]];
50
51#[macro_export]
53macro_rules! declare_process_instruction {
54 ($process_instruction:ident, $cu_to_consume:expr, |$invoke_context:ident| $inner:tt) => {
55 $crate::solana_sbpf::declare_builtin_function!(
56 $process_instruction,
57 fn rust(
58 invoke_context: &mut $crate::invoke_context::InvokeContext,
59 _arg0: u64,
60 _arg1: u64,
61 _arg2: u64,
62 _arg3: u64,
63 _arg4: u64,
64 _memory_mapping: &mut $crate::solana_sbpf::memory_region::MemoryMapping,
65 ) -> std::result::Result<u64, Box<dyn std::error::Error>> {
66 fn process_instruction_inner(
67 $invoke_context: &mut $crate::invoke_context::InvokeContext,
68 ) -> std::result::Result<(), $crate::__private::InstructionError>
69 $inner
70
71 let consumption_result = if $cu_to_consume > 0
72 {
73 invoke_context.consume_checked($cu_to_consume)
74 } else {
75 Ok(())
76 };
77 consumption_result
78 .and_then(|_| {
79 process_instruction_inner(invoke_context)
80 .map(|_| 0)
81 .map_err(|err| Box::new(err) as Box<dyn std::error::Error>)
82 })
83 .into()
84 }
85 );
86 };
87}
88
89impl ContextObject for InvokeContext<'_, '_> {
90 fn consume(&mut self, amount: u64) {
91 let mut compute_meter = self.compute_meter.borrow_mut();
94 *compute_meter = compute_meter.saturating_sub(amount);
95 }
96
97 fn get_remaining(&self) -> u64 {
98 *self.compute_meter.borrow()
99 }
100}
101
102#[derive(Clone, PartialEq, Eq, Debug)]
103pub struct AllocErr;
104impl fmt::Display for AllocErr {
105 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
106 f.write_str("Error: Memory allocation failed")
107 }
108}
109
110pub struct BpfAllocator {
111 len: u64,
112 pos: u64,
113}
114
115impl BpfAllocator {
116 pub fn new(len: u64) -> Self {
117 Self { len, pos: 0 }
118 }
119
120 pub fn alloc(&mut self, layout: Layout) -> Result<u64, AllocErr> {
121 let bytes_to_align = (self.pos as *const u8).align_offset(layout.align()) as u64;
122 if self
123 .pos
124 .saturating_add(bytes_to_align)
125 .saturating_add(layout.size() as u64)
126 <= self.len
127 {
128 self.pos = self.pos.saturating_add(bytes_to_align);
129 let addr = MM_HEAP_START.saturating_add(self.pos);
130 self.pos = self.pos.saturating_add(layout.size() as u64);
131 Ok(addr)
132 } else {
133 Err(AllocErr)
134 }
135 }
136}
137
138pub struct EnvironmentConfig<'a> {
139 pub blockhash: Hash,
140 pub blockhash_lamports_per_signature: u64,
141 epoch_stake_callback: &'a dyn InvokeContextCallback,
142 feature_set: &'a SVMFeatureSet,
143 pub program_runtime_environments_for_execution: &'a ProgramRuntimeEnvironments,
144 pub program_runtime_environments_for_deployment: &'a ProgramRuntimeEnvironments,
145 sysvar_cache: &'a SysvarCache,
146}
147impl<'a> EnvironmentConfig<'a> {
148 pub fn new(
149 blockhash: Hash,
150 blockhash_lamports_per_signature: u64,
151 epoch_stake_callback: &'a dyn InvokeContextCallback,
152 feature_set: &'a SVMFeatureSet,
153 program_runtime_environments_for_execution: &'a ProgramRuntimeEnvironments,
154 program_runtime_environments_for_deployment: &'a ProgramRuntimeEnvironments,
155 sysvar_cache: &'a SysvarCache,
156 ) -> Self {
157 Self {
158 blockhash,
159 blockhash_lamports_per_signature,
160 epoch_stake_callback,
161 feature_set,
162 program_runtime_environments_for_execution,
163 program_runtime_environments_for_deployment,
164 sysvar_cache,
165 }
166 }
167}
168
169pub struct SyscallContext {
170 pub allocator: BpfAllocator,
171 pub accounts_metadata: Vec<SerializedAccountMetadata>,
172}
173
174#[derive(Debug, Clone)]
175pub struct SerializedAccountMetadata {
176 pub original_data_len: usize,
177 pub vm_data_addr: u64,
178 pub vm_key_addr: u64,
179 pub vm_lamports_addr: u64,
180 pub vm_owner_addr: u64,
181}
182
183pub struct InvokeContext<'a, 'ix_data> {
185 pub transaction_context: &'a mut TransactionContext<'ix_data>,
187 pub program_cache_for_tx_batch: &'a mut ProgramCacheForTxBatch,
189 pub environment_config: EnvironmentConfig<'a>,
191 compute_budget: SVMTransactionExecutionBudget,
193 execution_cost: SVMTransactionExecutionCost,
195 compute_meter: RefCell<u64>,
198 log_collector: Option<Rc<RefCell<LogCollector>>>,
199 pub execute_time: Option<Measure>,
201 pub timings: ExecuteDetailsTimings,
202 pub syscall_context: Vec<Option<SyscallContext>>,
203 register_traces: Vec<(usize, Vec<[u64; 12]>)>,
205}
206
207impl<'a, 'ix_data> InvokeContext<'a, 'ix_data> {
208 #[allow(clippy::too_many_arguments)]
209 pub fn new(
210 transaction_context: &'a mut TransactionContext<'ix_data>,
211 program_cache_for_tx_batch: &'a mut ProgramCacheForTxBatch,
212 environment_config: EnvironmentConfig<'a>,
213 log_collector: Option<Rc<RefCell<LogCollector>>>,
214 compute_budget: SVMTransactionExecutionBudget,
215 execution_cost: SVMTransactionExecutionCost,
216 ) -> Self {
217 Self {
218 transaction_context,
219 program_cache_for_tx_batch,
220 environment_config,
221 log_collector,
222 compute_budget,
223 execution_cost,
224 compute_meter: RefCell::new(compute_budget.compute_unit_limit),
225 execute_time: None,
226 timings: ExecuteDetailsTimings::default(),
227 syscall_context: Vec::new(),
228 register_traces: Vec::new(),
229 }
230 }
231
232 pub fn push(&mut self) -> Result<(), InstructionError> {
234 let instruction_context = self
235 .transaction_context
236 .get_instruction_context_at_index_in_trace(
237 self.transaction_context.get_instruction_trace_length(),
238 )?;
239 let program_id = instruction_context
240 .get_program_key()
241 .map_err(|_| InstructionError::UnsupportedProgramId)?;
242 if self.transaction_context.get_instruction_stack_height() != 0 {
243 let contains =
244 (0..self.transaction_context.get_instruction_stack_height()).any(|level| {
245 self.transaction_context
246 .get_instruction_context_at_nesting_level(level)
247 .and_then(|instruction_context| instruction_context.get_program_key())
248 .map(|program_key| program_key == program_id)
249 .unwrap_or(false)
250 });
251 let is_last = self
252 .transaction_context
253 .get_current_instruction_context()
254 .and_then(|instruction_context| instruction_context.get_program_key())
255 .map(|program_key| program_key == program_id)
256 .unwrap_or(false);
257 if contains && !is_last {
258 return Err(InstructionError::ReentrancyNotAllowed);
260 }
261 }
262
263 self.syscall_context.push(None);
264 self.transaction_context.push()
265 }
266
267 fn pop(&mut self) -> Result<(), InstructionError> {
269 self.syscall_context.pop();
270 self.transaction_context.pop()
271 }
272
273 pub fn get_stack_height(&self) -> usize {
276 self.transaction_context.get_instruction_stack_height()
277 }
278
279 pub fn native_invoke(
281 &mut self,
282 instruction: Instruction,
283 signers: &[Pubkey],
284 ) -> Result<(), InstructionError> {
285 self.prepare_next_instruction(instruction, signers)?;
286 let mut compute_units_consumed = 0;
287 self.process_instruction(&mut compute_units_consumed, &mut ExecuteTimings::default())?;
288 Ok(())
289 }
290
291 pub fn prepare_next_instruction(
294 &mut self,
295 instruction: Instruction,
296 signers: &[Pubkey],
297 ) -> Result<(), InstructionError> {
298 let mut transaction_callee_map: Vec<u8> = vec![u8::MAX; MAX_ACCOUNTS_PER_TRANSACTION];
302 let mut instruction_accounts: Vec<InstructionAccount> =
303 Vec::with_capacity(instruction.accounts.len());
304
305 let program_account_index = {
309 let instruction_context = self.transaction_context.get_current_instruction_context()?;
310 debug_assert!(instruction.accounts.len() <= transaction_callee_map.len());
311
312 for account_meta in instruction.accounts.iter() {
313 let index_in_transaction = self
314 .transaction_context
315 .find_index_of_account(&account_meta.pubkey)
316 .ok_or_else(|| {
317 ic_msg!(
318 self,
319 "Instruction references an unknown account {}",
320 account_meta.pubkey,
321 );
322 InstructionError::MissingAccount
323 })?;
324
325 debug_assert!((index_in_transaction as usize) < transaction_callee_map.len());
326 let index_in_callee = transaction_callee_map
327 .get_mut(index_in_transaction as usize)
328 .unwrap();
329
330 if (*index_in_callee as usize) < instruction_accounts.len() {
331 let cloned_account = {
332 let instruction_account = instruction_accounts
333 .get_mut(*index_in_callee as usize)
334 .ok_or(InstructionError::MissingAccount)?;
335 instruction_account.set_is_signer(
336 instruction_account.is_signer() || account_meta.is_signer,
337 );
338 instruction_account.set_is_writable(
339 instruction_account.is_writable() || account_meta.is_writable,
340 );
341 *instruction_account
342 };
343 instruction_accounts.push(cloned_account);
344 } else {
345 *index_in_callee = instruction_accounts.len() as u8;
346 instruction_accounts.push(InstructionAccount::new(
347 index_in_transaction,
348 account_meta.is_signer,
349 account_meta.is_writable,
350 ));
351 }
352 }
353
354 for current_index in 0..instruction_accounts.len() {
355 let instruction_account = instruction_accounts.get(current_index).unwrap();
356 let index_in_callee = *transaction_callee_map
357 .get(instruction_account.index_in_transaction as usize)
358 .unwrap() as usize;
359
360 if current_index != index_in_callee {
361 let (is_signer, is_writable) = {
362 let reference_account = instruction_accounts
363 .get(index_in_callee)
364 .ok_or(InstructionError::MissingAccount)?;
365 (
366 reference_account.is_signer(),
367 reference_account.is_writable(),
368 )
369 };
370
371 let current_account = instruction_accounts.get_mut(current_index).unwrap();
372 current_account.set_is_signer(current_account.is_signer() || is_signer);
373 current_account.set_is_writable(current_account.is_writable() || is_writable);
374 continue;
376 }
377
378 let index_in_caller = instruction_context.get_index_of_account_in_instruction(
379 instruction_account.index_in_transaction,
380 )?;
381
382 let account_key = &instruction.accounts.get(current_index).unwrap().pubkey;
384 let caller_instruction_account = instruction_context
386 .instruction_accounts()
387 .get(index_in_caller as usize)
388 .unwrap();
389
390 if instruction_account.is_writable() && !caller_instruction_account.is_writable() {
392 ic_msg!(self, "{}'s writable privilege escalated", account_key,);
393 return Err(InstructionError::PrivilegeEscalation);
394 }
395
396 if instruction_account.is_signer()
399 && !(caller_instruction_account.is_signer() || signers.contains(account_key))
400 {
401 ic_msg!(self, "{}'s signer privilege escalated", account_key,);
402 return Err(InstructionError::PrivilegeEscalation);
403 }
404 }
405
406 let callee_program_id = &instruction.program_id;
408 let program_account_index_in_transaction = self
409 .transaction_context
410 .find_index_of_account(callee_program_id);
411 let program_account_index_in_instruction = program_account_index_in_transaction
412 .map(|index| instruction_context.get_index_of_account_in_instruction(index));
413
414 if program_account_index_in_instruction.is_none()
417 || program_account_index_in_instruction.unwrap().is_err()
418 {
419 ic_msg!(self, "Unknown program {}", callee_program_id);
420 return Err(InstructionError::MissingAccount);
421 }
422
423 program_account_index_in_transaction.unwrap()
426 };
427
428 self.transaction_context.configure_next_instruction(
429 program_account_index,
430 instruction_accounts,
431 transaction_callee_map,
432 Cow::Owned(instruction.data),
433 )?;
434 Ok(())
435 }
436
437 pub fn prepare_next_top_level_instruction(
440 &mut self,
441 message: &impl SVMMessage,
442 instruction: &SVMInstruction,
443 program_account_index: IndexOfAccount,
444 data: &'ix_data [u8],
445 ) -> Result<(), InstructionError> {
446 let mut transaction_callee_map: Vec<u8> = vec![u8::MAX; MAX_ACCOUNTS_PER_TRANSACTION];
451 debug_assert!(instruction.accounts.len() <= transaction_callee_map.len());
452
453 let mut instruction_accounts: Vec<InstructionAccount> =
454 Vec::with_capacity(instruction.accounts.len());
455 for index_in_transaction in instruction.accounts.iter() {
456 debug_assert!((*index_in_transaction as usize) < transaction_callee_map.len());
457
458 let index_in_callee = transaction_callee_map
459 .get_mut(*index_in_transaction as usize)
460 .unwrap();
461
462 if (*index_in_callee as usize) > instruction_accounts.len() {
463 *index_in_callee = instruction_accounts.len() as u8;
464 }
465
466 let index_in_transaction = *index_in_transaction as usize;
467 instruction_accounts.push(InstructionAccount::new(
468 index_in_transaction as IndexOfAccount,
469 message.is_signer(index_in_transaction),
470 message.is_writable(index_in_transaction),
471 ));
472 }
473
474 self.transaction_context.configure_next_instruction(
475 program_account_index,
476 instruction_accounts,
477 transaction_callee_map,
478 Cow::Borrowed(data),
479 )?;
480 Ok(())
481 }
482
483 pub fn process_instruction(
485 &mut self,
486 compute_units_consumed: &mut u64,
487 timings: &mut ExecuteTimings,
488 ) -> Result<(), InstructionError> {
489 *compute_units_consumed = 0;
490 self.push()?;
491 self.process_executable_chain(compute_units_consumed, timings)
492 .and(self.pop())
495 }
496
497 pub fn process_precompile(
499 &mut self,
500 program_id: &Pubkey,
501 instruction_data: &[u8],
502 message_instruction_datas_iter: impl Iterator<Item = &'ix_data [u8]>,
503 ) -> Result<(), InstructionError> {
504 self.push()?;
505 let instruction_datas: Vec<_> = message_instruction_datas_iter.collect();
506 self.environment_config
507 .epoch_stake_callback
508 .process_precompile(program_id, instruction_data, instruction_datas)
509 .map_err(InstructionError::from)
510 .and(self.pop())
511 }
512
513 fn process_executable_chain(
515 &mut self,
516 compute_units_consumed: &mut u64,
517 timings: &mut ExecuteTimings,
518 ) -> Result<(), InstructionError> {
519 let instruction_context = self.transaction_context.get_current_instruction_context()?;
520 let process_executable_chain_time = Measure::start("process_executable_chain_time");
521
522 let builtin_id = {
523 let owner_id = instruction_context.get_program_owner()?;
524 if native_loader::check_id(&owner_id) {
525 *instruction_context.get_program_key()?
526 } else if bpf_loader_deprecated::check_id(&owner_id)
527 || bpf_loader::check_id(&owner_id)
528 || bpf_loader_upgradeable::check_id(&owner_id)
529 || loader_v4::check_id(&owner_id)
530 {
531 owner_id
532 } else {
533 return Err(InstructionError::UnsupportedProgramId);
534 }
535 };
536
537 const ENTRYPOINT_KEY: u32 = 0x71E3CF81;
539 let entry = self
540 .program_cache_for_tx_batch
541 .find(&builtin_id)
542 .ok_or(InstructionError::UnsupportedProgramId)?;
543 let function = match &entry.program {
544 ProgramCacheEntryType::Builtin(program) => program
545 .get_function_registry()
546 .lookup_by_key(ENTRYPOINT_KEY)
547 .map(|(_name, function)| function),
548 _ => None,
549 }
550 .ok_or(InstructionError::UnsupportedProgramId)?;
551
552 let program_id = *instruction_context.get_program_key()?;
553 self.transaction_context
554 .set_return_data(program_id, Vec::new())?;
555 let logger = self.get_log_collector();
556 stable_log::program_invoke(&logger, &program_id, self.get_stack_height());
557 let pre_remaining_units = self.get_remaining();
558 let mock_config = Config::default();
562 let empty_memory_mapping =
563 MemoryMapping::new(Vec::new(), &mock_config, SBPFVersion::V0).unwrap();
564 let mut vm = EbpfVm::new(
565 self.environment_config
566 .program_runtime_environments_for_execution
567 .program_runtime_v2
568 .clone(),
569 SBPFVersion::V0,
570 unsafe { std::mem::transmute::<&mut InvokeContext, &mut InvokeContext>(self) },
572 empty_memory_mapping,
573 0,
574 );
575 vm.invoke_function(function);
576 let result = match vm.program_result {
577 ProgramResult::Ok(_) => {
578 stable_log::program_success(&logger, &program_id);
579 Ok(())
580 }
581 ProgramResult::Err(ref err) => {
582 if let EbpfError::SyscallError(syscall_error) = err {
583 if let Some(instruction_err) = syscall_error.downcast_ref::<InstructionError>()
584 {
585 stable_log::program_failure(&logger, &program_id, instruction_err);
586 Err(instruction_err.clone())
587 } else {
588 stable_log::program_failure(&logger, &program_id, syscall_error);
589 Err(InstructionError::ProgramFailedToComplete)
590 }
591 } else {
592 stable_log::program_failure(&logger, &program_id, err);
593 Err(InstructionError::ProgramFailedToComplete)
594 }
595 }
596 };
597 let post_remaining_units = self.get_remaining();
598 *compute_units_consumed = pre_remaining_units.saturating_sub(post_remaining_units);
599
600 if builtin_id == program_id && result.is_ok() && *compute_units_consumed == 0 {
601 return Err(InstructionError::BuiltinProgramsMustConsumeComputeUnits);
602 }
603
604 timings
605 .execute_accessories
606 .process_instructions
607 .process_executable_chain_us += process_executable_chain_time.end_as_us();
608 result
609 }
610
611 pub fn get_log_collector(&self) -> Option<Rc<RefCell<LogCollector>>> {
613 self.log_collector.clone()
614 }
615
616 pub fn consume_checked(&self, amount: u64) -> Result<(), Box<dyn std::error::Error>> {
618 let mut compute_meter = self.compute_meter.borrow_mut();
619 let exceeded = *compute_meter < amount;
620 *compute_meter = compute_meter.saturating_sub(amount);
621 if exceeded {
622 return Err(Box::new(InstructionError::ComputationalBudgetExceeded));
623 }
624 Ok(())
625 }
626
627 pub fn mock_set_remaining(&self, remaining: u64) {
631 *self.compute_meter.borrow_mut() = remaining;
632 }
633
634 pub fn get_compute_budget(&self) -> &SVMTransactionExecutionBudget {
636 &self.compute_budget
637 }
638
639 pub fn get_execution_cost(&self) -> &SVMTransactionExecutionCost {
641 &self.execution_cost
642 }
643
644 pub fn get_feature_set(&self) -> &SVMFeatureSet {
646 self.environment_config.feature_set
647 }
648
649 pub fn get_program_runtime_environments_for_deployment(&self) -> &ProgramRuntimeEnvironments {
650 self.environment_config
651 .program_runtime_environments_for_deployment
652 }
653
654 pub fn is_stake_raise_minimum_delegation_to_1_sol_active(&self) -> bool {
655 self.environment_config
656 .feature_set
657 .stake_raise_minimum_delegation_to_1_sol
658 }
659
660 pub fn is_deprecate_legacy_vote_ixs_active(&self) -> bool {
661 self.environment_config
662 .feature_set
663 .deprecate_legacy_vote_ixs
664 }
665
666 pub fn get_sysvar_cache(&self) -> &SysvarCache {
668 self.environment_config.sysvar_cache
669 }
670
671 pub fn get_epoch_stake(&self) -> u64 {
673 self.environment_config
674 .epoch_stake_callback
675 .get_epoch_stake()
676 }
677
678 pub fn get_epoch_stake_for_vote_account(&self, pubkey: &'a Pubkey) -> u64 {
680 self.environment_config
681 .epoch_stake_callback
682 .get_epoch_stake_for_vote_account(pubkey)
683 }
684
685 pub fn is_precompile(&self, pubkey: &Pubkey) -> bool {
686 self.environment_config
687 .epoch_stake_callback
688 .is_precompile(pubkey)
689 }
690
691 pub fn get_check_aligned(&self) -> bool {
693 self.transaction_context
694 .get_current_instruction_context()
695 .and_then(|instruction_context| {
696 let owner_id = instruction_context.get_program_owner();
697 debug_assert!(owner_id.is_ok());
698 owner_id
699 })
700 .map(|owner_key| owner_key != bpf_loader_deprecated::id())
701 .unwrap_or(true)
702 }
703
704 pub fn set_syscall_context(
706 &mut self,
707 syscall_context: SyscallContext,
708 ) -> Result<(), InstructionError> {
709 *self
710 .syscall_context
711 .last_mut()
712 .ok_or(InstructionError::CallDepth)? = Some(syscall_context);
713 Ok(())
714 }
715
716 pub fn get_syscall_context(&self) -> Result<&SyscallContext, InstructionError> {
718 self.syscall_context
719 .last()
720 .and_then(std::option::Option::as_ref)
721 .ok_or(InstructionError::CallDepth)
722 }
723
724 pub fn get_syscall_context_mut(&mut self) -> Result<&mut SyscallContext, InstructionError> {
726 self.syscall_context
727 .last_mut()
728 .and_then(|syscall_context| syscall_context.as_mut())
729 .ok_or(InstructionError::CallDepth)
730 }
731
732 pub fn insert_register_trace(&mut self, register_trace: Vec<[u64; 12]>) {
734 if register_trace.is_empty() {
735 return;
736 }
737 let Ok(instruction_context) = self.transaction_context.get_current_instruction_context()
738 else {
739 return;
740 };
741 self.register_traces
742 .push((instruction_context.get_index_in_trace(), register_trace));
743 }
744
745 pub fn iterate_vm_traces(
747 &self,
748 callback: &dyn Fn(InstructionContext, &Executable, RegisterTrace),
749 ) {
750 for (index_in_trace, register_trace) in &self.register_traces {
751 let Ok(instruction_context) = self
752 .transaction_context
753 .get_instruction_context_at_index_in_trace(*index_in_trace)
754 else {
755 continue;
756 };
757 let Ok(program_id) = instruction_context.get_program_key() else {
758 continue;
759 };
760 let Some(entry) = self.program_cache_for_tx_batch.find(program_id) else {
761 continue;
762 };
763 let ProgramCacheEntryType::Loaded(ref executable) = entry.program else {
764 continue;
765 };
766 callback(instruction_context, executable, register_trace.as_slice());
767 }
768 }
769}
770
771#[macro_export]
772macro_rules! with_mock_invoke_context_with_feature_set {
773 (
774 $invoke_context:ident,
775 $transaction_context:ident,
776 $feature_set:ident,
777 $transaction_accounts:expr $(,)?
778 ) => {
779 use {
780 solana_svm_callback::InvokeContextCallback,
781 solana_svm_log_collector::LogCollector,
782 $crate::{
783 __private::{Hash, ReadableAccount, Rent, TransactionContext},
784 execution_budget::{SVMTransactionExecutionBudget, SVMTransactionExecutionCost},
785 invoke_context::{EnvironmentConfig, InvokeContext},
786 loaded_programs::{ProgramCacheForTxBatch, ProgramRuntimeEnvironments},
787 sysvar_cache::SysvarCache,
788 },
789 };
790
791 struct MockInvokeContextCallback {}
792 impl InvokeContextCallback for MockInvokeContextCallback {}
793
794 let compute_budget = SVMTransactionExecutionBudget::new_with_defaults(
795 $feature_set.raise_cpi_nesting_limit_to_8,
796 );
797 let mut $transaction_context = TransactionContext::new(
798 $transaction_accounts,
799 Rent::default(),
800 compute_budget.max_instruction_stack_depth,
801 compute_budget.max_instruction_trace_length,
802 );
803 let mut sysvar_cache = SysvarCache::default();
804 sysvar_cache.fill_missing_entries(|pubkey, callback| {
805 for index in 0..$transaction_context.get_number_of_accounts() {
806 if $transaction_context
807 .get_key_of_account_at_index(index)
808 .unwrap()
809 == pubkey
810 {
811 callback(
812 $transaction_context
813 .accounts()
814 .try_borrow(index)
815 .unwrap()
816 .data(),
817 );
818 }
819 }
820 });
821 let program_runtime_environments = ProgramRuntimeEnvironments::default();
822 let environment_config = EnvironmentConfig::new(
823 Hash::default(),
824 0,
825 &MockInvokeContextCallback {},
826 $feature_set,
827 &program_runtime_environments,
828 &program_runtime_environments,
829 &sysvar_cache,
830 );
831 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
832 let mut $invoke_context = InvokeContext::new(
833 &mut $transaction_context,
834 &mut program_cache_for_tx_batch,
835 environment_config,
836 Some(LogCollector::new_ref()),
837 compute_budget,
838 SVMTransactionExecutionCost::new_with_defaults(
839 $feature_set.increase_cpi_account_info_limit,
840 ),
841 );
842 };
843}
844
845#[macro_export]
846macro_rules! with_mock_invoke_context {
847 (
848 $invoke_context:ident,
849 $transaction_context:ident,
850 $transaction_accounts:expr $(,)?
851 ) => {
852 use $crate::with_mock_invoke_context_with_feature_set;
853 let feature_set = &solana_svm_feature_set::SVMFeatureSet::default();
854 with_mock_invoke_context_with_feature_set!(
855 $invoke_context,
856 $transaction_context,
857 feature_set,
858 $transaction_accounts
859 )
860 };
861}
862
863#[allow(clippy::too_many_arguments)]
864pub fn mock_process_instruction_with_feature_set<
865 F: FnMut(&mut InvokeContext),
866 G: FnMut(&mut InvokeContext),
867>(
868 loader_id: &Pubkey,
869 program_index: Option<IndexOfAccount>,
870 instruction_data: &[u8],
871 mut transaction_accounts: Vec<KeyedAccountSharedData>,
872 instruction_account_metas: Vec<AccountMeta>,
873 expected_result: Result<(), InstructionError>,
874 builtin_function: BuiltinFunctionWithContext,
875 mut pre_adjustments: F,
876 mut post_adjustments: G,
877 feature_set: &SVMFeatureSet,
878) -> Vec<AccountSharedData> {
879 let mut instruction_accounts: Vec<InstructionAccount> =
880 Vec::with_capacity(instruction_account_metas.len());
881 for account_meta in instruction_account_metas.iter() {
882 let index_in_transaction = transaction_accounts
883 .iter()
884 .position(|(key, _account)| *key == account_meta.pubkey)
885 .unwrap_or(transaction_accounts.len())
886 as IndexOfAccount;
887 instruction_accounts.push(InstructionAccount::new(
888 index_in_transaction,
889 account_meta.is_signer,
890 account_meta.is_writable,
891 ));
892 }
893
894 let program_index = if let Some(index) = program_index {
895 index
896 } else {
897 let processor_account = AccountSharedData::new(0, 0, &native_loader::id());
898 transaction_accounts.push((*loader_id, processor_account));
899 transaction_accounts.len().saturating_sub(1) as IndexOfAccount
900 };
901 let pop_epoch_schedule_account = if !transaction_accounts
902 .iter()
903 .any(|(key, _)| *key == sysvar::epoch_schedule::id())
904 {
905 transaction_accounts.push((
906 sysvar::epoch_schedule::id(),
907 create_account_shared_data_for_test(&EpochSchedule::default()),
908 ));
909 true
910 } else {
911 false
912 };
913 with_mock_invoke_context_with_feature_set!(
914 invoke_context,
915 transaction_context,
916 feature_set,
917 transaction_accounts
918 );
919 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
920 program_cache_for_tx_batch.replenish(
921 *loader_id,
922 Arc::new(ProgramCacheEntry::new_builtin(0, 0, builtin_function)),
923 );
924 program_cache_for_tx_batch.set_slot_for_tests(
925 invoke_context
926 .get_sysvar_cache()
927 .get_clock()
928 .map(|clock| clock.slot)
929 .unwrap_or(1),
930 );
931 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
932 pre_adjustments(&mut invoke_context);
933 invoke_context
934 .transaction_context
935 .configure_next_instruction_for_tests(
936 program_index,
937 instruction_accounts,
938 instruction_data.to_vec(),
939 )
940 .unwrap();
941 let result = invoke_context.process_instruction(&mut 0, &mut ExecuteTimings::default());
942 assert_eq!(result, expected_result);
943 post_adjustments(&mut invoke_context);
944 let mut transaction_accounts = transaction_context.deconstruct_without_keys().unwrap();
945 if pop_epoch_schedule_account {
946 transaction_accounts.pop();
947 }
948 transaction_accounts.pop();
949 transaction_accounts
950}
951
952pub fn mock_process_instruction<F: FnMut(&mut InvokeContext), G: FnMut(&mut InvokeContext)>(
953 loader_id: &Pubkey,
954 program_index: Option<IndexOfAccount>,
955 instruction_data: &[u8],
956 transaction_accounts: Vec<KeyedAccountSharedData>,
957 instruction_account_metas: Vec<AccountMeta>,
958 expected_result: Result<(), InstructionError>,
959 builtin_function: BuiltinFunctionWithContext,
960 pre_adjustments: F,
961 post_adjustments: G,
962) -> Vec<AccountSharedData> {
963 mock_process_instruction_with_feature_set(
964 loader_id,
965 program_index,
966 instruction_data,
967 transaction_accounts,
968 instruction_account_metas,
969 expected_result,
970 builtin_function,
971 pre_adjustments,
972 post_adjustments,
973 &SVMFeatureSet::all_enabled(),
974 )
975}
976
977#[cfg(test)]
978mod tests {
979 use {
980 super::*,
981 crate::execution_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
982 serde::{Deserialize, Serialize},
983 solana_account::WritableAccount,
984 solana_instruction::Instruction,
985 solana_keypair::Keypair,
986 solana_rent::Rent,
987 solana_signer::Signer,
988 solana_transaction::{sanitized::SanitizedTransaction, Transaction},
989 solana_transaction_context::MAX_ACCOUNTS_PER_INSTRUCTION,
990 std::collections::HashSet,
991 test_case::test_case,
992 };
993
994 #[derive(Debug, Serialize, Deserialize)]
995 enum MockInstruction {
996 NoopSuccess,
997 NoopFail,
998 ModifyOwned,
999 ModifyNotOwned,
1000 ModifyReadonly,
1001 UnbalancedPush,
1002 UnbalancedPop,
1003 ConsumeComputeUnits {
1004 compute_units_to_consume: u64,
1005 desired_result: Result<(), InstructionError>,
1006 },
1007 Resize {
1008 new_len: u64,
1009 },
1010 }
1011
1012 const MOCK_BUILTIN_COMPUTE_UNIT_COST: u64 = 1;
1013
1014 declare_process_instruction!(
1015 MockBuiltin,
1016 MOCK_BUILTIN_COMPUTE_UNIT_COST,
1017 |invoke_context| {
1018 let transaction_context = &invoke_context.transaction_context;
1019 let instruction_context = transaction_context.get_current_instruction_context()?;
1020 let instruction_data = instruction_context.get_instruction_data();
1021 let program_id = instruction_context.get_program_key()?;
1022 let instruction_accounts = (0..4)
1023 .map(|instruction_account_index| {
1024 InstructionAccount::new(instruction_account_index, false, false)
1025 })
1026 .collect::<Vec<_>>();
1027 assert_eq!(
1028 program_id,
1029 instruction_context
1030 .try_borrow_instruction_account(0)?
1031 .get_owner()
1032 );
1033 assert_ne!(
1034 instruction_context
1035 .try_borrow_instruction_account(1)?
1036 .get_owner(),
1037 instruction_context.get_key_of_instruction_account(0)?
1038 );
1039
1040 if let Ok(instruction) = bincode::deserialize(instruction_data) {
1041 match instruction {
1042 MockInstruction::NoopSuccess => (),
1043 MockInstruction::NoopFail => return Err(InstructionError::GenericError),
1044 MockInstruction::ModifyOwned => instruction_context
1045 .try_borrow_instruction_account(0)?
1046 .set_data_from_slice(&[1])?,
1047 MockInstruction::ModifyNotOwned => instruction_context
1048 .try_borrow_instruction_account(1)?
1049 .set_data_from_slice(&[1])?,
1050 MockInstruction::ModifyReadonly => instruction_context
1051 .try_borrow_instruction_account(2)?
1052 .set_data_from_slice(&[1])?,
1053 MockInstruction::UnbalancedPush => {
1054 instruction_context
1055 .try_borrow_instruction_account(0)?
1056 .checked_add_lamports(1)?;
1057 let program_id = *transaction_context.get_key_of_account_at_index(3)?;
1058 let metas = vec![
1059 AccountMeta::new_readonly(
1060 *transaction_context.get_key_of_account_at_index(0)?,
1061 false,
1062 ),
1063 AccountMeta::new_readonly(
1064 *transaction_context.get_key_of_account_at_index(1)?,
1065 false,
1066 ),
1067 ];
1068 let inner_instruction = Instruction::new_with_bincode(
1069 program_id,
1070 &MockInstruction::NoopSuccess,
1071 metas,
1072 );
1073 invoke_context
1074 .transaction_context
1075 .configure_next_instruction_for_tests(3, instruction_accounts, vec![])
1076 .unwrap();
1077 let result = invoke_context.push();
1078 assert_eq!(result, Err(InstructionError::UnbalancedInstruction));
1079 result?;
1080 invoke_context
1081 .native_invoke(inner_instruction, &[])
1082 .and(invoke_context.pop())?;
1083 }
1084 MockInstruction::UnbalancedPop => instruction_context
1085 .try_borrow_instruction_account(0)?
1086 .checked_add_lamports(1)?,
1087 MockInstruction::ConsumeComputeUnits {
1088 compute_units_to_consume,
1089 desired_result,
1090 } => {
1091 invoke_context
1092 .consume_checked(compute_units_to_consume)
1093 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
1094 return desired_result;
1095 }
1096 MockInstruction::Resize { new_len } => instruction_context
1097 .try_borrow_instruction_account(0)?
1098 .set_data_from_slice(&vec![0; new_len as usize])?,
1099 }
1100 } else {
1101 return Err(InstructionError::InvalidInstructionData);
1102 }
1103 Ok(())
1104 }
1105 );
1106
1107 #[test_case(false; "SIMD-0268 disabled")]
1108 #[test_case(true; "SIMD-0268 enabled")]
1109 fn test_instruction_stack_height(simd_0268_active: bool) {
1110 let one_more_than_max_depth =
1111 SVMTransactionExecutionBudget::new_with_defaults(simd_0268_active)
1112 .max_instruction_stack_depth
1113 .saturating_add(1);
1114 let mut invoke_stack = vec![];
1115 let mut transaction_accounts = vec![];
1116 let mut instruction_accounts = vec![];
1117 for index in 0..one_more_than_max_depth {
1118 invoke_stack.push(solana_pubkey::new_rand());
1119 transaction_accounts.push((
1120 solana_pubkey::new_rand(),
1121 AccountSharedData::new(index as u64, 1, invoke_stack.get(index).unwrap()),
1122 ));
1123 instruction_accounts.push(InstructionAccount::new(
1124 index as IndexOfAccount,
1125 false,
1126 true,
1127 ));
1128 }
1129 for (index, program_id) in invoke_stack.iter().enumerate() {
1130 transaction_accounts.push((
1131 *program_id,
1132 AccountSharedData::new(1, 1, &solana_pubkey::Pubkey::default()),
1133 ));
1134 instruction_accounts.push(InstructionAccount::new(
1135 index as IndexOfAccount,
1136 false,
1137 false,
1138 ));
1139 }
1140 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1141
1142 let mut depth_reached: usize = 0;
1144 for _ in 0..invoke_stack.len() {
1145 invoke_context
1146 .transaction_context
1147 .configure_next_instruction_for_tests(
1148 one_more_than_max_depth.saturating_add(depth_reached) as IndexOfAccount,
1149 instruction_accounts.clone(),
1150 vec![],
1151 )
1152 .unwrap();
1153 if Err(InstructionError::CallDepth) == invoke_context.push() {
1154 break;
1155 }
1156 depth_reached = depth_reached.saturating_add(1);
1157 }
1158 assert_ne!(depth_reached, 0);
1159 assert!(depth_reached < one_more_than_max_depth);
1160 }
1161
1162 #[test]
1163 fn test_max_instruction_trace_length() {
1164 const MAX_INSTRUCTIONS: usize = 8;
1165 let mut transaction_context = TransactionContext::new(
1166 vec![(
1167 Pubkey::new_unique(),
1168 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1169 )],
1170 Rent::default(),
1171 1,
1172 MAX_INSTRUCTIONS,
1173 );
1174 for _ in 0..MAX_INSTRUCTIONS {
1175 transaction_context.push().unwrap();
1176 transaction_context
1177 .configure_next_instruction_for_tests(
1178 0,
1179 vec![InstructionAccount::new(0, false, false)],
1180 vec![],
1181 )
1182 .unwrap();
1183 transaction_context.pop().unwrap();
1184 }
1185 assert_eq!(
1186 transaction_context.push(),
1187 Err(InstructionError::MaxInstructionTraceLengthExceeded)
1188 );
1189 }
1190
1191 #[test_case(MockInstruction::NoopSuccess, Ok(()); "NoopSuccess")]
1192 #[test_case(MockInstruction::NoopFail, Err(InstructionError::GenericError); "NoopFail")]
1193 #[test_case(MockInstruction::ModifyOwned, Ok(()); "ModifyOwned")]
1194 #[test_case(MockInstruction::ModifyNotOwned, Err(InstructionError::ExternalAccountDataModified); "ModifyNotOwned")]
1195 #[test_case(MockInstruction::ModifyReadonly, Err(InstructionError::ReadonlyDataModified); "ModifyReadonly")]
1196 #[test_case(MockInstruction::UnbalancedPush, Err(InstructionError::UnbalancedInstruction); "UnbalancedPush")]
1197 #[test_case(MockInstruction::UnbalancedPop, Err(InstructionError::UnbalancedInstruction); "UnbalancedPop")]
1198 fn test_process_instruction_account_modifications(
1199 instruction: MockInstruction,
1200 expected_result: Result<(), InstructionError>,
1201 ) {
1202 let callee_program_id = solana_pubkey::new_rand();
1203 let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1204 let not_owned_account = AccountSharedData::new(84, 1, &solana_pubkey::new_rand());
1205 let readonly_account = AccountSharedData::new(168, 1, &solana_pubkey::new_rand());
1206 let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1207 let mut program_account = AccountSharedData::new(1, 1, &native_loader::id());
1208 program_account.set_executable(true);
1209 let transaction_accounts = vec![
1210 (solana_pubkey::new_rand(), owned_account),
1211 (solana_pubkey::new_rand(), not_owned_account),
1212 (solana_pubkey::new_rand(), readonly_account),
1213 (callee_program_id, program_account),
1214 (solana_pubkey::new_rand(), loader_account),
1215 ];
1216 let metas = vec![
1217 AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1218 AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1219 AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1220 ];
1221 let instruction_accounts = (0..4)
1222 .map(|instruction_account_index| {
1223 InstructionAccount::new(
1224 instruction_account_index,
1225 false,
1226 instruction_account_index < 2,
1227 )
1228 })
1229 .collect::<Vec<_>>();
1230 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1231 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1232 program_cache_for_tx_batch.replenish(
1233 callee_program_id,
1234 Arc::new(ProgramCacheEntry::new_builtin(0, 1, MockBuiltin::vm)),
1235 );
1236 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1237
1238 invoke_context
1240 .transaction_context
1241 .configure_next_instruction_for_tests(4, instruction_accounts, vec![])
1242 .unwrap();
1243 invoke_context.push().unwrap();
1244 let inner_instruction =
1245 Instruction::new_with_bincode(callee_program_id, &instruction, metas.clone());
1246 let result = invoke_context
1247 .native_invoke(inner_instruction, &[])
1248 .and(invoke_context.pop());
1249 assert_eq!(result, expected_result);
1250 }
1251
1252 #[test_case(Ok(()); "Ok")]
1253 #[test_case(Err(InstructionError::GenericError); "GenericError")]
1254 fn test_process_instruction_compute_unit_consumption(
1255 expected_result: Result<(), InstructionError>,
1256 ) {
1257 let callee_program_id = solana_pubkey::new_rand();
1258 let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1259 let not_owned_account = AccountSharedData::new(84, 1, &solana_pubkey::new_rand());
1260 let readonly_account = AccountSharedData::new(168, 1, &solana_pubkey::new_rand());
1261 let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1262 let mut program_account = AccountSharedData::new(1, 1, &native_loader::id());
1263 program_account.set_executable(true);
1264 let transaction_accounts = vec![
1265 (solana_pubkey::new_rand(), owned_account),
1266 (solana_pubkey::new_rand(), not_owned_account),
1267 (solana_pubkey::new_rand(), readonly_account),
1268 (callee_program_id, program_account),
1269 (solana_pubkey::new_rand(), loader_account),
1270 ];
1271 let metas = vec![
1272 AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1273 AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1274 AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1275 ];
1276 let instruction_accounts = (0..4)
1277 .map(|instruction_account_index| {
1278 InstructionAccount::new(
1279 instruction_account_index,
1280 false,
1281 instruction_account_index < 2,
1282 )
1283 })
1284 .collect::<Vec<_>>();
1285 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1286 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1287 program_cache_for_tx_batch.replenish(
1288 callee_program_id,
1289 Arc::new(ProgramCacheEntry::new_builtin(0, 1, MockBuiltin::vm)),
1290 );
1291 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1292
1293 let compute_units_to_consume = 10;
1295 invoke_context
1296 .transaction_context
1297 .configure_next_instruction_for_tests(4, instruction_accounts, vec![])
1298 .unwrap();
1299 invoke_context.push().unwrap();
1300 let inner_instruction = Instruction::new_with_bincode(
1301 callee_program_id,
1302 &MockInstruction::ConsumeComputeUnits {
1303 compute_units_to_consume,
1304 desired_result: expected_result.clone(),
1305 },
1306 metas.clone(),
1307 );
1308 invoke_context
1309 .prepare_next_instruction(inner_instruction, &[])
1310 .unwrap();
1311
1312 let mut compute_units_consumed = 0;
1313 let result = invoke_context
1314 .process_instruction(&mut compute_units_consumed, &mut ExecuteTimings::default());
1315
1316 assert!(compute_units_consumed > 0);
1320 assert_eq!(
1321 compute_units_consumed,
1322 compute_units_to_consume.saturating_add(MOCK_BUILTIN_COMPUTE_UNIT_COST),
1323 );
1324 assert_eq!(result, expected_result);
1325
1326 invoke_context.pop().unwrap();
1327 }
1328
1329 #[test]
1330 fn test_invoke_context_compute_budget() {
1331 let transaction_accounts = vec![(solana_pubkey::new_rand(), AccountSharedData::default())];
1332 let execution_budget = SVMTransactionExecutionBudget {
1333 compute_unit_limit: u64::from(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT),
1334 ..SVMTransactionExecutionBudget::default()
1335 };
1336
1337 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1338 invoke_context.compute_budget = execution_budget;
1339
1340 invoke_context
1341 .transaction_context
1342 .configure_next_instruction_for_tests(0, vec![], vec![])
1343 .unwrap();
1344 invoke_context.push().unwrap();
1345 assert_eq!(*invoke_context.get_compute_budget(), execution_budget);
1346 invoke_context.pop().unwrap();
1347 }
1348
1349 #[test_case(0; "Resize the account to *the same size*, so not consuming any additional size")]
1350 #[test_case(1; "Resize the account larger")]
1351 #[test_case(-1; "Resize the account smaller")]
1352 fn test_process_instruction_accounts_resize_delta(resize_delta: i64) {
1353 let program_key = Pubkey::new_unique();
1354 let user_account_data_len = 123u64;
1355 let user_account =
1356 AccountSharedData::new(100, user_account_data_len as usize, &program_key);
1357 let dummy_account = AccountSharedData::new(10, 0, &program_key);
1358 let mut program_account = AccountSharedData::new(500, 500, &native_loader::id());
1359 program_account.set_executable(true);
1360 let transaction_accounts = vec![
1361 (Pubkey::new_unique(), user_account),
1362 (Pubkey::new_unique(), dummy_account),
1363 (program_key, program_account),
1364 ];
1365 let instruction_accounts = vec![
1366 InstructionAccount::new(0, false, true),
1367 InstructionAccount::new(1, false, false),
1368 ];
1369 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1370 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1371 program_cache_for_tx_batch.replenish(
1372 program_key,
1373 Arc::new(ProgramCacheEntry::new_builtin(0, 0, MockBuiltin::vm)),
1374 );
1375 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1376
1377 let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
1378 let instruction_data = bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1379
1380 invoke_context
1381 .transaction_context
1382 .configure_next_instruction_for_tests(2, instruction_accounts, instruction_data)
1383 .unwrap();
1384 let result = invoke_context.process_instruction(&mut 0, &mut ExecuteTimings::default());
1385
1386 assert!(result.is_ok());
1387 assert_eq!(
1388 invoke_context.transaction_context.accounts().resize_delta(),
1389 resize_delta
1390 );
1391 }
1392
1393 #[test]
1394 fn test_prepare_instruction_maximum_accounts() {
1395 let mut transaction_accounts: Vec<KeyedAccountSharedData> =
1396 Vec::with_capacity(MAX_ACCOUNTS_PER_TRANSACTION);
1397 let mut account_metas: Vec<AccountMeta> = Vec::with_capacity(MAX_ACCOUNTS_PER_INSTRUCTION);
1398
1399 let fee_payer = Keypair::new();
1401 transaction_accounts.push((
1402 fee_payer.pubkey(),
1403 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1404 ));
1405 account_metas.push(AccountMeta::new(fee_payer.pubkey(), true));
1406
1407 let program_id = Pubkey::new_unique();
1408 let mut program_account = AccountSharedData::new(1, 1, &Pubkey::new_unique());
1409 program_account.set_executable(true);
1410 transaction_accounts.push((program_id, program_account));
1411 account_metas.push(AccountMeta::new_readonly(program_id, false));
1412
1413 for _ in 2..MAX_ACCOUNTS_PER_INSTRUCTION {
1414 let key = Pubkey::new_unique();
1415 transaction_accounts.push((key, AccountSharedData::new(1, 1, &Pubkey::new_unique())));
1416 account_metas.push(AccountMeta::new_readonly(key, false));
1417 }
1418
1419 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1420
1421 let instruction_1 = Instruction::new_with_bytes(program_id, &[20], account_metas.clone());
1422
1423 let instruction_2 = Instruction::new_with_bytes(
1424 program_id,
1425 &[20],
1426 account_metas.iter().rev().cloned().collect(),
1427 );
1428
1429 let transaction = Transaction::new_with_payer(
1430 &[instruction_1.clone(), instruction_2.clone()],
1431 Some(&fee_payer.pubkey()),
1432 );
1433
1434 let sanitized =
1435 SanitizedTransaction::try_from_legacy_transaction(transaction, &HashSet::new())
1436 .unwrap();
1437
1438 fn test_case_1(invoke_context: &InvokeContext) {
1439 let instruction_context = invoke_context
1440 .transaction_context
1441 .get_next_instruction_context()
1442 .unwrap();
1443 for index_in_transaction in 0..MAX_ACCOUNTS_PER_INSTRUCTION as IndexOfAccount {
1444 let index_in_instruction = instruction_context
1445 .get_index_of_account_in_instruction(index_in_transaction as IndexOfAccount)
1446 .unwrap();
1447 let other_transaction = instruction_context
1448 .get_index_of_instruction_account_in_transaction(index_in_instruction)
1449 .unwrap();
1450 assert_eq!(index_in_transaction, other_transaction);
1451 assert_eq!(index_in_transaction, index_in_instruction);
1452 }
1453 }
1454
1455 fn test_case_2(invoke_context: &InvokeContext) {
1456 let instruction_context = invoke_context
1457 .transaction_context
1458 .get_next_instruction_context()
1459 .unwrap();
1460 for index_in_transaction in 0..MAX_ACCOUNTS_PER_INSTRUCTION as IndexOfAccount {
1461 let index_in_instruction = instruction_context
1462 .get_index_of_account_in_instruction(index_in_transaction as IndexOfAccount)
1463 .unwrap();
1464 let other_transaction = instruction_context
1465 .get_index_of_instruction_account_in_transaction(index_in_instruction)
1466 .unwrap();
1467 assert_eq!(
1468 index_in_instruction,
1469 (MAX_ACCOUNTS_PER_INSTRUCTION as IndexOfAccount)
1470 .saturating_sub(index_in_transaction)
1471 .saturating_sub(1)
1472 );
1473 assert_eq!(index_in_transaction, other_transaction);
1474 }
1475 }
1476
1477 let svm_instruction =
1478 SVMInstruction::from(sanitized.message().instructions().first().unwrap());
1479 invoke_context
1480 .prepare_next_top_level_instruction(
1481 &sanitized,
1482 &svm_instruction,
1483 90,
1484 svm_instruction.data,
1485 )
1486 .unwrap();
1487
1488 test_case_1(&invoke_context);
1489
1490 invoke_context.transaction_context.push().unwrap();
1491 let svm_instruction =
1492 SVMInstruction::from(sanitized.message().instructions().get(1).unwrap());
1493 invoke_context
1494 .prepare_next_top_level_instruction(
1495 &sanitized,
1496 &svm_instruction,
1497 90,
1498 svm_instruction.data,
1499 )
1500 .unwrap();
1501
1502 test_case_2(&invoke_context);
1503
1504 invoke_context.transaction_context.push().unwrap();
1505 invoke_context
1506 .prepare_next_instruction(instruction_1, &[fee_payer.pubkey()])
1507 .unwrap();
1508 test_case_1(&invoke_context);
1509
1510 invoke_context.transaction_context.push().unwrap();
1511 invoke_context
1512 .prepare_next_instruction(instruction_2, &[fee_payer.pubkey()])
1513 .unwrap();
1514 test_case_2(&invoke_context);
1515 }
1516
1517 #[test]
1518 fn test_duplicated_accounts() {
1519 let mut transaction_accounts: Vec<KeyedAccountSharedData> =
1520 Vec::with_capacity(MAX_ACCOUNTS_PER_TRANSACTION);
1521 let mut account_metas: Vec<AccountMeta> =
1522 Vec::with_capacity(MAX_ACCOUNTS_PER_INSTRUCTION.saturating_sub(1));
1523
1524 let fee_payer = Keypair::new();
1526 transaction_accounts.push((
1527 fee_payer.pubkey(),
1528 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1529 ));
1530 account_metas.push(AccountMeta::new(fee_payer.pubkey(), true));
1531
1532 let program_id = Pubkey::new_unique();
1533 let mut program_account = AccountSharedData::new(1, 1, &Pubkey::new_unique());
1534 program_account.set_executable(true);
1535 transaction_accounts.push((program_id, program_account));
1536 account_metas.push(AccountMeta::new_readonly(program_id, false));
1537
1538 for i in 2..account_metas.capacity() {
1539 if i % 2 == 0 {
1540 let key = Pubkey::new_unique();
1541 transaction_accounts
1542 .push((key, AccountSharedData::new(1, 1, &Pubkey::new_unique())));
1543 account_metas.push(AccountMeta::new_readonly(key, false));
1544 } else {
1545 let last_key = transaction_accounts.last().unwrap().0;
1546 account_metas.push(AccountMeta::new_readonly(last_key, false));
1547 }
1548 }
1549
1550 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1551
1552 let instruction = Instruction::new_with_bytes(program_id, &[20], account_metas.clone());
1553
1554 let transaction = Transaction::new_with_payer(&[instruction], Some(&fee_payer.pubkey()));
1555
1556 let sanitized =
1557 SanitizedTransaction::try_from_legacy_transaction(transaction, &HashSet::new())
1558 .unwrap();
1559 let svm_instruction =
1560 SVMInstruction::from(sanitized.message().instructions().first().unwrap());
1561
1562 invoke_context
1563 .prepare_next_top_level_instruction(
1564 &sanitized,
1565 &svm_instruction,
1566 90,
1567 svm_instruction.data,
1568 )
1569 .unwrap();
1570
1571 {
1572 let instruction_context = invoke_context
1573 .transaction_context
1574 .get_next_instruction_context()
1575 .unwrap();
1576 for index_in_instruction in 2..account_metas.len() as IndexOfAccount {
1577 let is_duplicate = instruction_context
1578 .is_instruction_account_duplicate(index_in_instruction)
1579 .unwrap();
1580 if index_in_instruction % 2 == 0 {
1581 assert!(is_duplicate.is_none());
1582 } else {
1583 assert_eq!(is_duplicate, Some(index_in_instruction.saturating_sub(1)));
1584 }
1585 }
1586 }
1587
1588 invoke_context.transaction_context.push().unwrap();
1589
1590 let instruction = Instruction::new_with_bytes(
1591 program_id,
1592 &[20],
1593 account_metas.iter().cloned().rev().collect(),
1594 );
1595
1596 invoke_context
1597 .prepare_next_instruction(instruction, &[fee_payer.pubkey()])
1598 .unwrap();
1599 let instruction_context = invoke_context
1600 .transaction_context
1601 .get_next_instruction_context()
1602 .unwrap();
1603 for index_in_instruction in 2..account_metas.len().saturating_sub(1) as u16 {
1604 let is_duplicate = instruction_context
1605 .is_instruction_account_duplicate(index_in_instruction)
1606 .unwrap();
1607 if index_in_instruction % 2 == 0 {
1608 assert!(is_duplicate.is_none());
1609 } else {
1610 assert_eq!(is_duplicate, Some(index_in_instruction.saturating_sub(1)));
1611 }
1612 }
1613 }
1614}