solana_program_runtime/
invoke_context.rs

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