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