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