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