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},
16    solana_log_collector::{ic_msg, LogCollector},
17    solana_measure::measure::Measure,
18    solana_pubkey::Pubkey,
19    solana_sbpf::{
20        ebpf::MM_HEAP_START,
21        error::{EbpfError, ProgramResult},
22        memory_region::MemoryMapping,
23        program::{BuiltinFunction, SBPFVersion},
24        vm::{Config, ContextObject, EbpfVm},
25    },
26    solana_sdk_ids::{
27        bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, loader_v4, native_loader, sysvar,
28    },
29    solana_stable_layout::stable_instruction::StableInstruction,
30    solana_svm_callback::InvokeContextCallback,
31    solana_svm_feature_set::SVMFeatureSet,
32    solana_timings::{ExecuteDetailsTimings, ExecuteTimings},
33    solana_transaction_context::{
34        IndexOfAccount, InstructionAccount, TransactionAccount, TransactionContext,
35    },
36    solana_type_overrides::sync::{atomic::Ordering, Arc},
37    std::{
38        alloc::Layout,
39        cell::RefCell,
40        fmt::{self, Debug},
41        rc::Rc,
42    },
43};
44
45pub type BuiltinFunctionWithContext = BuiltinFunction<InvokeContext<'static>>;
46
47/// Adapter so we can unify the interfaces of built-in programs and syscalls
48#[macro_export]
49macro_rules! declare_process_instruction {
50    ($process_instruction:ident, $cu_to_consume:expr, |$invoke_context:ident| $inner:tt) => {
51        $crate::solana_sbpf::declare_builtin_function!(
52            $process_instruction,
53            fn rust(
54                invoke_context: &mut $crate::invoke_context::InvokeContext,
55                _arg0: u64,
56                _arg1: u64,
57                _arg2: u64,
58                _arg3: u64,
59                _arg4: u64,
60                _memory_mapping: &mut $crate::solana_sbpf::memory_region::MemoryMapping,
61            ) -> std::result::Result<u64, Box<dyn std::error::Error>> {
62                fn process_instruction_inner(
63                    $invoke_context: &mut $crate::invoke_context::InvokeContext,
64                ) -> std::result::Result<(), $crate::__private::InstructionError>
65                    $inner
66
67                let consumption_result = if $cu_to_consume > 0
68                {
69                    invoke_context.consume_checked($cu_to_consume)
70                } else {
71                    Ok(())
72                };
73                consumption_result
74                    .and_then(|_| {
75                        process_instruction_inner(invoke_context)
76                            .map(|_| 0)
77                            .map_err(|err| Box::new(err) as Box<dyn std::error::Error>)
78                    })
79                    .into()
80            }
81        );
82    };
83}
84
85impl ContextObject for InvokeContext<'_> {
86    fn trace(&mut self, state: [u64; 12]) {
87        self.syscall_context
88            .last_mut()
89            .unwrap()
90            .as_mut()
91            .unwrap()
92            .trace_log
93            .push(state);
94    }
95
96    fn consume(&mut self, amount: u64) {
97        // 1 to 1 instruction to compute unit mapping
98        // ignore overflow, Ebpf will bail if exceeded
99        let mut compute_meter = self.compute_meter.borrow_mut();
100        *compute_meter = compute_meter.saturating_sub(amount);
101    }
102
103    fn get_remaining(&self) -> u64 {
104        *self.compute_meter.borrow()
105    }
106}
107
108#[derive(Clone, PartialEq, Eq, Debug)]
109pub struct AllocErr;
110impl fmt::Display for AllocErr {
111    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
112        f.write_str("Error: Memory allocation failed")
113    }
114}
115
116pub struct BpfAllocator {
117    len: u64,
118    pos: u64,
119}
120
121impl BpfAllocator {
122    pub fn new(len: u64) -> Self {
123        Self { len, pos: 0 }
124    }
125
126    pub fn alloc(&mut self, layout: Layout) -> Result<u64, AllocErr> {
127        let bytes_to_align = (self.pos as *const u8).align_offset(layout.align()) as u64;
128        if self
129            .pos
130            .saturating_add(bytes_to_align)
131            .saturating_add(layout.size() as u64)
132            <= self.len
133        {
134            self.pos = self.pos.saturating_add(bytes_to_align);
135            let addr = MM_HEAP_START.saturating_add(self.pos);
136            self.pos = self.pos.saturating_add(layout.size() as u64);
137            Ok(addr)
138        } else {
139            Err(AllocErr)
140        }
141    }
142}
143
144pub struct EnvironmentConfig<'a> {
145    pub blockhash: Hash,
146    pub blockhash_lamports_per_signature: u64,
147    epoch_stake_callback: &'a dyn InvokeContextCallback,
148    feature_set: &'a SVMFeatureSet,
149    sysvar_cache: &'a SysvarCache,
150}
151impl<'a> EnvironmentConfig<'a> {
152    pub fn new(
153        blockhash: Hash,
154        blockhash_lamports_per_signature: u64,
155        epoch_stake_callback: &'a dyn InvokeContextCallback,
156        feature_set: &'a SVMFeatureSet,
157        sysvar_cache: &'a SysvarCache,
158    ) -> Self {
159        Self {
160            blockhash,
161            blockhash_lamports_per_signature,
162            epoch_stake_callback,
163            feature_set,
164            sysvar_cache,
165        }
166    }
167}
168
169pub struct SyscallContext {
170    pub allocator: BpfAllocator,
171    pub accounts_metadata: Vec<SerializedAccountMetadata>,
172    pub trace_log: Vec<[u64; 12]>,
173}
174
175#[derive(Debug, Clone)]
176pub struct SerializedAccountMetadata {
177    pub original_data_len: usize,
178    pub vm_data_addr: u64,
179    pub vm_key_addr: u64,
180    pub vm_lamports_addr: u64,
181    pub vm_owner_addr: u64,
182}
183
184/// Main pipeline from runtime to program execution.
185pub struct InvokeContext<'a> {
186    /// Information about the currently executing transaction.
187    pub transaction_context: &'a mut TransactionContext,
188    /// The local program cache for the transaction batch.
189    pub program_cache_for_tx_batch: &'a mut ProgramCacheForTxBatch,
190    /// Runtime configurations used to provision the invocation environment.
191    pub environment_config: EnvironmentConfig<'a>,
192    /// The compute budget for the current invocation.
193    compute_budget: SVMTransactionExecutionBudget,
194    /// The compute cost for the current invocation.
195    execution_cost: SVMTransactionExecutionCost,
196    /// Instruction compute meter, for tracking compute units consumed against
197    /// the designated compute budget during program execution.
198    compute_meter: RefCell<u64>,
199    log_collector: Option<Rc<RefCell<LogCollector>>>,
200    /// Latest measurement not yet accumulated in [ExecuteDetailsTimings::execute_us]
201    pub execute_time: Option<Measure>,
202    pub timings: ExecuteDetailsTimings,
203    pub syscall_context: Vec<Option<SyscallContext>>,
204    traces: Vec<Vec<[u64; 12]>>,
205}
206
207impl<'a> InvokeContext<'a> {
208    #[allow(clippy::too_many_arguments)]
209    pub fn new(
210        transaction_context: &'a mut TransactionContext,
211        program_cache_for_tx_batch: &'a mut ProgramCacheForTxBatch,
212        environment_config: EnvironmentConfig<'a>,
213        log_collector: Option<Rc<RefCell<LogCollector>>>,
214        compute_budget: SVMTransactionExecutionBudget,
215        execution_cost: SVMTransactionExecutionCost,
216    ) -> Self {
217        Self {
218            transaction_context,
219            program_cache_for_tx_batch,
220            environment_config,
221            log_collector,
222            compute_budget,
223            execution_cost,
224            compute_meter: RefCell::new(compute_budget.compute_unit_limit),
225            execute_time: None,
226            timings: ExecuteDetailsTimings::default(),
227            syscall_context: Vec::new(),
228            traces: Vec::new(),
229        }
230    }
231
232    pub fn get_environments_for_slot(
233        &self,
234        effective_slot: Slot,
235    ) -> Result<&ProgramRuntimeEnvironments, InstructionError> {
236        let epoch_schedule = self.environment_config.sysvar_cache.get_epoch_schedule()?;
237        let epoch = epoch_schedule.get_epoch(effective_slot);
238        Ok(self
239            .program_cache_for_tx_batch
240            .get_environments_for_epoch(epoch))
241    }
242
243    /// Push a stack frame onto the invocation stack
244    pub fn push(&mut self) -> Result<(), InstructionError> {
245        let instruction_context = self
246            .transaction_context
247            .get_instruction_context_at_index_in_trace(
248                self.transaction_context.get_instruction_trace_length(),
249            )?;
250        let program_id = instruction_context
251            .get_last_program_key(self.transaction_context)
252            .map_err(|_| InstructionError::UnsupportedProgramId)?;
253        if self
254            .transaction_context
255            .get_instruction_context_stack_height()
256            != 0
257        {
258            let contains = (0..self
259                .transaction_context
260                .get_instruction_context_stack_height())
261                .any(|level| {
262                    self.transaction_context
263                        .get_instruction_context_at_nesting_level(level)
264                        .and_then(|instruction_context| {
265                            instruction_context
266                                .try_borrow_last_program_account(self.transaction_context)
267                        })
268                        .map(|program_account| program_account.get_key() == program_id)
269                        .unwrap_or(false)
270                });
271            let is_last = self
272                .transaction_context
273                .get_current_instruction_context()
274                .and_then(|instruction_context| {
275                    instruction_context.try_borrow_last_program_account(self.transaction_context)
276                })
277                .map(|program_account| program_account.get_key() == program_id)
278                .unwrap_or(false);
279            if contains && !is_last {
280                // Reentrancy not allowed unless caller is calling itself
281                return Err(InstructionError::ReentrancyNotAllowed);
282            }
283        }
284
285        self.syscall_context.push(None);
286        self.transaction_context.push()
287    }
288
289    /// Pop a stack frame from the invocation stack
290    fn pop(&mut self) -> Result<(), InstructionError> {
291        if let Some(Some(syscall_context)) = self.syscall_context.pop() {
292            self.traces.push(syscall_context.trace_log);
293        }
294        self.transaction_context.pop()
295    }
296
297    /// Current height of the invocation stack, top level instructions are height
298    /// `solana_instruction::TRANSACTION_LEVEL_STACK_HEIGHT`
299    pub fn get_stack_height(&self) -> usize {
300        self.transaction_context
301            .get_instruction_context_stack_height()
302    }
303
304    /// Entrypoint for a cross-program invocation from a builtin program
305    pub fn native_invoke(
306        &mut self,
307        instruction: StableInstruction,
308        signers: &[Pubkey],
309    ) -> Result<(), InstructionError> {
310        let (instruction_accounts, program_indices) =
311            self.prepare_instruction(&instruction, signers)?;
312        let mut compute_units_consumed = 0;
313        self.process_instruction(
314            &instruction.data,
315            &instruction_accounts,
316            &program_indices,
317            &mut compute_units_consumed,
318            &mut ExecuteTimings::default(),
319        )?;
320        Ok(())
321    }
322
323    /// Helper to prepare for process_instruction()
324    #[allow(clippy::type_complexity)]
325    pub fn prepare_instruction(
326        &mut self,
327        instruction: &StableInstruction,
328        signers: &[Pubkey],
329    ) -> Result<(Vec<InstructionAccount>, Vec<IndexOfAccount>), InstructionError> {
330        // Finds the index of each account in the instruction by its pubkey.
331        // Then normalizes / unifies the privileges of duplicate accounts.
332        // Note: This is an O(n^2) algorithm,
333        // but performed on a very small slice and requires no heap allocations.
334        let instruction_context = self.transaction_context.get_current_instruction_context()?;
335        let mut deduplicated_instruction_accounts: Vec<InstructionAccount> = Vec::new();
336        let mut duplicate_indicies = Vec::with_capacity(instruction.accounts.len() as usize);
337        for (instruction_account_index, account_meta) in instruction.accounts.iter().enumerate() {
338            let index_in_transaction = self
339                .transaction_context
340                .find_index_of_account(&account_meta.pubkey)
341                .ok_or_else(|| {
342                    ic_msg!(
343                        self,
344                        "Instruction references an unknown account {}",
345                        account_meta.pubkey,
346                    );
347                    InstructionError::MissingAccount
348                })?;
349            if let Some(duplicate_index) =
350                deduplicated_instruction_accounts
351                    .iter()
352                    .position(|instruction_account| {
353                        instruction_account.index_in_transaction == index_in_transaction
354                    })
355            {
356                duplicate_indicies.push(duplicate_index);
357                let instruction_account = deduplicated_instruction_accounts
358                    .get_mut(duplicate_index)
359                    .ok_or(InstructionError::NotEnoughAccountKeys)?;
360                instruction_account.is_signer |= account_meta.is_signer;
361                instruction_account.is_writable |= account_meta.is_writable;
362            } else {
363                let index_in_caller = instruction_context
364                    .find_index_of_instruction_account(
365                        self.transaction_context,
366                        &account_meta.pubkey,
367                    )
368                    .ok_or_else(|| {
369                        ic_msg!(
370                            self,
371                            "Instruction references an unknown account {}",
372                            account_meta.pubkey,
373                        );
374                        InstructionError::MissingAccount
375                    })?;
376                duplicate_indicies.push(deduplicated_instruction_accounts.len());
377                deduplicated_instruction_accounts.push(InstructionAccount {
378                    index_in_transaction,
379                    index_in_caller,
380                    index_in_callee: instruction_account_index as IndexOfAccount,
381                    is_signer: account_meta.is_signer,
382                    is_writable: account_meta.is_writable,
383                });
384            }
385        }
386        for instruction_account in deduplicated_instruction_accounts.iter() {
387            let borrowed_account = instruction_context.try_borrow_instruction_account(
388                self.transaction_context,
389                instruction_account.index_in_caller,
390            )?;
391
392            // Readonly in caller cannot become writable in callee
393            if instruction_account.is_writable && !borrowed_account.is_writable() {
394                ic_msg!(
395                    self,
396                    "{}'s writable privilege escalated",
397                    borrowed_account.get_key(),
398                );
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                && !(borrowed_account.is_signer() || signers.contains(borrowed_account.get_key()))
406            {
407                ic_msg!(
408                    self,
409                    "{}'s signer privilege escalated",
410                    borrowed_account.get_key()
411                );
412                return Err(InstructionError::PrivilegeEscalation);
413            }
414        }
415        let instruction_accounts = duplicate_indicies
416            .into_iter()
417            .map(|duplicate_index| {
418                deduplicated_instruction_accounts
419                    .get(duplicate_index)
420                    .cloned()
421                    .ok_or(InstructionError::NotEnoughAccountKeys)
422            })
423            .collect::<Result<Vec<InstructionAccount>, InstructionError>>()?;
424
425        // Find and validate executables / program accounts
426        let callee_program_id = instruction.program_id;
427        let program_account_index = if self.get_feature_set().lift_cpi_caller_restriction {
428            self.transaction_context
429                .find_index_of_program_account(&callee_program_id)
430                .ok_or_else(|| {
431                    ic_msg!(self, "Unknown program {}", callee_program_id);
432                    InstructionError::MissingAccount
433                })?
434        } else {
435            let program_account_index = instruction_context
436                .find_index_of_instruction_account(self.transaction_context, &callee_program_id)
437                .ok_or_else(|| {
438                    ic_msg!(self, "Unknown program {}", callee_program_id);
439                    InstructionError::MissingAccount
440                })?;
441            let borrowed_program_account = instruction_context
442                .try_borrow_instruction_account(self.transaction_context, program_account_index)?;
443            #[allow(deprecated)]
444            if !self
445                .get_feature_set()
446                .remove_accounts_executable_flag_checks
447                && !borrowed_program_account.is_executable()
448            {
449                ic_msg!(self, "Account {} is not executable", callee_program_id);
450                return Err(InstructionError::AccountNotExecutable);
451            }
452            borrowed_program_account.get_index_in_transaction()
453        };
454
455        Ok((instruction_accounts, vec![program_account_index]))
456    }
457
458    /// Processes an instruction and returns how many compute units were used
459    pub fn process_instruction(
460        &mut self,
461        instruction_data: &[u8],
462        instruction_accounts: &[InstructionAccount],
463        program_indices: &[IndexOfAccount],
464        compute_units_consumed: &mut u64,
465        timings: &mut ExecuteTimings,
466    ) -> Result<(), InstructionError> {
467        *compute_units_consumed = 0;
468        self.transaction_context
469            .get_next_instruction_context()?
470            .configure(program_indices, instruction_accounts, instruction_data);
471        self.push()?;
472        self.process_executable_chain(compute_units_consumed, timings)
473            // MUST pop if and only if `push` succeeded, independent of `result`.
474            // Thus, the `.and()` instead of an `.and_then()`.
475            .and(self.pop())
476    }
477
478    /// Processes a precompile instruction
479    pub fn process_precompile<'ix_data>(
480        &mut self,
481        program_id: &Pubkey,
482        instruction_data: &[u8],
483        instruction_accounts: &[InstructionAccount],
484        program_indices: &[IndexOfAccount],
485        message_instruction_datas_iter: impl Iterator<Item = &'ix_data [u8]>,
486    ) -> Result<(), InstructionError> {
487        self.transaction_context
488            .get_next_instruction_context()?
489            .configure(program_indices, instruction_accounts, instruction_data);
490        self.push()?;
491
492        let instruction_datas: Vec<_> = message_instruction_datas_iter.collect();
493        self.environment_config
494            .epoch_stake_callback
495            .process_precompile(program_id, instruction_data, instruction_datas)
496            .map_err(InstructionError::from)
497            .and(self.pop())
498    }
499
500    /// Calls the instruction's program entrypoint method
501    fn process_executable_chain(
502        &mut self,
503        compute_units_consumed: &mut u64,
504        timings: &mut ExecuteTimings,
505    ) -> Result<(), InstructionError> {
506        let instruction_context = self.transaction_context.get_current_instruction_context()?;
507        let process_executable_chain_time = Measure::start("process_executable_chain_time");
508
509        let builtin_id = {
510            debug_assert!(instruction_context.get_number_of_program_accounts() <= 1);
511            let borrowed_root_account = instruction_context
512                .try_borrow_program_account(self.transaction_context, 0)
513                .map_err(|_| InstructionError::UnsupportedProgramId)?;
514            let owner_id = borrowed_root_account.get_owner();
515            if native_loader::check_id(owner_id) {
516                *borrowed_root_account.get_key()
517            } else if self
518                .get_feature_set()
519                .remove_accounts_executable_flag_checks
520            {
521                if bpf_loader_deprecated::check_id(owner_id)
522                    || bpf_loader::check_id(owner_id)
523                    || bpf_loader_upgradeable::check_id(owner_id)
524                    || loader_v4::check_id(owner_id)
525                {
526                    *owner_id
527                } else {
528                    return Err(InstructionError::UnsupportedProgramId);
529                }
530            } else {
531                *owner_id
532            }
533        };
534
535        // The Murmur3 hash value (used by RBPF) of the string "entrypoint"
536        const ENTRYPOINT_KEY: u32 = 0x71E3CF81;
537        let entry = self
538            .program_cache_for_tx_batch
539            .find(&builtin_id)
540            .ok_or(InstructionError::UnsupportedProgramId)?;
541        let function = match &entry.program {
542            ProgramCacheEntryType::Builtin(program) => program
543                .get_function_registry()
544                .lookup_by_key(ENTRYPOINT_KEY)
545                .map(|(_name, function)| function),
546            _ => None,
547        }
548        .ok_or(InstructionError::UnsupportedProgramId)?;
549        entry.ix_usage_counter.fetch_add(1, Ordering::Relaxed);
550
551        let program_id = *instruction_context.get_last_program_key(self.transaction_context)?;
552        self.transaction_context
553            .set_return_data(program_id, Vec::new())?;
554        let logger = self.get_log_collector();
555        stable_log::program_invoke(&logger, &program_id, self.get_stack_height());
556        let pre_remaining_units = self.get_remaining();
557        // In program-runtime v2 we will create this VM instance only once per transaction.
558        // `program_runtime_environment_v2.get_config()` will be used instead of `mock_config`.
559        // For now, only built-ins are invoked from here, so the VM and its Config are irrelevant.
560        let mock_config = Config::default();
561        let empty_memory_mapping =
562            MemoryMapping::new(Vec::new(), &mock_config, SBPFVersion::V0).unwrap();
563        let mut vm = EbpfVm::new(
564            self.program_cache_for_tx_batch
565                .environments
566                .program_runtime_v2
567                .clone(),
568            SBPFVersion::V0,
569            // Removes lifetime tracking
570            unsafe { std::mem::transmute::<&mut InvokeContext, &mut InvokeContext>(self) },
571            empty_memory_mapping,
572            0,
573        );
574        vm.invoke_function(function);
575        let result = match vm.program_result {
576            ProgramResult::Ok(_) => {
577                stable_log::program_success(&logger, &program_id);
578                Ok(())
579            }
580            ProgramResult::Err(ref err) => {
581                if let EbpfError::SyscallError(syscall_error) = err {
582                    if let Some(instruction_err) = syscall_error.downcast_ref::<InstructionError>()
583                    {
584                        stable_log::program_failure(&logger, &program_id, instruction_err);
585                        Err(instruction_err.clone())
586                    } else {
587                        stable_log::program_failure(&logger, &program_id, syscall_error);
588                        Err(InstructionError::ProgramFailedToComplete)
589                    }
590                } else {
591                    stable_log::program_failure(&logger, &program_id, err);
592                    Err(InstructionError::ProgramFailedToComplete)
593                }
594            }
595        };
596        let post_remaining_units = self.get_remaining();
597        *compute_units_consumed = pre_remaining_units.saturating_sub(post_remaining_units);
598
599        if builtin_id == program_id && result.is_ok() && *compute_units_consumed == 0 {
600            return Err(InstructionError::BuiltinProgramsMustConsumeComputeUnits);
601        }
602
603        timings
604            .execute_accessories
605            .process_instructions
606            .process_executable_chain_us += process_executable_chain_time.end_as_us();
607        result
608    }
609
610    /// Get this invocation's LogCollector
611    pub fn get_log_collector(&self) -> Option<Rc<RefCell<LogCollector>>> {
612        self.log_collector.clone()
613    }
614
615    /// Consume compute units
616    pub fn consume_checked(&self, amount: u64) -> Result<(), Box<dyn std::error::Error>> {
617        let mut compute_meter = self.compute_meter.borrow_mut();
618        let exceeded = *compute_meter < amount;
619        *compute_meter = compute_meter.saturating_sub(amount);
620        if exceeded {
621            return Err(Box::new(InstructionError::ComputationalBudgetExceeded));
622        }
623        Ok(())
624    }
625
626    /// Set compute units
627    ///
628    /// Only use for tests and benchmarks
629    pub fn mock_set_remaining(&self, remaining: u64) {
630        *self.compute_meter.borrow_mut() = remaining;
631    }
632
633    /// Get this invocation's compute budget
634    pub fn get_compute_budget(&self) -> &SVMTransactionExecutionBudget {
635        &self.compute_budget
636    }
637
638    /// Get this invocation's compute budget
639    pub fn get_execution_cost(&self) -> &SVMTransactionExecutionCost {
640        &self.execution_cost
641    }
642
643    /// Get the current feature set.
644    pub fn get_feature_set(&self) -> &SVMFeatureSet {
645        self.environment_config.feature_set
646    }
647
648    pub fn is_stake_raise_minimum_delegation_to_1_sol_active(&self) -> bool {
649        self.environment_config
650            .feature_set
651            .stake_raise_minimum_delegation_to_1_sol
652    }
653
654    pub fn is_deprecate_legacy_vote_ixs_active(&self) -> bool {
655        self.environment_config
656            .feature_set
657            .deprecate_legacy_vote_ixs
658    }
659
660    /// Get cached sysvars
661    pub fn get_sysvar_cache(&self) -> &SysvarCache {
662        self.environment_config.sysvar_cache
663    }
664
665    /// Get cached epoch total stake.
666    pub fn get_epoch_stake(&self) -> u64 {
667        self.environment_config
668            .epoch_stake_callback
669            .get_epoch_stake()
670    }
671
672    /// Get cached stake for the epoch vote account.
673    pub fn get_epoch_stake_for_vote_account(&self, pubkey: &'a Pubkey) -> u64 {
674        self.environment_config
675            .epoch_stake_callback
676            .get_epoch_stake_for_vote_account(pubkey)
677    }
678
679    pub fn is_precompile(&self, pubkey: &Pubkey) -> bool {
680        self.environment_config
681            .epoch_stake_callback
682            .is_precompile(pubkey)
683    }
684
685    // Should alignment be enforced during user pointer translation
686    pub fn get_check_aligned(&self) -> bool {
687        self.transaction_context
688            .get_current_instruction_context()
689            .and_then(|instruction_context| {
690                let program_account =
691                    instruction_context.try_borrow_last_program_account(self.transaction_context);
692                debug_assert!(program_account.is_ok());
693                program_account
694            })
695            .map(|program_account| *program_account.get_owner() != bpf_loader_deprecated::id())
696            .unwrap_or(true)
697    }
698
699    // Set this instruction syscall context
700    pub fn set_syscall_context(
701        &mut self,
702        syscall_context: SyscallContext,
703    ) -> Result<(), InstructionError> {
704        *self
705            .syscall_context
706            .last_mut()
707            .ok_or(InstructionError::CallDepth)? = Some(syscall_context);
708        Ok(())
709    }
710
711    // Get this instruction's SyscallContext
712    pub fn get_syscall_context(&self) -> Result<&SyscallContext, InstructionError> {
713        self.syscall_context
714            .last()
715            .and_then(std::option::Option::as_ref)
716            .ok_or(InstructionError::CallDepth)
717    }
718
719    // Get this instruction's SyscallContext
720    pub fn get_syscall_context_mut(&mut self) -> Result<&mut SyscallContext, InstructionError> {
721        self.syscall_context
722            .last_mut()
723            .and_then(|syscall_context| syscall_context.as_mut())
724            .ok_or(InstructionError::CallDepth)
725    }
726
727    /// Return a references to traces
728    pub fn get_traces(&self) -> &Vec<Vec<[u64; 12]>> {
729        &self.traces
730    }
731}
732
733#[macro_export]
734macro_rules! with_mock_invoke_context_with_feature_set {
735    (
736        $invoke_context:ident,
737        $transaction_context:ident,
738        $feature_set:ident,
739        $transaction_accounts:expr $(,)?
740    ) => {
741        use {
742            solana_log_collector::LogCollector,
743            solana_svm_callback::InvokeContextCallback,
744            $crate::{
745                __private::{Hash, ReadableAccount, Rent, TransactionContext},
746                execution_budget::{SVMTransactionExecutionBudget, SVMTransactionExecutionCost},
747                invoke_context::{EnvironmentConfig, InvokeContext},
748                loaded_programs::ProgramCacheForTxBatch,
749                sysvar_cache::SysvarCache,
750            },
751        };
752
753        struct MockInvokeContextCallback {}
754        impl InvokeContextCallback for MockInvokeContextCallback {}
755
756        let compute_budget = SVMTransactionExecutionBudget::default();
757        let mut $transaction_context = TransactionContext::new(
758            $transaction_accounts,
759            Rent::default(),
760            compute_budget.max_instruction_stack_depth,
761            compute_budget.max_instruction_trace_length,
762        );
763        let mut sysvar_cache = SysvarCache::default();
764        sysvar_cache.fill_missing_entries(|pubkey, callback| {
765            for index in 0..$transaction_context.get_number_of_accounts() {
766                if $transaction_context
767                    .get_key_of_account_at_index(index)
768                    .unwrap()
769                    == pubkey
770                {
771                    callback(
772                        $transaction_context
773                            .accounts()
774                            .try_borrow(index)
775                            .unwrap()
776                            .data(),
777                    );
778                }
779            }
780        });
781        let environment_config = EnvironmentConfig::new(
782            Hash::default(),
783            0,
784            &MockInvokeContextCallback {},
785            $feature_set,
786            &sysvar_cache,
787        );
788        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
789        let mut $invoke_context = InvokeContext::new(
790            &mut $transaction_context,
791            &mut program_cache_for_tx_batch,
792            environment_config,
793            Some(LogCollector::new_ref()),
794            compute_budget,
795            SVMTransactionExecutionCost::default(),
796        );
797    };
798}
799
800#[macro_export]
801macro_rules! with_mock_invoke_context {
802    (
803        $invoke_context:ident,
804        $transaction_context:ident,
805        $transaction_accounts:expr $(,)?
806    ) => {
807        use $crate::with_mock_invoke_context_with_feature_set;
808        let feature_set = &solana_svm_feature_set::SVMFeatureSet::default();
809        with_mock_invoke_context_with_feature_set!(
810            $invoke_context,
811            $transaction_context,
812            feature_set,
813            $transaction_accounts
814        )
815    };
816}
817
818#[allow(clippy::too_many_arguments)]
819pub fn mock_process_instruction_with_feature_set<
820    F: FnMut(&mut InvokeContext),
821    G: FnMut(&mut InvokeContext),
822>(
823    loader_id: &Pubkey,
824    mut program_indices: Vec<IndexOfAccount>,
825    instruction_data: &[u8],
826    mut transaction_accounts: Vec<TransactionAccount>,
827    instruction_account_metas: Vec<AccountMeta>,
828    expected_result: Result<(), InstructionError>,
829    builtin_function: BuiltinFunctionWithContext,
830    mut pre_adjustments: F,
831    mut post_adjustments: G,
832    feature_set: &SVMFeatureSet,
833) -> Vec<AccountSharedData> {
834    let mut instruction_accounts: Vec<InstructionAccount> =
835        Vec::with_capacity(instruction_account_metas.len());
836    for (instruction_account_index, account_meta) in instruction_account_metas.iter().enumerate() {
837        let index_in_transaction = transaction_accounts
838            .iter()
839            .position(|(key, _account)| *key == account_meta.pubkey)
840            .unwrap_or(transaction_accounts.len())
841            as IndexOfAccount;
842        let index_in_callee = instruction_accounts
843            .get(0..instruction_account_index)
844            .unwrap()
845            .iter()
846            .position(|instruction_account| {
847                instruction_account.index_in_transaction == index_in_transaction
848            })
849            .unwrap_or(instruction_account_index) as IndexOfAccount;
850        instruction_accounts.push(InstructionAccount {
851            index_in_transaction,
852            index_in_caller: index_in_transaction,
853            index_in_callee,
854            is_signer: account_meta.is_signer,
855            is_writable: account_meta.is_writable,
856        });
857    }
858    if program_indices.is_empty() {
859        program_indices.insert(0, transaction_accounts.len() as IndexOfAccount);
860        let processor_account = AccountSharedData::new(0, 0, &native_loader::id());
861        transaction_accounts.push((*loader_id, processor_account));
862    }
863    let pop_epoch_schedule_account = if !transaction_accounts
864        .iter()
865        .any(|(key, _)| *key == sysvar::epoch_schedule::id())
866    {
867        transaction_accounts.push((
868            sysvar::epoch_schedule::id(),
869            create_account_shared_data_for_test(&EpochSchedule::default()),
870        ));
871        true
872    } else {
873        false
874    };
875    with_mock_invoke_context_with_feature_set!(
876        invoke_context,
877        transaction_context,
878        feature_set,
879        transaction_accounts
880    );
881    let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
882    program_cache_for_tx_batch.replenish(
883        *loader_id,
884        Arc::new(ProgramCacheEntry::new_builtin(0, 0, builtin_function)),
885    );
886    invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
887    pre_adjustments(&mut invoke_context);
888    let result = invoke_context.process_instruction(
889        instruction_data,
890        &instruction_accounts,
891        &program_indices,
892        &mut 0,
893        &mut ExecuteTimings::default(),
894    );
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_indices: Vec<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_indices,
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_rent::Rent,
939        test_case::test_case,
940    };
941
942    #[derive(Debug, Serialize, Deserialize)]
943    enum MockInstruction {
944        NoopSuccess,
945        NoopFail,
946        ModifyOwned,
947        ModifyNotOwned,
948        ModifyReadonly,
949        UnbalancedPush,
950        UnbalancedPop,
951        ConsumeComputeUnits {
952            compute_units_to_consume: u64,
953            desired_result: Result<(), InstructionError>,
954        },
955        Resize {
956            new_len: u64,
957        },
958    }
959
960    const MOCK_BUILTIN_COMPUTE_UNIT_COST: u64 = 1;
961
962    declare_process_instruction!(
963        MockBuiltin,
964        MOCK_BUILTIN_COMPUTE_UNIT_COST,
965        |invoke_context| {
966            let transaction_context = &invoke_context.transaction_context;
967            let instruction_context = transaction_context.get_current_instruction_context()?;
968            let instruction_data = instruction_context.get_instruction_data();
969            let program_id = instruction_context.get_last_program_key(transaction_context)?;
970            let instruction_accounts = (0..4)
971                .map(|instruction_account_index| InstructionAccount {
972                    index_in_transaction: instruction_account_index,
973                    index_in_caller: instruction_account_index,
974                    index_in_callee: instruction_account_index,
975                    is_signer: false,
976                    is_writable: false,
977                })
978                .collect::<Vec<_>>();
979            assert_eq!(
980                program_id,
981                instruction_context
982                    .try_borrow_instruction_account(transaction_context, 0)?
983                    .get_owner()
984            );
985            assert_ne!(
986                instruction_context
987                    .try_borrow_instruction_account(transaction_context, 1)?
988                    .get_owner(),
989                instruction_context
990                    .try_borrow_instruction_account(transaction_context, 0)?
991                    .get_key()
992            );
993
994            if let Ok(instruction) = bincode::deserialize(instruction_data) {
995                match instruction {
996                    MockInstruction::NoopSuccess => (),
997                    MockInstruction::NoopFail => return Err(InstructionError::GenericError),
998                    MockInstruction::ModifyOwned => instruction_context
999                        .try_borrow_instruction_account(transaction_context, 0)?
1000                        .set_data_from_slice(&[1])?,
1001                    MockInstruction::ModifyNotOwned => instruction_context
1002                        .try_borrow_instruction_account(transaction_context, 1)?
1003                        .set_data_from_slice(&[1])?,
1004                    MockInstruction::ModifyReadonly => instruction_context
1005                        .try_borrow_instruction_account(transaction_context, 2)?
1006                        .set_data_from_slice(&[1])?,
1007                    MockInstruction::UnbalancedPush => {
1008                        instruction_context
1009                            .try_borrow_instruction_account(transaction_context, 0)?
1010                            .checked_add_lamports(1)?;
1011                        let program_id = *transaction_context.get_key_of_account_at_index(3)?;
1012                        let metas = vec![
1013                            AccountMeta::new_readonly(
1014                                *transaction_context.get_key_of_account_at_index(0)?,
1015                                false,
1016                            ),
1017                            AccountMeta::new_readonly(
1018                                *transaction_context.get_key_of_account_at_index(1)?,
1019                                false,
1020                            ),
1021                        ];
1022                        let inner_instruction = Instruction::new_with_bincode(
1023                            program_id,
1024                            &MockInstruction::NoopSuccess,
1025                            metas,
1026                        );
1027                        invoke_context
1028                            .transaction_context
1029                            .get_next_instruction_context()
1030                            .unwrap()
1031                            .configure(&[3], &instruction_accounts, &[]);
1032                        let result = invoke_context.push();
1033                        assert_eq!(result, Err(InstructionError::UnbalancedInstruction));
1034                        result?;
1035                        invoke_context
1036                            .native_invoke(inner_instruction.into(), &[])
1037                            .and(invoke_context.pop())?;
1038                    }
1039                    MockInstruction::UnbalancedPop => instruction_context
1040                        .try_borrow_instruction_account(transaction_context, 0)?
1041                        .checked_add_lamports(1)?,
1042                    MockInstruction::ConsumeComputeUnits {
1043                        compute_units_to_consume,
1044                        desired_result,
1045                    } => {
1046                        invoke_context
1047                            .consume_checked(compute_units_to_consume)
1048                            .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
1049                        return desired_result;
1050                    }
1051                    MockInstruction::Resize { new_len } => instruction_context
1052                        .try_borrow_instruction_account(transaction_context, 0)?
1053                        .set_data(vec![0; new_len as usize])?,
1054                }
1055            } else {
1056                return Err(InstructionError::InvalidInstructionData);
1057            }
1058            Ok(())
1059        }
1060    );
1061
1062    #[test]
1063    fn test_instruction_stack_height() {
1064        let one_more_than_max_depth = SVMTransactionExecutionBudget::default()
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 {
1077                index_in_transaction: index as IndexOfAccount,
1078                index_in_caller: index as IndexOfAccount,
1079                index_in_callee: instruction_accounts.len() as IndexOfAccount,
1080                is_signer: false,
1081                is_writable: true,
1082            });
1083        }
1084        for (index, program_id) in invoke_stack.iter().enumerate() {
1085            transaction_accounts.push((
1086                *program_id,
1087                AccountSharedData::new(1, 1, &solana_pubkey::Pubkey::default()),
1088            ));
1089            instruction_accounts.push(InstructionAccount {
1090                index_in_transaction: index as IndexOfAccount,
1091                index_in_caller: index as IndexOfAccount,
1092                index_in_callee: index as IndexOfAccount,
1093                is_signer: false,
1094                is_writable: false,
1095            });
1096        }
1097        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1098
1099        // Check call depth increases and has a limit
1100        let mut depth_reached: usize = 0;
1101        for _ in 0..invoke_stack.len() {
1102            invoke_context
1103                .transaction_context
1104                .get_next_instruction_context()
1105                .unwrap()
1106                .configure(
1107                    &[one_more_than_max_depth.saturating_add(depth_reached) as IndexOfAccount],
1108                    &instruction_accounts,
1109                    &[],
1110                );
1111            if Err(InstructionError::CallDepth) == invoke_context.push() {
1112                break;
1113            }
1114            depth_reached = depth_reached.saturating_add(1);
1115        }
1116        assert_ne!(depth_reached, 0);
1117        assert!(depth_reached < one_more_than_max_depth);
1118    }
1119
1120    #[test]
1121    fn test_max_instruction_trace_length() {
1122        const MAX_INSTRUCTIONS: usize = 8;
1123        let mut transaction_context =
1124            TransactionContext::new(Vec::new(), Rent::default(), 1, MAX_INSTRUCTIONS);
1125        for _ in 0..MAX_INSTRUCTIONS {
1126            transaction_context.push().unwrap();
1127            transaction_context.pop().unwrap();
1128        }
1129        assert_eq!(
1130            transaction_context.push(),
1131            Err(InstructionError::MaxInstructionTraceLengthExceeded)
1132        );
1133    }
1134
1135    #[test_case(MockInstruction::NoopSuccess, Ok(()); "NoopSuccess")]
1136    #[test_case(MockInstruction::NoopFail, Err(InstructionError::GenericError); "NoopFail")]
1137    #[test_case(MockInstruction::ModifyOwned, Ok(()); "ModifyOwned")]
1138    #[test_case(MockInstruction::ModifyNotOwned, Err(InstructionError::ExternalAccountDataModified); "ModifyNotOwned")]
1139    #[test_case(MockInstruction::ModifyReadonly, Err(InstructionError::ReadonlyDataModified); "ModifyReadonly")]
1140    #[test_case(MockInstruction::UnbalancedPush, Err(InstructionError::UnbalancedInstruction); "UnbalancedPush")]
1141    #[test_case(MockInstruction::UnbalancedPop, Err(InstructionError::UnbalancedInstruction); "UnbalancedPop")]
1142    fn test_process_instruction_account_modifications(
1143        instruction: MockInstruction,
1144        expected_result: Result<(), InstructionError>,
1145    ) {
1146        let callee_program_id = solana_pubkey::new_rand();
1147        let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1148        let not_owned_account = AccountSharedData::new(84, 1, &solana_pubkey::new_rand());
1149        let readonly_account = AccountSharedData::new(168, 1, &solana_pubkey::new_rand());
1150        let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1151        let mut program_account = AccountSharedData::new(1, 1, &native_loader::id());
1152        program_account.set_executable(true);
1153        let transaction_accounts = vec![
1154            (solana_pubkey::new_rand(), owned_account),
1155            (solana_pubkey::new_rand(), not_owned_account),
1156            (solana_pubkey::new_rand(), readonly_account),
1157            (callee_program_id, program_account),
1158            (solana_pubkey::new_rand(), loader_account),
1159        ];
1160        let metas = vec![
1161            AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1162            AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1163            AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1164        ];
1165        let instruction_accounts = (0..4)
1166            .map(|instruction_account_index| InstructionAccount {
1167                index_in_transaction: instruction_account_index,
1168                index_in_caller: instruction_account_index,
1169                index_in_callee: instruction_account_index,
1170                is_signer: false,
1171                is_writable: instruction_account_index < 2,
1172            })
1173            .collect::<Vec<_>>();
1174        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1175        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1176        program_cache_for_tx_batch.replenish(
1177            callee_program_id,
1178            Arc::new(ProgramCacheEntry::new_builtin(0, 1, MockBuiltin::vm)),
1179        );
1180        invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1181
1182        // Account modification tests
1183        invoke_context
1184            .transaction_context
1185            .get_next_instruction_context()
1186            .unwrap()
1187            .configure(&[4], &instruction_accounts, &[]);
1188        invoke_context.push().unwrap();
1189        let inner_instruction =
1190            Instruction::new_with_bincode(callee_program_id, &instruction, metas.clone());
1191        let result = invoke_context
1192            .native_invoke(inner_instruction.into(), &[])
1193            .and(invoke_context.pop());
1194        assert_eq!(result, expected_result);
1195    }
1196
1197    #[test_case(Ok(()); "Ok")]
1198    #[test_case(Err(InstructionError::GenericError); "GenericError")]
1199    fn test_process_instruction_compute_unit_consumption(
1200        expected_result: Result<(), InstructionError>,
1201    ) {
1202        let callee_program_id = solana_pubkey::new_rand();
1203        let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1204        let not_owned_account = AccountSharedData::new(84, 1, &solana_pubkey::new_rand());
1205        let readonly_account = AccountSharedData::new(168, 1, &solana_pubkey::new_rand());
1206        let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1207        let mut program_account = AccountSharedData::new(1, 1, &native_loader::id());
1208        program_account.set_executable(true);
1209        let transaction_accounts = vec![
1210            (solana_pubkey::new_rand(), owned_account),
1211            (solana_pubkey::new_rand(), not_owned_account),
1212            (solana_pubkey::new_rand(), readonly_account),
1213            (callee_program_id, program_account),
1214            (solana_pubkey::new_rand(), loader_account),
1215        ];
1216        let metas = vec![
1217            AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1218            AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1219            AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1220        ];
1221        let instruction_accounts = (0..4)
1222            .map(|instruction_account_index| InstructionAccount {
1223                index_in_transaction: instruction_account_index,
1224                index_in_caller: instruction_account_index,
1225                index_in_callee: instruction_account_index,
1226                is_signer: false,
1227                is_writable: instruction_account_index < 2,
1228            })
1229            .collect::<Vec<_>>();
1230        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1231        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1232        program_cache_for_tx_batch.replenish(
1233            callee_program_id,
1234            Arc::new(ProgramCacheEntry::new_builtin(0, 1, MockBuiltin::vm)),
1235        );
1236        invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1237
1238        // Compute unit consumption tests
1239        let compute_units_to_consume = 10;
1240        invoke_context
1241            .transaction_context
1242            .get_next_instruction_context()
1243            .unwrap()
1244            .configure(&[4], &instruction_accounts, &[]);
1245        invoke_context.push().unwrap();
1246        let inner_instruction = Instruction::new_with_bincode(
1247            callee_program_id,
1248            &MockInstruction::ConsumeComputeUnits {
1249                compute_units_to_consume,
1250                desired_result: expected_result.clone(),
1251            },
1252            metas.clone(),
1253        );
1254        let inner_instruction = StableInstruction::from(inner_instruction);
1255        let (inner_instruction_accounts, program_indices) = invoke_context
1256            .prepare_instruction(&inner_instruction, &[])
1257            .unwrap();
1258
1259        let mut compute_units_consumed = 0;
1260        let result = invoke_context.process_instruction(
1261            &inner_instruction.data,
1262            &inner_instruction_accounts,
1263            &program_indices,
1264            &mut compute_units_consumed,
1265            &mut ExecuteTimings::default(),
1266        );
1267
1268        // Because the instruction had compute cost > 0, then regardless of the execution result,
1269        // the number of compute units consumed should be a non-default which is something greater
1270        // than zero.
1271        assert!(compute_units_consumed > 0);
1272        assert_eq!(
1273            compute_units_consumed,
1274            compute_units_to_consume.saturating_add(MOCK_BUILTIN_COMPUTE_UNIT_COST),
1275        );
1276        assert_eq!(result, expected_result);
1277
1278        invoke_context.pop().unwrap();
1279    }
1280
1281    #[test]
1282    fn test_invoke_context_compute_budget() {
1283        let transaction_accounts = vec![(solana_pubkey::new_rand(), AccountSharedData::default())];
1284        let execution_budget = SVMTransactionExecutionBudget {
1285            compute_unit_limit: u64::from(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT),
1286            ..SVMTransactionExecutionBudget::default()
1287        };
1288
1289        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1290        invoke_context.compute_budget = execution_budget;
1291
1292        invoke_context
1293            .transaction_context
1294            .get_next_instruction_context()
1295            .unwrap()
1296            .configure(&[0], &[], &[]);
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 = [
1319            InstructionAccount {
1320                index_in_transaction: 0,
1321                index_in_caller: 0,
1322                index_in_callee: 0,
1323                is_signer: false,
1324                is_writable: true,
1325            },
1326            InstructionAccount {
1327                index_in_transaction: 1,
1328                index_in_caller: 1,
1329                index_in_callee: 1,
1330                is_signer: false,
1331                is_writable: false,
1332            },
1333        ];
1334        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1335        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1336        program_cache_for_tx_batch.replenish(
1337            program_key,
1338            Arc::new(ProgramCacheEntry::new_builtin(0, 0, MockBuiltin::vm)),
1339        );
1340        invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1341
1342        let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
1343        let instruction_data = bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1344
1345        let result = invoke_context.process_instruction(
1346            &instruction_data,
1347            &instruction_accounts,
1348            &[2],
1349            &mut 0,
1350            &mut ExecuteTimings::default(),
1351        );
1352
1353        assert!(result.is_ok());
1354        assert_eq!(
1355            invoke_context
1356                .transaction_context
1357                .accounts_resize_delta()
1358                .unwrap(),
1359            resize_delta
1360        );
1361    }
1362}