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