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<u16> = vec![u16::MAX; MAX_ACCOUNTS_PER_TRANSACTION];
300 let mut instruction_accounts: Vec<InstructionAccount> =
301 Vec::with_capacity(instruction.accounts.len());
302
303 let program_account_index = {
307 let instruction_context = self.transaction_context.get_current_instruction_context()?;
308
309 for account_meta in instruction.accounts.iter() {
310 let index_in_transaction = self
311 .transaction_context
312 .find_index_of_account(&account_meta.pubkey)
313 .ok_or_else(|| {
314 ic_msg!(
315 self,
316 "Instruction references an unknown account {}",
317 account_meta.pubkey,
318 );
319 InstructionError::MissingAccount
320 })?;
321
322 debug_assert!((index_in_transaction as usize) < transaction_callee_map.len());
323 let index_in_callee = transaction_callee_map
324 .get_mut(index_in_transaction as usize)
325 .unwrap();
326
327 if (*index_in_callee as usize) < instruction_accounts.len() {
328 let cloned_account = {
329 let instruction_account = instruction_accounts
330 .get_mut(*index_in_callee as usize)
331 .ok_or(InstructionError::MissingAccount)?;
332 instruction_account.set_is_signer(
333 instruction_account.is_signer() || account_meta.is_signer,
334 );
335 instruction_account.set_is_writable(
336 instruction_account.is_writable() || account_meta.is_writable,
337 );
338 *instruction_account
339 };
340 instruction_accounts.push(cloned_account);
341 } else {
342 *index_in_callee = instruction_accounts.len() as u16;
343 instruction_accounts.push(InstructionAccount::new(
344 index_in_transaction,
345 account_meta.is_signer,
346 account_meta.is_writable,
347 ));
348 }
349 }
350
351 for current_index in 0..instruction_accounts.len() {
352 let instruction_account = instruction_accounts.get(current_index).unwrap();
353 let index_in_callee = *transaction_callee_map
354 .get(instruction_account.index_in_transaction as usize)
355 .unwrap() as usize;
356
357 if current_index != index_in_callee {
358 let (is_signer, is_writable) = {
359 let reference_account = instruction_accounts
360 .get(index_in_callee)
361 .ok_or(InstructionError::MissingAccount)?;
362 (
363 reference_account.is_signer(),
364 reference_account.is_writable(),
365 )
366 };
367
368 let current_account = instruction_accounts.get_mut(current_index).unwrap();
369 current_account.set_is_signer(current_account.is_signer() || is_signer);
370 current_account.set_is_writable(current_account.is_writable() || is_writable);
371 continue;
373 }
374
375 let index_in_caller = instruction_context.get_index_of_account_in_instruction(
376 instruction_account.index_in_transaction,
377 )?;
378
379 let account_key = &instruction.accounts.get(current_index).unwrap().pubkey;
381 let caller_instruction_account = instruction_context
383 .instruction_accounts()
384 .get(index_in_caller as usize)
385 .unwrap();
386
387 if instruction_account.is_writable() && !caller_instruction_account.is_writable() {
389 ic_msg!(self, "{}'s writable privilege escalated", account_key,);
390 return Err(InstructionError::PrivilegeEscalation);
391 }
392
393 if instruction_account.is_signer()
396 && !(caller_instruction_account.is_signer() || signers.contains(account_key))
397 {
398 ic_msg!(self, "{}'s signer privilege escalated", account_key,);
399 return Err(InstructionError::PrivilegeEscalation);
400 }
401 }
402
403 let callee_program_id = &instruction.program_id;
405 let program_account_index_in_transaction = self
406 .transaction_context
407 .find_index_of_account(callee_program_id);
408 let program_account_index_in_instruction = program_account_index_in_transaction
409 .map(|index| instruction_context.get_index_of_account_in_instruction(index));
410
411 if program_account_index_in_instruction.is_none()
414 || program_account_index_in_instruction.unwrap().is_err()
415 {
416 ic_msg!(self, "Unknown program {}", callee_program_id);
417 return Err(InstructionError::MissingAccount);
418 }
419
420 program_account_index_in_transaction.unwrap()
423 };
424
425 self.transaction_context.configure_next_instruction(
426 program_account_index,
427 instruction_accounts,
428 transaction_callee_map,
429 Cow::Owned(instruction.data),
430 )?;
431 Ok(())
432 }
433
434 pub fn prepare_next_top_level_instruction(
437 &mut self,
438 message: &impl SVMMessage,
439 instruction: &SVMInstruction,
440 program_account_index: IndexOfAccount,
441 data: &'ix_data [u8],
442 ) -> Result<(), InstructionError> {
443 let mut transaction_callee_map: Vec<u16> = vec![u16::MAX; MAX_ACCOUNTS_PER_TRANSACTION];
445
446 let mut instruction_accounts: Vec<InstructionAccount> =
447 Vec::with_capacity(instruction.accounts.len());
448 for index_in_transaction in instruction.accounts.iter() {
449 debug_assert!((*index_in_transaction as usize) < transaction_callee_map.len());
450
451 let index_in_callee = transaction_callee_map
452 .get_mut(*index_in_transaction as usize)
453 .unwrap();
454
455 if (*index_in_callee as usize) > instruction_accounts.len() {
456 *index_in_callee = instruction_accounts.len() as u16;
457 }
458
459 let index_in_transaction = *index_in_transaction as usize;
460 instruction_accounts.push(InstructionAccount::new(
461 index_in_transaction as IndexOfAccount,
462 message.is_signer(index_in_transaction),
463 message.is_writable(index_in_transaction),
464 ));
465 }
466
467 self.transaction_context.configure_next_instruction(
468 program_account_index,
469 instruction_accounts,
470 transaction_callee_map,
471 Cow::Borrowed(data),
472 )?;
473 Ok(())
474 }
475
476 pub fn process_instruction(
478 &mut self,
479 compute_units_consumed: &mut u64,
480 timings: &mut ExecuteTimings,
481 ) -> Result<(), InstructionError> {
482 *compute_units_consumed = 0;
483 self.push()?;
484 self.process_executable_chain(compute_units_consumed, timings)
485 .and(self.pop())
488 }
489
490 pub fn process_precompile(
492 &mut self,
493 program_id: &Pubkey,
494 instruction_data: &[u8],
495 message_instruction_datas_iter: impl Iterator<Item = &'ix_data [u8]>,
496 ) -> Result<(), InstructionError> {
497 self.push()?;
498 let instruction_datas: Vec<_> = message_instruction_datas_iter.collect();
499 self.environment_config
500 .epoch_stake_callback
501 .process_precompile(program_id, instruction_data, instruction_datas)
502 .map_err(InstructionError::from)
503 .and(self.pop())
504 }
505
506 fn process_executable_chain(
508 &mut self,
509 compute_units_consumed: &mut u64,
510 timings: &mut ExecuteTimings,
511 ) -> Result<(), InstructionError> {
512 let instruction_context = self.transaction_context.get_current_instruction_context()?;
513 let process_executable_chain_time = Measure::start("process_executable_chain_time");
514
515 let builtin_id = {
516 let owner_id = instruction_context.get_program_owner()?;
517 if native_loader::check_id(&owner_id) {
518 *instruction_context.get_program_key()?
519 } else if bpf_loader_deprecated::check_id(&owner_id)
520 || bpf_loader::check_id(&owner_id)
521 || bpf_loader_upgradeable::check_id(&owner_id)
522 || loader_v4::check_id(&owner_id)
523 {
524 owner_id
525 } else {
526 return Err(InstructionError::UnsupportedProgramId);
527 }
528 };
529
530 const ENTRYPOINT_KEY: u32 = 0x71E3CF81;
532 let entry = self
533 .program_cache_for_tx_batch
534 .find(&builtin_id)
535 .ok_or(InstructionError::UnsupportedProgramId)?;
536 let function = match &entry.program {
537 ProgramCacheEntryType::Builtin(program) => program
538 .get_function_registry()
539 .lookup_by_key(ENTRYPOINT_KEY)
540 .map(|(_name, function)| function),
541 _ => None,
542 }
543 .ok_or(InstructionError::UnsupportedProgramId)?;
544
545 let program_id = *instruction_context.get_program_key()?;
546 self.transaction_context
547 .set_return_data(program_id, Vec::new())?;
548 let logger = self.get_log_collector();
549 stable_log::program_invoke(&logger, &program_id, self.get_stack_height());
550 let pre_remaining_units = self.get_remaining();
551 let mock_config = Config::default();
555 let empty_memory_mapping =
556 MemoryMapping::new(Vec::new(), &mock_config, SBPFVersion::V0).unwrap();
557 let mut vm = EbpfVm::new(
558 self.environment_config
559 .program_runtime_environments_for_execution
560 .program_runtime_v2
561 .clone(),
562 SBPFVersion::V0,
563 unsafe { std::mem::transmute::<&mut InvokeContext, &mut InvokeContext>(self) },
565 empty_memory_mapping,
566 0,
567 );
568 vm.invoke_function(function);
569 let result = match vm.program_result {
570 ProgramResult::Ok(_) => {
571 stable_log::program_success(&logger, &program_id);
572 Ok(())
573 }
574 ProgramResult::Err(ref err) => {
575 if let EbpfError::SyscallError(syscall_error) = err {
576 if let Some(instruction_err) = syscall_error.downcast_ref::<InstructionError>()
577 {
578 stable_log::program_failure(&logger, &program_id, instruction_err);
579 Err(instruction_err.clone())
580 } else {
581 stable_log::program_failure(&logger, &program_id, syscall_error);
582 Err(InstructionError::ProgramFailedToComplete)
583 }
584 } else {
585 stable_log::program_failure(&logger, &program_id, err);
586 Err(InstructionError::ProgramFailedToComplete)
587 }
588 }
589 };
590 let post_remaining_units = self.get_remaining();
591 *compute_units_consumed = pre_remaining_units.saturating_sub(post_remaining_units);
592
593 if builtin_id == program_id && result.is_ok() && *compute_units_consumed == 0 {
594 return Err(InstructionError::BuiltinProgramsMustConsumeComputeUnits);
595 }
596
597 timings
598 .execute_accessories
599 .process_instructions
600 .process_executable_chain_us += process_executable_chain_time.end_as_us();
601 result
602 }
603
604 pub fn get_log_collector(&self) -> Option<Rc<RefCell<LogCollector>>> {
606 self.log_collector.clone()
607 }
608
609 pub fn consume_checked(&self, amount: u64) -> Result<(), Box<dyn std::error::Error>> {
611 let mut compute_meter = self.compute_meter.borrow_mut();
612 let exceeded = *compute_meter < amount;
613 *compute_meter = compute_meter.saturating_sub(amount);
614 if exceeded {
615 return Err(Box::new(InstructionError::ComputationalBudgetExceeded));
616 }
617 Ok(())
618 }
619
620 pub fn mock_set_remaining(&self, remaining: u64) {
624 *self.compute_meter.borrow_mut() = remaining;
625 }
626
627 pub fn get_compute_budget(&self) -> &SVMTransactionExecutionBudget {
629 &self.compute_budget
630 }
631
632 pub fn get_execution_cost(&self) -> &SVMTransactionExecutionCost {
634 &self.execution_cost
635 }
636
637 pub fn get_feature_set(&self) -> &SVMFeatureSet {
639 self.environment_config.feature_set
640 }
641
642 pub fn get_program_runtime_environments_for_deployment(&self) -> &ProgramRuntimeEnvironments {
643 self.environment_config
644 .program_runtime_environments_for_deployment
645 }
646
647 pub fn is_stake_raise_minimum_delegation_to_1_sol_active(&self) -> bool {
648 self.environment_config
649 .feature_set
650 .stake_raise_minimum_delegation_to_1_sol
651 }
652
653 pub fn is_deprecate_legacy_vote_ixs_active(&self) -> bool {
654 self.environment_config
655 .feature_set
656 .deprecate_legacy_vote_ixs
657 }
658
659 pub fn get_sysvar_cache(&self) -> &SysvarCache {
661 self.environment_config.sysvar_cache
662 }
663
664 pub fn get_epoch_stake(&self) -> u64 {
666 self.environment_config
667 .epoch_stake_callback
668 .get_epoch_stake()
669 }
670
671 pub fn get_epoch_stake_for_vote_account(&self, pubkey: &'a Pubkey) -> u64 {
673 self.environment_config
674 .epoch_stake_callback
675 .get_epoch_stake_for_vote_account(pubkey)
676 }
677
678 pub fn is_precompile(&self, pubkey: &Pubkey) -> bool {
679 self.environment_config
680 .epoch_stake_callback
681 .is_precompile(pubkey)
682 }
683
684 pub fn get_check_aligned(&self) -> bool {
686 self.transaction_context
687 .get_current_instruction_context()
688 .and_then(|instruction_context| {
689 let owner_id = instruction_context.get_program_owner();
690 debug_assert!(owner_id.is_ok());
691 owner_id
692 })
693 .map(|owner_key| owner_key != bpf_loader_deprecated::id())
694 .unwrap_or(true)
695 }
696
697 pub fn set_syscall_context(
699 &mut self,
700 syscall_context: SyscallContext,
701 ) -> Result<(), InstructionError> {
702 *self
703 .syscall_context
704 .last_mut()
705 .ok_or(InstructionError::CallDepth)? = Some(syscall_context);
706 Ok(())
707 }
708
709 pub fn get_syscall_context(&self) -> Result<&SyscallContext, InstructionError> {
711 self.syscall_context
712 .last()
713 .and_then(std::option::Option::as_ref)
714 .ok_or(InstructionError::CallDepth)
715 }
716
717 pub fn get_syscall_context_mut(&mut self) -> Result<&mut SyscallContext, InstructionError> {
719 self.syscall_context
720 .last_mut()
721 .and_then(|syscall_context| syscall_context.as_mut())
722 .ok_or(InstructionError::CallDepth)
723 }
724
725 pub fn insert_register_trace(&mut self, register_trace: Vec<[u64; 12]>) {
727 if register_trace.is_empty() {
728 return;
729 }
730 let Ok(instruction_context) = self.transaction_context.get_current_instruction_context()
731 else {
732 return;
733 };
734 self.register_traces
735 .push((instruction_context.get_index_in_trace(), register_trace));
736 }
737
738 pub fn iterate_vm_traces(
740 &self,
741 callback: &dyn Fn(InstructionContext, &Executable, RegisterTrace),
742 ) {
743 for (index_in_trace, register_trace) in &self.register_traces {
744 let Ok(instruction_context) = self
745 .transaction_context
746 .get_instruction_context_at_index_in_trace(*index_in_trace)
747 else {
748 continue;
749 };
750 let Ok(program_id) = instruction_context.get_program_key() else {
751 continue;
752 };
753 let Some(entry) = self.program_cache_for_tx_batch.find(program_id) else {
754 continue;
755 };
756 let ProgramCacheEntryType::Loaded(ref executable) = entry.program else {
757 continue;
758 };
759 callback(instruction_context, executable, register_trace.as_slice());
760 }
761 }
762}
763
764#[macro_export]
765macro_rules! with_mock_invoke_context_with_feature_set {
766 (
767 $invoke_context:ident,
768 $transaction_context:ident,
769 $feature_set:ident,
770 $transaction_accounts:expr $(,)?
771 ) => {
772 use {
773 solana_svm_callback::InvokeContextCallback,
774 solana_svm_log_collector::LogCollector,
775 $crate::{
776 __private::{Hash, ReadableAccount, Rent, TransactionContext},
777 execution_budget::{SVMTransactionExecutionBudget, SVMTransactionExecutionCost},
778 invoke_context::{EnvironmentConfig, InvokeContext},
779 loaded_programs::{ProgramCacheForTxBatch, ProgramRuntimeEnvironments},
780 sysvar_cache::SysvarCache,
781 },
782 };
783
784 struct MockInvokeContextCallback {}
785 impl InvokeContextCallback for MockInvokeContextCallback {}
786
787 let compute_budget = SVMTransactionExecutionBudget::new_with_defaults(
788 $feature_set.raise_cpi_nesting_limit_to_8,
789 );
790 let mut $transaction_context = TransactionContext::new(
791 $transaction_accounts,
792 Rent::default(),
793 compute_budget.max_instruction_stack_depth,
794 compute_budget.max_instruction_trace_length,
795 );
796 let mut sysvar_cache = SysvarCache::default();
797 sysvar_cache.fill_missing_entries(|pubkey, callback| {
798 for index in 0..$transaction_context.get_number_of_accounts() {
799 if $transaction_context
800 .get_key_of_account_at_index(index)
801 .unwrap()
802 == pubkey
803 {
804 callback(
805 $transaction_context
806 .accounts()
807 .try_borrow(index)
808 .unwrap()
809 .data(),
810 );
811 }
812 }
813 });
814 let program_runtime_environments = ProgramRuntimeEnvironments::default();
815 let environment_config = EnvironmentConfig::new(
816 Hash::default(),
817 0,
818 &MockInvokeContextCallback {},
819 $feature_set,
820 &program_runtime_environments,
821 &program_runtime_environments,
822 &sysvar_cache,
823 );
824 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
825 let mut $invoke_context = InvokeContext::new(
826 &mut $transaction_context,
827 &mut program_cache_for_tx_batch,
828 environment_config,
829 Some(LogCollector::new_ref()),
830 compute_budget,
831 SVMTransactionExecutionCost::new_with_defaults(
832 $feature_set.increase_cpi_account_info_limit,
833 ),
834 );
835 };
836}
837
838#[macro_export]
839macro_rules! with_mock_invoke_context {
840 (
841 $invoke_context:ident,
842 $transaction_context:ident,
843 $transaction_accounts:expr $(,)?
844 ) => {
845 use $crate::with_mock_invoke_context_with_feature_set;
846 let feature_set = &solana_svm_feature_set::SVMFeatureSet::default();
847 with_mock_invoke_context_with_feature_set!(
848 $invoke_context,
849 $transaction_context,
850 feature_set,
851 $transaction_accounts
852 )
853 };
854}
855
856#[allow(clippy::too_many_arguments)]
857pub fn mock_process_instruction_with_feature_set<
858 F: FnMut(&mut InvokeContext),
859 G: FnMut(&mut InvokeContext),
860>(
861 loader_id: &Pubkey,
862 program_index: Option<IndexOfAccount>,
863 instruction_data: &[u8],
864 mut transaction_accounts: Vec<KeyedAccountSharedData>,
865 instruction_account_metas: Vec<AccountMeta>,
866 expected_result: Result<(), InstructionError>,
867 builtin_function: BuiltinFunctionWithContext,
868 mut pre_adjustments: F,
869 mut post_adjustments: G,
870 feature_set: &SVMFeatureSet,
871) -> Vec<AccountSharedData> {
872 let mut instruction_accounts: Vec<InstructionAccount> =
873 Vec::with_capacity(instruction_account_metas.len());
874 for account_meta in instruction_account_metas.iter() {
875 let index_in_transaction = transaction_accounts
876 .iter()
877 .position(|(key, _account)| *key == account_meta.pubkey)
878 .unwrap_or(transaction_accounts.len())
879 as IndexOfAccount;
880 instruction_accounts.push(InstructionAccount::new(
881 index_in_transaction,
882 account_meta.is_signer,
883 account_meta.is_writable,
884 ));
885 }
886
887 let program_index = if let Some(index) = program_index {
888 index
889 } else {
890 let processor_account = AccountSharedData::new(0, 0, &native_loader::id());
891 transaction_accounts.push((*loader_id, processor_account));
892 transaction_accounts.len().saturating_sub(1) as IndexOfAccount
893 };
894 let pop_epoch_schedule_account = if !transaction_accounts
895 .iter()
896 .any(|(key, _)| *key == sysvar::epoch_schedule::id())
897 {
898 transaction_accounts.push((
899 sysvar::epoch_schedule::id(),
900 create_account_shared_data_for_test(&EpochSchedule::default()),
901 ));
902 true
903 } else {
904 false
905 };
906 with_mock_invoke_context_with_feature_set!(
907 invoke_context,
908 transaction_context,
909 feature_set,
910 transaction_accounts
911 );
912 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
913 program_cache_for_tx_batch.replenish(
914 *loader_id,
915 Arc::new(ProgramCacheEntry::new_builtin(0, 0, builtin_function)),
916 );
917 program_cache_for_tx_batch.set_slot_for_tests(
918 invoke_context
919 .get_sysvar_cache()
920 .get_clock()
921 .map(|clock| clock.slot)
922 .unwrap_or(1),
923 );
924 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
925 pre_adjustments(&mut invoke_context);
926 invoke_context
927 .transaction_context
928 .configure_next_instruction_for_tests(
929 program_index,
930 instruction_accounts,
931 instruction_data.to_vec(),
932 )
933 .unwrap();
934 let result = invoke_context.process_instruction(&mut 0, &mut ExecuteTimings::default());
935 assert_eq!(result, expected_result);
936 post_adjustments(&mut invoke_context);
937 let mut transaction_accounts = transaction_context.deconstruct_without_keys().unwrap();
938 if pop_epoch_schedule_account {
939 transaction_accounts.pop();
940 }
941 transaction_accounts.pop();
942 transaction_accounts
943}
944
945pub fn mock_process_instruction<F: FnMut(&mut InvokeContext), G: FnMut(&mut InvokeContext)>(
946 loader_id: &Pubkey,
947 program_index: Option<IndexOfAccount>,
948 instruction_data: &[u8],
949 transaction_accounts: Vec<KeyedAccountSharedData>,
950 instruction_account_metas: Vec<AccountMeta>,
951 expected_result: Result<(), InstructionError>,
952 builtin_function: BuiltinFunctionWithContext,
953 pre_adjustments: F,
954 post_adjustments: G,
955) -> Vec<AccountSharedData> {
956 mock_process_instruction_with_feature_set(
957 loader_id,
958 program_index,
959 instruction_data,
960 transaction_accounts,
961 instruction_account_metas,
962 expected_result,
963 builtin_function,
964 pre_adjustments,
965 post_adjustments,
966 &SVMFeatureSet::all_enabled(),
967 )
968}
969
970#[cfg(test)]
971mod tests {
972 use {
973 super::*,
974 crate::execution_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
975 serde::{Deserialize, Serialize},
976 solana_account::WritableAccount,
977 solana_instruction::Instruction,
978 solana_keypair::Keypair,
979 solana_rent::Rent,
980 solana_signer::Signer,
981 solana_transaction::{sanitized::SanitizedTransaction, Transaction},
982 solana_transaction_context::MAX_ACCOUNTS_PER_INSTRUCTION,
983 std::collections::HashSet,
984 test_case::test_case,
985 };
986
987 #[derive(Debug, Serialize, Deserialize)]
988 enum MockInstruction {
989 NoopSuccess,
990 NoopFail,
991 ModifyOwned,
992 ModifyNotOwned,
993 ModifyReadonly,
994 UnbalancedPush,
995 UnbalancedPop,
996 ConsumeComputeUnits {
997 compute_units_to_consume: u64,
998 desired_result: Result<(), InstructionError>,
999 },
1000 Resize {
1001 new_len: u64,
1002 },
1003 }
1004
1005 const MOCK_BUILTIN_COMPUTE_UNIT_COST: u64 = 1;
1006
1007 declare_process_instruction!(
1008 MockBuiltin,
1009 MOCK_BUILTIN_COMPUTE_UNIT_COST,
1010 |invoke_context| {
1011 let transaction_context = &invoke_context.transaction_context;
1012 let instruction_context = transaction_context.get_current_instruction_context()?;
1013 let instruction_data = instruction_context.get_instruction_data();
1014 let program_id = instruction_context.get_program_key()?;
1015 let instruction_accounts = (0..4)
1016 .map(|instruction_account_index| {
1017 InstructionAccount::new(instruction_account_index, false, false)
1018 })
1019 .collect::<Vec<_>>();
1020 assert_eq!(
1021 program_id,
1022 instruction_context
1023 .try_borrow_instruction_account(0)?
1024 .get_owner()
1025 );
1026 assert_ne!(
1027 instruction_context
1028 .try_borrow_instruction_account(1)?
1029 .get_owner(),
1030 instruction_context.get_key_of_instruction_account(0)?
1031 );
1032
1033 if let Ok(instruction) = bincode::deserialize(instruction_data) {
1034 match instruction {
1035 MockInstruction::NoopSuccess => (),
1036 MockInstruction::NoopFail => return Err(InstructionError::GenericError),
1037 MockInstruction::ModifyOwned => instruction_context
1038 .try_borrow_instruction_account(0)?
1039 .set_data_from_slice(&[1])?,
1040 MockInstruction::ModifyNotOwned => instruction_context
1041 .try_borrow_instruction_account(1)?
1042 .set_data_from_slice(&[1])?,
1043 MockInstruction::ModifyReadonly => instruction_context
1044 .try_borrow_instruction_account(2)?
1045 .set_data_from_slice(&[1])?,
1046 MockInstruction::UnbalancedPush => {
1047 instruction_context
1048 .try_borrow_instruction_account(0)?
1049 .checked_add_lamports(1)?;
1050 let program_id = *transaction_context.get_key_of_account_at_index(3)?;
1051 let metas = vec![
1052 AccountMeta::new_readonly(
1053 *transaction_context.get_key_of_account_at_index(0)?,
1054 false,
1055 ),
1056 AccountMeta::new_readonly(
1057 *transaction_context.get_key_of_account_at_index(1)?,
1058 false,
1059 ),
1060 ];
1061 let inner_instruction = Instruction::new_with_bincode(
1062 program_id,
1063 &MockInstruction::NoopSuccess,
1064 metas,
1065 );
1066 invoke_context
1067 .transaction_context
1068 .configure_next_instruction_for_tests(3, instruction_accounts, vec![])
1069 .unwrap();
1070 let result = invoke_context.push();
1071 assert_eq!(result, Err(InstructionError::UnbalancedInstruction));
1072 result?;
1073 invoke_context
1074 .native_invoke(inner_instruction, &[])
1075 .and(invoke_context.pop())?;
1076 }
1077 MockInstruction::UnbalancedPop => instruction_context
1078 .try_borrow_instruction_account(0)?
1079 .checked_add_lamports(1)?,
1080 MockInstruction::ConsumeComputeUnits {
1081 compute_units_to_consume,
1082 desired_result,
1083 } => {
1084 invoke_context
1085 .consume_checked(compute_units_to_consume)
1086 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
1087 return desired_result;
1088 }
1089 MockInstruction::Resize { new_len } => instruction_context
1090 .try_borrow_instruction_account(0)?
1091 .set_data_from_slice(&vec![0; new_len as usize])?,
1092 }
1093 } else {
1094 return Err(InstructionError::InvalidInstructionData);
1095 }
1096 Ok(())
1097 }
1098 );
1099
1100 #[test_case(false; "SIMD-0268 disabled")]
1101 #[test_case(true; "SIMD-0268 enabled")]
1102 fn test_instruction_stack_height(simd_0268_active: bool) {
1103 let one_more_than_max_depth =
1104 SVMTransactionExecutionBudget::new_with_defaults(simd_0268_active)
1105 .max_instruction_stack_depth
1106 .saturating_add(1);
1107 let mut invoke_stack = vec![];
1108 let mut transaction_accounts = vec![];
1109 let mut instruction_accounts = vec![];
1110 for index in 0..one_more_than_max_depth {
1111 invoke_stack.push(solana_pubkey::new_rand());
1112 transaction_accounts.push((
1113 solana_pubkey::new_rand(),
1114 AccountSharedData::new(index as u64, 1, invoke_stack.get(index).unwrap()),
1115 ));
1116 instruction_accounts.push(InstructionAccount::new(
1117 index as IndexOfAccount,
1118 false,
1119 true,
1120 ));
1121 }
1122 for (index, program_id) in invoke_stack.iter().enumerate() {
1123 transaction_accounts.push((
1124 *program_id,
1125 AccountSharedData::new(1, 1, &solana_pubkey::Pubkey::default()),
1126 ));
1127 instruction_accounts.push(InstructionAccount::new(
1128 index as IndexOfAccount,
1129 false,
1130 false,
1131 ));
1132 }
1133 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1134
1135 let mut depth_reached: usize = 0;
1137 for _ in 0..invoke_stack.len() {
1138 invoke_context
1139 .transaction_context
1140 .configure_next_instruction_for_tests(
1141 one_more_than_max_depth.saturating_add(depth_reached) as IndexOfAccount,
1142 instruction_accounts.clone(),
1143 vec![],
1144 )
1145 .unwrap();
1146 if Err(InstructionError::CallDepth) == invoke_context.push() {
1147 break;
1148 }
1149 depth_reached = depth_reached.saturating_add(1);
1150 }
1151 assert_ne!(depth_reached, 0);
1152 assert!(depth_reached < one_more_than_max_depth);
1153 }
1154
1155 #[test]
1156 fn test_max_instruction_trace_length() {
1157 const MAX_INSTRUCTIONS: usize = 8;
1158 let mut transaction_context = TransactionContext::new(
1159 vec![(
1160 Pubkey::new_unique(),
1161 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1162 )],
1163 Rent::default(),
1164 1,
1165 MAX_INSTRUCTIONS,
1166 );
1167 for _ in 0..MAX_INSTRUCTIONS {
1168 transaction_context.push().unwrap();
1169 transaction_context
1170 .configure_next_instruction_for_tests(
1171 0,
1172 vec![InstructionAccount::new(0, false, false)],
1173 vec![],
1174 )
1175 .unwrap();
1176 transaction_context.pop().unwrap();
1177 }
1178 assert_eq!(
1179 transaction_context.push(),
1180 Err(InstructionError::MaxInstructionTraceLengthExceeded)
1181 );
1182 }
1183
1184 #[test_case(MockInstruction::NoopSuccess, Ok(()); "NoopSuccess")]
1185 #[test_case(MockInstruction::NoopFail, Err(InstructionError::GenericError); "NoopFail")]
1186 #[test_case(MockInstruction::ModifyOwned, Ok(()); "ModifyOwned")]
1187 #[test_case(MockInstruction::ModifyNotOwned, Err(InstructionError::ExternalAccountDataModified); "ModifyNotOwned")]
1188 #[test_case(MockInstruction::ModifyReadonly, Err(InstructionError::ReadonlyDataModified); "ModifyReadonly")]
1189 #[test_case(MockInstruction::UnbalancedPush, Err(InstructionError::UnbalancedInstruction); "UnbalancedPush")]
1190 #[test_case(MockInstruction::UnbalancedPop, Err(InstructionError::UnbalancedInstruction); "UnbalancedPop")]
1191 fn test_process_instruction_account_modifications(
1192 instruction: MockInstruction,
1193 expected_result: Result<(), InstructionError>,
1194 ) {
1195 let callee_program_id = solana_pubkey::new_rand();
1196 let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1197 let not_owned_account = AccountSharedData::new(84, 1, &solana_pubkey::new_rand());
1198 let readonly_account = AccountSharedData::new(168, 1, &solana_pubkey::new_rand());
1199 let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1200 let mut program_account = AccountSharedData::new(1, 1, &native_loader::id());
1201 program_account.set_executable(true);
1202 let transaction_accounts = vec![
1203 (solana_pubkey::new_rand(), owned_account),
1204 (solana_pubkey::new_rand(), not_owned_account),
1205 (solana_pubkey::new_rand(), readonly_account),
1206 (callee_program_id, program_account),
1207 (solana_pubkey::new_rand(), loader_account),
1208 ];
1209 let metas = vec![
1210 AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1211 AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1212 AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1213 ];
1214 let instruction_accounts = (0..4)
1215 .map(|instruction_account_index| {
1216 InstructionAccount::new(
1217 instruction_account_index,
1218 false,
1219 instruction_account_index < 2,
1220 )
1221 })
1222 .collect::<Vec<_>>();
1223 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1224 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1225 program_cache_for_tx_batch.replenish(
1226 callee_program_id,
1227 Arc::new(ProgramCacheEntry::new_builtin(0, 1, MockBuiltin::vm)),
1228 );
1229 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1230
1231 invoke_context
1233 .transaction_context
1234 .configure_next_instruction_for_tests(4, instruction_accounts, vec![])
1235 .unwrap();
1236 invoke_context.push().unwrap();
1237 let inner_instruction =
1238 Instruction::new_with_bincode(callee_program_id, &instruction, metas.clone());
1239 let result = invoke_context
1240 .native_invoke(inner_instruction, &[])
1241 .and(invoke_context.pop());
1242 assert_eq!(result, expected_result);
1243 }
1244
1245 #[test_case(Ok(()); "Ok")]
1246 #[test_case(Err(InstructionError::GenericError); "GenericError")]
1247 fn test_process_instruction_compute_unit_consumption(
1248 expected_result: Result<(), InstructionError>,
1249 ) {
1250 let callee_program_id = solana_pubkey::new_rand();
1251 let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1252 let not_owned_account = AccountSharedData::new(84, 1, &solana_pubkey::new_rand());
1253 let readonly_account = AccountSharedData::new(168, 1, &solana_pubkey::new_rand());
1254 let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1255 let mut program_account = AccountSharedData::new(1, 1, &native_loader::id());
1256 program_account.set_executable(true);
1257 let transaction_accounts = vec![
1258 (solana_pubkey::new_rand(), owned_account),
1259 (solana_pubkey::new_rand(), not_owned_account),
1260 (solana_pubkey::new_rand(), readonly_account),
1261 (callee_program_id, program_account),
1262 (solana_pubkey::new_rand(), loader_account),
1263 ];
1264 let metas = vec![
1265 AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1266 AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1267 AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1268 ];
1269 let instruction_accounts = (0..4)
1270 .map(|instruction_account_index| {
1271 InstructionAccount::new(
1272 instruction_account_index,
1273 false,
1274 instruction_account_index < 2,
1275 )
1276 })
1277 .collect::<Vec<_>>();
1278 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1279 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1280 program_cache_for_tx_batch.replenish(
1281 callee_program_id,
1282 Arc::new(ProgramCacheEntry::new_builtin(0, 1, MockBuiltin::vm)),
1283 );
1284 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1285
1286 let compute_units_to_consume = 10;
1288 invoke_context
1289 .transaction_context
1290 .configure_next_instruction_for_tests(4, instruction_accounts, vec![])
1291 .unwrap();
1292 invoke_context.push().unwrap();
1293 let inner_instruction = Instruction::new_with_bincode(
1294 callee_program_id,
1295 &MockInstruction::ConsumeComputeUnits {
1296 compute_units_to_consume,
1297 desired_result: expected_result.clone(),
1298 },
1299 metas.clone(),
1300 );
1301 invoke_context
1302 .prepare_next_instruction(inner_instruction, &[])
1303 .unwrap();
1304
1305 let mut compute_units_consumed = 0;
1306 let result = invoke_context
1307 .process_instruction(&mut compute_units_consumed, &mut ExecuteTimings::default());
1308
1309 assert!(compute_units_consumed > 0);
1313 assert_eq!(
1314 compute_units_consumed,
1315 compute_units_to_consume.saturating_add(MOCK_BUILTIN_COMPUTE_UNIT_COST),
1316 );
1317 assert_eq!(result, expected_result);
1318
1319 invoke_context.pop().unwrap();
1320 }
1321
1322 #[test]
1323 fn test_invoke_context_compute_budget() {
1324 let transaction_accounts = vec![(solana_pubkey::new_rand(), AccountSharedData::default())];
1325 let execution_budget = SVMTransactionExecutionBudget {
1326 compute_unit_limit: u64::from(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT),
1327 ..SVMTransactionExecutionBudget::default()
1328 };
1329
1330 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1331 invoke_context.compute_budget = execution_budget;
1332
1333 invoke_context
1334 .transaction_context
1335 .configure_next_instruction_for_tests(0, vec![], vec![])
1336 .unwrap();
1337 invoke_context.push().unwrap();
1338 assert_eq!(*invoke_context.get_compute_budget(), execution_budget);
1339 invoke_context.pop().unwrap();
1340 }
1341
1342 #[test_case(0; "Resize the account to *the same size*, so not consuming any additional size")]
1343 #[test_case(1; "Resize the account larger")]
1344 #[test_case(-1; "Resize the account smaller")]
1345 fn test_process_instruction_accounts_resize_delta(resize_delta: i64) {
1346 let program_key = Pubkey::new_unique();
1347 let user_account_data_len = 123u64;
1348 let user_account =
1349 AccountSharedData::new(100, user_account_data_len as usize, &program_key);
1350 let dummy_account = AccountSharedData::new(10, 0, &program_key);
1351 let mut program_account = AccountSharedData::new(500, 500, &native_loader::id());
1352 program_account.set_executable(true);
1353 let transaction_accounts = vec![
1354 (Pubkey::new_unique(), user_account),
1355 (Pubkey::new_unique(), dummy_account),
1356 (program_key, program_account),
1357 ];
1358 let instruction_accounts = vec![
1359 InstructionAccount::new(0, false, true),
1360 InstructionAccount::new(1, false, false),
1361 ];
1362 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1363 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1364 program_cache_for_tx_batch.replenish(
1365 program_key,
1366 Arc::new(ProgramCacheEntry::new_builtin(0, 0, MockBuiltin::vm)),
1367 );
1368 invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1369
1370 let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
1371 let instruction_data = bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1372
1373 invoke_context
1374 .transaction_context
1375 .configure_next_instruction_for_tests(2, instruction_accounts, instruction_data)
1376 .unwrap();
1377 let result = invoke_context.process_instruction(&mut 0, &mut ExecuteTimings::default());
1378
1379 assert!(result.is_ok());
1380 assert_eq!(
1381 invoke_context.transaction_context.accounts().resize_delta(),
1382 resize_delta
1383 );
1384 }
1385
1386 #[test]
1387 fn test_prepare_instruction_maximum_accounts() {
1388 const MAX_ACCOUNTS_REFERENCED: usize = u16::MAX as usize;
1389 let mut transaction_accounts: Vec<KeyedAccountSharedData> =
1390 Vec::with_capacity(MAX_ACCOUNTS_PER_TRANSACTION);
1391 let mut account_metas: Vec<AccountMeta> = Vec::with_capacity(MAX_ACCOUNTS_REFERENCED);
1392
1393 let fee_payer = Keypair::new();
1395 transaction_accounts.push((
1396 fee_payer.pubkey(),
1397 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1398 ));
1399 account_metas.push(AccountMeta::new(fee_payer.pubkey(), true));
1400
1401 let program_id = Pubkey::new_unique();
1402 let mut program_account = AccountSharedData::new(1, 1, &Pubkey::new_unique());
1403 program_account.set_executable(true);
1404 transaction_accounts.push((program_id, program_account));
1405 account_metas.push(AccountMeta::new_readonly(program_id, false));
1406
1407 for i in 2..MAX_ACCOUNTS_REFERENCED {
1408 if i < MAX_ACCOUNTS_PER_TRANSACTION {
1410 let key = Pubkey::new_unique();
1411 transaction_accounts
1412 .push((key, AccountSharedData::new(1, 1, &Pubkey::new_unique())));
1413 account_metas.push(AccountMeta::new_readonly(key, false));
1414 } else {
1415 let repeated_key = transaction_accounts
1416 .get(i % MAX_ACCOUNTS_PER_TRANSACTION)
1417 .unwrap()
1418 .0;
1419 account_metas.push(AccountMeta::new_readonly(repeated_key, false));
1420 }
1421 }
1422
1423 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1424
1425 let instruction_1 = Instruction::new_with_bytes(program_id, &[20], account_metas.clone());
1426
1427 let instruction_2 = Instruction::new_with_bytes(
1428 program_id,
1429 &[20],
1430 account_metas.iter().rev().cloned().collect(),
1431 );
1432
1433 let transaction = Transaction::new_with_payer(
1434 &[instruction_1.clone(), instruction_2.clone()],
1435 Some(&fee_payer.pubkey()),
1436 );
1437
1438 let sanitized =
1439 SanitizedTransaction::try_from_legacy_transaction(transaction, &HashSet::new())
1440 .unwrap();
1441
1442 fn test_case_1(invoke_context: &InvokeContext) {
1443 let instruction_context = invoke_context
1444 .transaction_context
1445 .get_next_instruction_context()
1446 .unwrap();
1447 for index_in_instruction in 0..MAX_ACCOUNTS_REFERENCED as IndexOfAccount {
1448 let index_in_transaction = instruction_context
1449 .get_index_of_instruction_account_in_transaction(index_in_instruction)
1450 .unwrap();
1451 let other_ix_index = instruction_context
1452 .get_index_of_account_in_instruction(index_in_transaction)
1453 .unwrap();
1454 if (index_in_instruction as usize) < MAX_ACCOUNTS_PER_TRANSACTION {
1455 assert_eq!(index_in_instruction, index_in_transaction);
1456 assert_eq!(index_in_instruction, other_ix_index);
1457 } else {
1458 assert_eq!(
1459 index_in_instruction as usize % MAX_ACCOUNTS_PER_TRANSACTION,
1460 index_in_transaction as usize
1461 );
1462 assert_eq!(
1463 index_in_instruction as usize % MAX_ACCOUNTS_PER_TRANSACTION,
1464 other_ix_index as usize
1465 );
1466 }
1467 }
1468 }
1469
1470 fn test_case_2(invoke_context: &InvokeContext) {
1471 let instruction_context = invoke_context
1472 .transaction_context
1473 .get_next_instruction_context()
1474 .unwrap();
1475 for index_in_instruction in 0..MAX_ACCOUNTS_REFERENCED as IndexOfAccount {
1476 let index_in_transaction = instruction_context
1477 .get_index_of_instruction_account_in_transaction(index_in_instruction)
1478 .unwrap();
1479 let other_ix_index = instruction_context
1480 .get_index_of_account_in_instruction(index_in_transaction)
1481 .unwrap();
1482 assert_eq!(
1483 index_in_transaction,
1484 (MAX_ACCOUNTS_REFERENCED as u16)
1485 .saturating_sub(index_in_instruction)
1486 .saturating_sub(1)
1487 .overflowing_rem(MAX_ACCOUNTS_PER_TRANSACTION as u16)
1488 .0
1489 );
1490 if (index_in_instruction as usize) < MAX_ACCOUNTS_PER_TRANSACTION {
1491 assert_eq!(index_in_instruction, other_ix_index);
1492 } else {
1493 assert_eq!(
1494 index_in_instruction as usize % MAX_ACCOUNTS_PER_TRANSACTION,
1495 other_ix_index as usize
1496 );
1497 }
1498 }
1499 }
1500
1501 let svm_instruction =
1502 SVMInstruction::from(sanitized.message().instructions().first().unwrap());
1503 invoke_context
1504 .prepare_next_top_level_instruction(
1505 &sanitized,
1506 &svm_instruction,
1507 90,
1508 svm_instruction.data,
1509 )
1510 .unwrap();
1511
1512 test_case_1(&invoke_context);
1513
1514 invoke_context.transaction_context.push().unwrap();
1515 let svm_instruction =
1516 SVMInstruction::from(sanitized.message().instructions().get(1).unwrap());
1517 invoke_context
1518 .prepare_next_top_level_instruction(
1519 &sanitized,
1520 &svm_instruction,
1521 90,
1522 svm_instruction.data,
1523 )
1524 .unwrap();
1525
1526 test_case_2(&invoke_context);
1527
1528 invoke_context.transaction_context.push().unwrap();
1529 invoke_context
1530 .prepare_next_instruction(instruction_1, &[fee_payer.pubkey()])
1531 .unwrap();
1532 test_case_1(&invoke_context);
1533
1534 invoke_context.transaction_context.push().unwrap();
1535 invoke_context
1536 .prepare_next_instruction(instruction_2, &[fee_payer.pubkey()])
1537 .unwrap();
1538 test_case_2(&invoke_context);
1539 }
1540
1541 #[test]
1542 fn test_duplicated_accounts() {
1543 let mut transaction_accounts: Vec<KeyedAccountSharedData> =
1544 Vec::with_capacity(MAX_ACCOUNTS_PER_TRANSACTION);
1545 let mut account_metas: Vec<AccountMeta> =
1546 Vec::with_capacity(MAX_ACCOUNTS_PER_INSTRUCTION.saturating_sub(1));
1547
1548 let fee_payer = Keypair::new();
1550 transaction_accounts.push((
1551 fee_payer.pubkey(),
1552 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1553 ));
1554 account_metas.push(AccountMeta::new(fee_payer.pubkey(), true));
1555
1556 let program_id = Pubkey::new_unique();
1557 let mut program_account = AccountSharedData::new(1, 1, &Pubkey::new_unique());
1558 program_account.set_executable(true);
1559 transaction_accounts.push((program_id, program_account));
1560 account_metas.push(AccountMeta::new_readonly(program_id, false));
1561
1562 for i in 2..account_metas.capacity() {
1563 if i % 2 == 0 {
1564 let key = Pubkey::new_unique();
1565 transaction_accounts
1566 .push((key, AccountSharedData::new(1, 1, &Pubkey::new_unique())));
1567 account_metas.push(AccountMeta::new_readonly(key, false));
1568 } else {
1569 let last_key = transaction_accounts.last().unwrap().0;
1570 account_metas.push(AccountMeta::new_readonly(last_key, false));
1571 }
1572 }
1573
1574 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1575
1576 let instruction = Instruction::new_with_bytes(program_id, &[20], account_metas.clone());
1577
1578 let transaction = Transaction::new_with_payer(&[instruction], Some(&fee_payer.pubkey()));
1579
1580 let sanitized =
1581 SanitizedTransaction::try_from_legacy_transaction(transaction, &HashSet::new())
1582 .unwrap();
1583 let svm_instruction =
1584 SVMInstruction::from(sanitized.message().instructions().first().unwrap());
1585
1586 invoke_context
1587 .prepare_next_top_level_instruction(
1588 &sanitized,
1589 &svm_instruction,
1590 90,
1591 svm_instruction.data,
1592 )
1593 .unwrap();
1594
1595 {
1596 let instruction_context = invoke_context
1597 .transaction_context
1598 .get_next_instruction_context()
1599 .unwrap();
1600 for index_in_instruction in 2..account_metas.len() as IndexOfAccount {
1601 let is_duplicate = instruction_context
1602 .is_instruction_account_duplicate(index_in_instruction)
1603 .unwrap();
1604 if index_in_instruction % 2 == 0 {
1605 assert!(is_duplicate.is_none());
1606 } else {
1607 assert_eq!(is_duplicate, Some(index_in_instruction.saturating_sub(1)));
1608 }
1609 }
1610 }
1611
1612 invoke_context.transaction_context.push().unwrap();
1613
1614 let instruction = Instruction::new_with_bytes(
1615 program_id,
1616 &[20],
1617 account_metas.iter().cloned().rev().collect(),
1618 );
1619
1620 invoke_context
1621 .prepare_next_instruction(instruction, &[fee_payer.pubkey()])
1622 .unwrap();
1623 let instruction_context = invoke_context
1624 .transaction_context
1625 .get_next_instruction_context()
1626 .unwrap();
1627 for index_in_instruction in 2..account_metas.len().saturating_sub(1) as u16 {
1628 let is_duplicate = instruction_context
1629 .is_instruction_account_duplicate(index_in_instruction)
1630 .unwrap();
1631 if index_in_instruction % 2 == 0 {
1632 assert!(is_duplicate.is_none());
1633 } else {
1634 assert_eq!(is_duplicate, Some(index_in_instruction.saturating_sub(1)));
1635 }
1636 }
1637 }
1638}