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