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