atlas_program_runtime/
invoke_context.rs

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
48// Type alias for compatibility - this type was removed from atlas-transaction-context
49pub 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/// Adapter so we can unify the interfaces of built-in programs and syscalls
56#[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        // 1 to 1 instruction to compute unit mapping
96        // ignore overflow, Ebpf will bail if exceeded
97        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        // Trace implementation - store register traces if needed
107        // This is called by the VM to record register state
108    }
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
192/// Main pipeline from runtime to program execution.
193pub struct InvokeContext<'a> {
194    /// Information about the currently executing transaction.
195    pub transaction_context: &'a mut TransactionContext,
196    /// The local program cache for the transaction batch.
197    pub program_cache_for_tx_batch: &'a mut ProgramCacheForTxBatch,
198    /// Runtime configurations used to provision the invocation environment.
199    pub environment_config: EnvironmentConfig<'a>,
200    /// The compute budget for the current invocation.
201    compute_budget: SVMTransactionExecutionBudget,
202    /// The compute cost for the current invocation.
203    execution_cost: SVMTransactionExecutionCost,
204    /// Instruction compute meter, for tracking compute units consumed against
205    /// the designated compute budget during program execution.
206    compute_meter: RefCell<u64>,
207    log_collector: Option<Rc<RefCell<LogCollector>>>,
208    /// Latest measurement not yet accumulated in [ExecuteDetailsTimings::execute_us]
209    pub execute_time: Option<Measure>,
210    pub timings: ExecuteDetailsTimings,
211    pub syscall_context: Vec<Option<SyscallContext>>,
212    /// Pairs of index in TX instruction trace and VM register trace
213    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    /// Push a stack frame onto the invocation stack
242    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                // Reentrancy not allowed unless caller is calling itself
268                return Err(InstructionError::ReentrancyNotAllowed);
269            }
270        }
271
272        self.syscall_context.push(None);
273        self.transaction_context.push()
274    }
275
276    /// Pop a stack frame from the invocation stack
277    fn pop(&mut self) -> Result<(), InstructionError> {
278        self.syscall_context.pop();
279        self.transaction_context.pop()
280    }
281
282    /// Current height of the invocation stack, top level instructions are height
283    /// `atlas_instruction::TRANSACTION_LEVEL_STACK_HEIGHT`
284    pub fn get_stack_height(&self) -> usize {
285        self.transaction_context.get_instruction_stack_height()
286    }
287
288    /// Entrypoint for a cross-program invocation from a builtin program
289    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    /// Helper to prepare for process_instruction() when the instruction is not a top level one,
301    /// and depends on `AccountMeta`s
302    pub fn prepare_next_instruction(
303        &mut self,
304        instruction: Instruction,
305        signers: &[Pubkey],
306    ) -> Result<(), InstructionError> {
307        // We reference accounts by an u8 index, so we have a total of 256 accounts.
308        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        // This code block is necessary to restrict the scope of the immutable borrow of
313        // transaction context (the `instruction_context` variable). At the end of this
314        // function, we must borrow it again as mutable.
315        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                    // This account is repeated, so there is no need to check for permissions
381                    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                // This unwrap is safe because instruction.accounts.len() == instruction_accounts.len()
389                let account_key = &instruction.accounts.get(current_index).unwrap().pubkey;
390                // get_index_of_account_in_instruction has already checked if the index is valid.
391                let caller_instruction_account = instruction_context
392                    .instruction_accounts()
393                    .get(index_in_caller as usize)
394                    .unwrap();
395
396                // Readonly in caller cannot become writable in callee
397                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                // To be signed in the callee,
403                // it must be either signed in the caller or by the program
404                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            // Find and validate executables / program accounts
413            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            // We first check if the account exists in the transaction, and then see if it is part
421            // of the instruction.
422            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            // SAFETY: This unwrap is safe, because we checked the index in instruction in the
430            // previous if-condition.
431            program_account_index_in_transaction.unwrap()
432        };
433
434        // API changed from Vec<u16> to Vec<u8> for deduplication map
435        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    /// Helper to prepare for process_instruction()/process_precompile() when the instruction is
449    /// a top level one
450    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        // We reference accounts by an u8 index, so we have a total of 256 accounts.
458        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        // API changed from Vec<u16> to Vec<u8> for deduplication map
482        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    /// Processes an instruction and returns how many compute units were used
496    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            // MUST pop if and only if `push` succeeded, independent of `result`.
505            // Thus, the `.and()` instead of an `.and_then()`.
506            .and(self.pop())
507    }
508
509    /// Processes a precompile instruction
510    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    /// Calls the instruction's program entrypoint method
526    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        // The Murmur3 hash value (used by RBPF) of the string "entrypoint"
550        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        // In program-runtime v2 we will create this VM instance only once per transaction.
571        // `program_runtime_environment_v2.get_config()` will be used instead of `mock_config`.
572        // For now, only built-ins are invoked from here, so the VM and its Config are irrelevant.
573        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            // Removes lifetime tracking
583            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    /// Get this invocation's LogCollector
624    pub fn get_log_collector(&self) -> Option<Rc<RefCell<LogCollector>>> {
625        self.log_collector.clone()
626    }
627
628    /// Consume compute units
629    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    /// Set compute units
640    ///
641    /// Only use for tests and benchmarks
642    pub fn mock_set_remaining(&self, remaining: u64) {
643        *self.compute_meter.borrow_mut() = remaining;
644    }
645
646    /// Get this invocation's compute budget
647    pub fn get_compute_budget(&self) -> &SVMTransactionExecutionBudget {
648        &self.compute_budget
649    }
650
651    /// Get this invocation's compute budget
652    pub fn get_execution_cost(&self) -> &SVMTransactionExecutionCost {
653        &self.execution_cost
654    }
655
656    /// Get the current feature set.
657    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    /// Get cached sysvars
679    pub fn get_sysvar_cache(&self) -> &SysvarCache {
680        self.environment_config.sysvar_cache
681    }
682
683    /// Get cached epoch total stake.
684    pub fn get_epoch_stake(&self) -> u64 {
685        self.environment_config
686            .epoch_stake_callback
687            .get_epoch_stake()
688    }
689
690    /// Get cached stake for the epoch vote account.
691    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    // Should alignment be enforced during user pointer translation
704    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    // Set this instruction syscall context
717    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    // Get this instruction's SyscallContext
729    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    // Get this instruction's SyscallContext
737    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    /// Insert a VM register trace
745    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        // API changed: get_index_in_trace() removed, use trace_length - 1 instead
754        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    /// Iterates over all VM register traces (including CPI)
761    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, // simd_0339_active - using false for tests
855            ),
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        // Check call depth increases and has a limit
1158        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        // Account modification tests
1254        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        // Compute unit consumption tests
1309        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        // Because the instruction had compute cost > 0, then regardless of the execution result,
1332        // the number of compute units consumed should be a non-default which is something greater
1333        // than zero.
1334        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        // Fee-payer
1416        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            // Let's reference 256 unique accounts, and the rest is repeated.
1431            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        // Fee-payer
1571        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}