Skip to main content

solana_program_runtime/
invoke_context.rs

1#[cfg(feature = "dev-context-only-utils")]
2use {
3    crate::program_cache_entry::ProgramCacheEntry,
4    qualifier_attr::qualifiers,
5    solana_account::{AccountSharedData, WritableAccount},
6    solana_epoch_schedule::EpochSchedule,
7    solana_instruction::AccountMeta,
8    solana_message::{LegacyMessage, Message, SanitizedMessage},
9    solana_sdk_ids::sysvar,
10    solana_transaction_context::transaction_accounts::KeyedAccountSharedData,
11    std::collections::{HashMap, HashSet},
12};
13use {
14    crate::{
15        execution_budget::{SVMTransactionExecutionBudget, SVMTransactionExecutionCost},
16        loaded_programs::{
17            ProgramCacheForTxBatch, ProgramRuntimeEnvironment, ProgramRuntimeEnvironments,
18        },
19        memory_context::{MemoryContext, MemoryContexts},
20        program_cache_entry::ProgramCacheEntryType,
21        stable_log,
22        sysvar_cache::SysvarCache,
23    },
24    solana_hash::Hash,
25    solana_instruction::Instruction,
26    solana_instruction_error::InstructionError,
27    solana_pubkey::Pubkey,
28    solana_sbpf::{
29        ebpf::MM_HEAP_START,
30        elf::{ElfError, Executable as GenericExecutable},
31        error::{EbpfError, ProgramResult},
32        memory_region::MemoryMapping,
33        program::{BuiltinProgram, SBPFVersion},
34        vm::{Config, ContextObject, EbpfVm},
35    },
36    solana_sdk_ids::{
37        bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, loader_v4, native_loader,
38    },
39    solana_svm_callback::InvokeContextCallback,
40    solana_svm_feature_set::SVMFeatureSet,
41    solana_svm_log_collector::{LogCollector, ic_msg},
42    solana_svm_measure::{measure::Measure, measure_us},
43    solana_svm_timings::{ExecuteDetailsTimings, ExecuteTimings},
44    solana_svm_transaction::svm_message::SVMMessage,
45    solana_svm_type_overrides::sync::Arc,
46    solana_transaction_context::{
47        IndexOfAccount, MAX_ACCOUNTS_PER_TRANSACTION, instruction::InstructionContext,
48        instruction_accounts::InstructionAccount, transaction::TransactionContext,
49    },
50    std::{
51        alloc::Layout,
52        borrow::Cow,
53        cell::{Cell, RefCell},
54        fmt::{self, Debug},
55        ptr,
56        rc::Rc,
57        time::Duration,
58    },
59};
60
61pub type BuiltinFunctionRegisterer =
62    fn(&mut BuiltinProgram<InvokeContext<'static, 'static>>, &str) -> Result<(), ElfError>;
63pub type Executable = GenericExecutable<InvokeContext<'static, 'static>>;
64pub type RegisterTrace<'a> = &'a [[u64; 12]];
65
66/// Adapter so we can unify the interfaces of built-in programs and syscalls
67#[macro_export]
68macro_rules! declare_process_instruction {
69    ($process_instruction:ident, $cu_to_consume:expr, |$invoke_context:ident| $inner:tt) => {
70        $crate::solana_sbpf::declare_builtin_function!(
71            $process_instruction,
72            fn rust(
73                invoke_context: &mut $crate::invoke_context::InvokeContext<'_, '_>,
74                _arg0: u64,
75                _arg1: u64,
76                _arg2: u64,
77                _arg3: u64,
78                _arg4: u64,
79            ) -> Result<u64, Box<dyn std::error::Error>> {
80                fn process_instruction_inner(
81                    $invoke_context: &mut $crate::invoke_context::InvokeContext,
82                ) -> std::result::Result<(), $crate::__private::InstructionError>
83                    $inner
84
85                let consumption_result = if $cu_to_consume > 0
86                {
87                    invoke_context.compute_meter.consume_checked($cu_to_consume)
88                } else {
89                    Ok(())
90                };
91                consumption_result
92                    .and_then(|_| {
93                        process_instruction_inner(invoke_context)
94                            .map(|_| 0)
95                            .map_err(|err| Box::new(err) as Box<dyn std::error::Error>)
96                    })
97                    .into()
98            }
99        );
100    };
101}
102
103impl ContextObject for InvokeContext<'_, '_> {
104    fn consume(&mut self, amount: u64) {
105        // 1 to 1 instruction to compute unit mapping
106        // ignore overflow, Ebpf will bail if exceeded
107        let compute_meter = self.compute_meter.0.get();
108        self.compute_meter
109            .0
110            .set(compute_meter.saturating_sub(amount));
111    }
112
113    fn get_remaining(&self) -> u64 {
114        self.compute_meter.0.get()
115    }
116
117    fn active_mapping_ptr(&mut self) -> ptr::NonNull<MemoryMapping> {
118        let memory = self
119            .memory_contexts
120            .memory_mapping_mut()
121            .expect("The memory context must have been set for the current instruction");
122        ptr::NonNull::from_mut(memory)
123    }
124}
125
126#[derive(Clone, PartialEq, Eq, Debug)]
127pub struct AllocErr;
128impl fmt::Display for AllocErr {
129    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
130        f.write_str("Error: Memory allocation failed")
131    }
132}
133
134pub struct BpfAllocator {
135    len: u64,
136    pos: u64,
137}
138
139impl BpfAllocator {
140    pub fn new(len: u64) -> Self {
141        Self { len, pos: 0 }
142    }
143
144    pub fn alloc(&mut self, layout: Layout) -> Result<u64, AllocErr> {
145        let bytes_to_align = (self.pos as *const u8).align_offset(layout.align()) as u64;
146        if self
147            .pos
148            .saturating_add(bytes_to_align)
149            .saturating_add(layout.size() as u64)
150            <= self.len
151        {
152            self.pos = self.pos.saturating_add(bytes_to_align);
153            let addr = MM_HEAP_START.saturating_add(self.pos);
154            self.pos = self.pos.saturating_add(layout.size() as u64);
155            Ok(addr)
156        } else {
157            Err(AllocErr)
158        }
159    }
160}
161
162pub struct EnvironmentConfig<'a> {
163    pub blockhash: Hash,
164    pub blockhash_lamports_per_signature: u64,
165    alpenglow_migration_succeeded: bool,
166    epoch_stake_callback: &'a dyn InvokeContextCallback,
167    feature_set: &'a SVMFeatureSet,
168    program_runtime_environments: &'a ProgramRuntimeEnvironments,
169    sysvar_cache: &'a SysvarCache,
170}
171impl<'a> EnvironmentConfig<'a> {
172    pub fn new(
173        blockhash: Hash,
174        blockhash_lamports_per_signature: u64,
175        alpenglow_migration_succeeded: bool,
176        epoch_stake_callback: &'a dyn InvokeContextCallback,
177        feature_set: &'a SVMFeatureSet,
178        program_runtime_environments: &'a ProgramRuntimeEnvironments,
179        sysvar_cache: &'a SysvarCache,
180    ) -> Self {
181        Self {
182            blockhash,
183            blockhash_lamports_per_signature,
184            alpenglow_migration_succeeded,
185            epoch_stake_callback,
186            feature_set,
187            program_runtime_environments,
188            sysvar_cache,
189        }
190    }
191
192    /// Get cached sysvars
193    pub fn sysvar_cache(&self) -> &SysvarCache {
194        self.sysvar_cache
195    }
196}
197
198pub struct ComputeMeter(Cell<u64>);
199
200impl ComputeMeter {
201    /// Consume compute units
202    pub fn consume_checked(&self, amount: u64) -> Result<(), Box<dyn std::error::Error>> {
203        let compute_meter = self.0.get();
204        let exceeded = compute_meter < amount;
205        self.0.set(compute_meter.saturating_sub(amount));
206        if exceeded {
207            return Err(Box::new(InstructionError::ComputationalBudgetExceeded));
208        }
209        Ok(())
210    }
211
212    /// Set compute units
213    ///
214    /// Only use for tests and benchmarks
215    #[cfg(feature = "dev-context-only-utils")]
216    pub fn mock_set_remaining(&self, remaining: u64) {
217        self.0.set(remaining);
218    }
219}
220
221/// Main pipeline from runtime to program execution.
222pub struct InvokeContext<'a, 'ix_data> {
223    /// Information about the currently executing transaction.
224    pub transaction_context: &'a mut TransactionContext<'ix_data>,
225    /// The local program cache for the transaction batch.
226    pub program_cache_for_tx_batch: &'a mut ProgramCacheForTxBatch,
227    /// Runtime configurations used to provision the invocation environment.
228    pub environment_config: EnvironmentConfig<'a>,
229    /// The compute budget for the current invocation.
230    compute_budget: SVMTransactionExecutionBudget,
231    /// The compute cost for the current invocation.
232    execution_cost: SVMTransactionExecutionCost,
233    /// Instruction compute meter, for tracking compute units consumed against
234    /// the designated compute budget during program execution.
235    pub compute_meter: ComputeMeter,
236    log_collector: Option<Rc<RefCell<LogCollector>>>,
237    /// Time spent so far executing nested program calls.
238    pub total_nested_exec_time: Duration,
239    pub timings: ExecuteDetailsTimings,
240    pub memory_contexts: MemoryContexts,
241    /// Pairs of index in TX instruction trace and VM register trace
242    register_traces: Vec<(usize, Vec<[u64; 12]>)>,
243    /// Debug port to use for this executing transaction.
244    #[cfg(feature = "sbpf-debugger")]
245    pub debug_port: Option<u16>,
246}
247
248impl<'a, 'ix_data> InvokeContext<'a, 'ix_data> {
249    pub fn new(
250        transaction_context: &'a mut TransactionContext<'ix_data>,
251        program_cache_for_tx_batch: &'a mut ProgramCacheForTxBatch,
252        environment_config: EnvironmentConfig<'a>,
253        log_collector: Option<Rc<RefCell<LogCollector>>>,
254        compute_budget: SVMTransactionExecutionBudget,
255        execution_cost: SVMTransactionExecutionCost,
256    ) -> Self {
257        Self {
258            transaction_context,
259            program_cache_for_tx_batch,
260            environment_config,
261            log_collector,
262            compute_budget,
263            execution_cost,
264            compute_meter: ComputeMeter(Cell::new(compute_budget.compute_unit_limit)),
265            total_nested_exec_time: Duration::ZERO,
266            timings: ExecuteDetailsTimings::default(),
267            memory_contexts: MemoryContexts::new(),
268            register_traces: Vec::new(),
269            #[cfg(feature = "sbpf-debugger")]
270            debug_port: None,
271        }
272    }
273
274    /// Push a stack frame onto the invocation stack
275    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
276    fn push(&mut self) -> Result<(), InstructionError> {
277        let instruction_context = self.transaction_context.get_next_instruction_context()?;
278        let program_id = instruction_context
279            .get_program_key()
280            .map_err(|_| InstructionError::UnsupportedProgramId)?;
281        if self.transaction_context.get_instruction_stack_height() != 0 {
282            let contains =
283                (0..self.transaction_context.get_instruction_stack_height()).any(|level| {
284                    self.transaction_context
285                        .get_instruction_context_at_nesting_level(level)
286                        .and_then(|instruction_context| instruction_context.get_program_key())
287                        .map(|program_key| program_key == program_id)
288                        .unwrap_or(false)
289                });
290            let is_last = self
291                .transaction_context
292                .get_current_instruction_context()
293                .and_then(|instruction_context| instruction_context.get_program_key())
294                .map(|program_key| program_key == program_id)
295                .unwrap_or(false);
296            if contains && !is_last {
297                // Reentrancy not allowed unless caller is calling itself
298                return Err(InstructionError::ReentrancyNotAllowed);
299            }
300        }
301
302        let detect_overflow_early = self
303            .environment_config
304            .feature_set
305            .early_instruction_trace_overflow_detection;
306        self.transaction_context.push(detect_overflow_early)?;
307        self.memory_contexts.push_placeholder();
308        Ok(())
309    }
310
311    /// Pop a stack frame from the invocation stack
312    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
313    fn pop(&mut self) -> Result<(), InstructionError> {
314        self.memory_contexts.pop();
315        self.transaction_context.pop()
316    }
317
318    /// Current height of the invocation stack, top level instructions are height
319    /// `solana_instruction::TRANSACTION_LEVEL_STACK_HEIGHT`
320    pub fn get_stack_height(&self) -> usize {
321        self.transaction_context.get_instruction_stack_height()
322    }
323
324    /// Entrypoint for a cross-program invocation from a builtin program.
325    ///
326    /// Takes signer seeds and derives PDAs internally via
327    /// `create_program_address`, mirroring the SBF CPI path. This makes
328    /// it structurally impossible for a builtin to vouch for a non-PDA
329    /// address (e.g. a user wallet) as a signer.
330    pub fn native_invoke_signed(
331        &mut self,
332        instruction: Instruction,
333        signer_seeds: &[&[&[u8]]],
334    ) -> Result<(), InstructionError> {
335        let caller_program_id = *self
336            .transaction_context
337            .get_current_instruction_context()?
338            .get_program_key()?;
339        // The conversion from `PubkeyError` to `InstructionError` through
340        // num-traits is incorrect, but it's the existing behavior.
341        let signers = signer_seeds
342            .iter()
343            .map(|seeds| Pubkey::create_program_address(seeds, &caller_program_id))
344            .collect::<Result<Vec<Pubkey>, solana_pubkey::PubkeyError>>()
345            .map_err(|e| e as u64)?;
346        self.build_instruction_frame(instruction)?;
347        self.internal_native_invoke(&signers)?;
348        Ok(())
349    }
350
351    /// A special native invoke for when the instruction is already configured in the instruction
352    /// trace
353    pub fn internal_native_invoke(&mut self, signers: &[Pubkey]) -> Result<(), InstructionError> {
354        self.verify_instruction_accounts(signers)?;
355        let mut compute_units_consumed = 0;
356        self.process_instruction(&mut compute_units_consumed, &mut ExecuteTimings::default())?;
357        Ok(())
358    }
359
360    /// Verifies if all the CPI accounts are present in the caller, and checks writability and
361    /// signing permissions.
362    pub(crate) fn verify_instruction_accounts(
363        &mut self,
364        signers: &[Pubkey],
365    ) -> Result<(), InstructionError> {
366        let instruction_context = self.transaction_context.get_current_instruction_context()?;
367        let next_context = self.transaction_context.get_next_instruction_context()?;
368        let callee_instruction_accounts = next_context.instruction_accounts();
369        for (idx, callee_account) in callee_instruction_accounts.iter().enumerate() {
370            if next_context
371                .is_instruction_account_duplicate(idx as u16)?
372                .is_some()
373            {
374                continue;
375            }
376
377            // The account passed down to the instruction is supposed to be present in the caller
378            let index_in_caller = instruction_context
379                .get_index_of_account_in_instruction(callee_account.index_in_transaction)?;
380
381            let caller_instruction_account = instruction_context
382                .instruction_accounts()
383                .get(index_in_caller as usize)
384                .expect(
385                    "get_index_of_account_in_instruction above has already checked if the index \
386                     is valid.",
387                );
388
389            let account_key = self
390                .transaction_context
391                .get_key_of_account_at_index(caller_instruction_account.index_in_transaction)?;
392
393            // Readonly in caller cannot become writable in callee
394            if callee_account.is_writable() && !caller_instruction_account.is_writable() {
395                ic_msg!(self, "{}'s writable privilege escalated", account_key,);
396                return Err(InstructionError::PrivilegeEscalation);
397            }
398
399            // To be signed in the callee,
400            // it must be either signed in the caller or by the program
401            if callee_account.is_signer()
402                && !(caller_instruction_account.is_signer() || signers.contains(account_key))
403            {
404                ic_msg!(self, "{}'s signer privilege escalated", account_key,);
405                return Err(InstructionError::PrivilegeEscalation);
406            }
407        }
408
409        // See if program account is part of the instruction
410        // `build_instruction_frame` already checked if the account is part of the transaction.
411        let program_id_tx_idx = next_context.get_index_of_program_account_in_transaction()?;
412        if instruction_context
413            .get_index_of_account_in_instruction(program_id_tx_idx)
414            .is_err()
415        {
416            let callee_program_id = self
417                .transaction_context
418                .get_key_of_account_at_index(program_id_tx_idx)
419                .expect("We should have checked that the program ID is in the transaction");
420            ic_msg!(self, "Unknown program {}", callee_program_id);
421            return Err(InstructionError::MissingAccount);
422        }
423
424        Ok(())
425    }
426
427    /// Convert an SDK Instruction from CPI to an InstructionFrame used by runtime.
428    pub(crate) fn build_instruction_frame(
429        &mut self,
430        instruction: Instruction,
431    ) -> Result<(), InstructionError> {
432        // We reference accounts by an u8 index, so we have a total of 256 accounts.
433        let transaction_callee_map_len = (self.transaction_context.get_number_of_accounts()
434            as usize)
435            .min(MAX_ACCOUNTS_PER_TRANSACTION);
436        let mut transaction_callee_map: Vec<u8> = vec![u8::MAX; transaction_callee_map_len];
437        let mut instruction_accounts: Vec<InstructionAccount> =
438            Vec::with_capacity(instruction.accounts.len());
439
440        // This code block is necessary to restrict the scope of the immutable borrow of
441        // transaction context (the `instruction_context` variable). At the end of this
442        // function, we must borrow it again as mutable.
443        let program_account_index = {
444            for account_meta in instruction.accounts.iter() {
445                let index_in_transaction = self
446                    .transaction_context
447                    .find_index_of_account(&account_meta.pubkey)
448                    .ok_or_else(|| {
449                        ic_msg!(
450                            self,
451                            "Instruction references an unknown account {}",
452                            account_meta.pubkey,
453                        );
454                        InstructionError::MissingAccount
455                    })?;
456
457                debug_assert!((index_in_transaction as usize) < transaction_callee_map.len());
458                let index_in_callee = transaction_callee_map
459                    .get_mut(index_in_transaction as usize)
460                    .unwrap();
461
462                if (*index_in_callee as usize) < instruction_accounts.len() {
463                    let cloned_account = {
464                        let instruction_account = instruction_accounts
465                            .get_mut(*index_in_callee as usize)
466                            .ok_or(InstructionError::MissingAccount)?;
467                        instruction_account.set_is_signer(
468                            instruction_account.is_signer() || account_meta.is_signer,
469                        );
470                        instruction_account.set_is_writable(
471                            instruction_account.is_writable() || account_meta.is_writable,
472                        );
473                        *instruction_account
474                    };
475                    instruction_accounts.push(cloned_account);
476                } else {
477                    *index_in_callee = instruction_accounts.len() as u8;
478                    instruction_accounts.push(InstructionAccount::new(
479                        index_in_transaction,
480                        account_meta.is_signer,
481                        account_meta.is_writable,
482                    ));
483                }
484            }
485
486            TransactionContext::replicate_account_flags(
487                &mut instruction_accounts,
488                &transaction_callee_map,
489            );
490
491            // Find executables / program accounts
492            let callee_program_id = &instruction.program_id;
493            let program_account_index_in_transaction = self
494                .transaction_context
495                .find_index_of_account(callee_program_id);
496
497            // Validate executables / program accounts
498            // Check if the account exists in the transaction
499            if program_account_index_in_transaction.is_none() {
500                ic_msg!(self, "Unknown program {}", callee_program_id);
501                return Err(InstructionError::MissingAccount);
502            }
503
504            // SAFETY: This unwrap is safe, because we checked the index in instruction in the
505            // previous if-condition.
506            program_account_index_in_transaction.unwrap()
507        };
508
509        // This ? operator should not error out because `fn get_current_instruction_index` is also called
510        // in `get_current_instruction_context`
511        let caller_index = self.transaction_context.get_current_instruction_index()?;
512        self.transaction_context.configure_instruction_at_index(
513            self.transaction_context.get_instruction_trace_length(),
514            program_account_index,
515            instruction_accounts,
516            transaction_callee_map,
517            Cow::Owned(instruction.data),
518            Some(caller_index as u16),
519        )?;
520        Ok(())
521    }
522
523    /// Process a message. Calls each instruction in the message over the
524    /// configured [`TransactionContext`] and returns the final result.
525    /// For any instructions that fail, a tuple is returned whose elements are
526    /// (index of instruction, instruction error). Once an error is returned,
527    /// execution stops.
528    pub fn process_message(
529        &mut self,
530        message: &'ix_data impl SVMMessage,
531        execute_timings: &mut ExecuteTimings,
532        accumulated_consumed_units: &mut u64,
533    ) -> Result<(), (u8, InstructionError)> {
534        self.prepare_top_level_instructions(message)?;
535
536        for (top_level_instruction_index, (program_id, instruction)) in
537            message.program_instructions_iter().enumerate()
538        {
539            let mut compute_units_consumed = 0;
540            let (result, process_instruction_us) = measure_us!({
541                if self.is_precompile(program_id) {
542                    self.process_precompile(
543                        program_id,
544                        instruction.data,
545                        message.instructions_iter().map(|ix| ix.data),
546                    )
547                } else {
548                    self.process_instruction(&mut compute_units_consumed, execute_timings)
549                }
550            });
551
552            *accumulated_consumed_units =
553                accumulated_consumed_units.saturating_add(compute_units_consumed);
554            // The per_program_timings are only used for metrics reporting at the trace
555            // level, so they should only be accumulated when trace level is enabled.
556            if log::log_enabled!(log::Level::Trace) {
557                execute_timings.details.accumulate_program(
558                    program_id,
559                    process_instruction_us,
560                    compute_units_consumed,
561                    result.is_err(),
562                );
563            }
564            self.timings = {
565                execute_timings.details.accumulate(&self.timings);
566                ExecuteDetailsTimings::default()
567            };
568            execute_timings
569                .execute_accessories
570                .process_instructions
571                .total_us += process_instruction_us;
572
573            result.map_err(|err| (top_level_instruction_index as u8, err))?;
574        }
575
576        Ok(())
577    }
578
579    /// Prepare the instruction trace with all the top level instructions
580    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
581    fn prepare_top_level_instructions(
582        &mut self,
583        message: &'ix_data impl SVMMessage,
584    ) -> Result<(), (u8, InstructionError)> {
585        for (top_level_instruction_index, (_, instruction)) in
586            message.program_instructions_iter().enumerate()
587        {
588            let transaction_callee_map_len = message
589                .account_keys()
590                .len()
591                .min(MAX_ACCOUNTS_PER_TRANSACTION);
592            let mut transaction_callee_map: Vec<u8> = vec![u8::MAX; transaction_callee_map_len];
593
594            let mut instruction_accounts: Vec<InstructionAccount> =
595                Vec::with_capacity(instruction.accounts.len());
596            for index_in_transaction in instruction.accounts.iter() {
597                let index_in_callee = transaction_callee_map
598                    .get_mut(*index_in_transaction as usize)
599                    .expect("Invalid index in transaction");
600
601                if (*index_in_callee as usize) > instruction_accounts.len() {
602                    *index_in_callee = instruction_accounts.len() as u8;
603                }
604
605                let index_in_transaction = *index_in_transaction as usize;
606                instruction_accounts.push(InstructionAccount::new(
607                    index_in_transaction as IndexOfAccount,
608                    message.is_signer(index_in_transaction),
609                    message.is_writable(index_in_transaction),
610                ));
611            }
612
613            self.transaction_context
614                .configure_instruction_at_index(
615                    top_level_instruction_index,
616                    instruction.program_id_index as u16,
617                    instruction_accounts,
618                    transaction_callee_map,
619                    Cow::Borrowed(instruction.data),
620                    None,
621                )
622                .map_err(|err| (top_level_instruction_index as u8, err))?;
623        }
624        Ok(())
625    }
626
627    /// Processes an instruction and returns how many compute units were used
628    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
629    pub(crate) fn process_instruction(
630        &mut self,
631        compute_units_consumed: &mut u64,
632        timings: &mut ExecuteTimings,
633    ) -> Result<(), InstructionError> {
634        *compute_units_consumed = 0;
635        self.push()?;
636        self.process_executable_chain(compute_units_consumed, timings)
637            // MUST pop if and only if `push` succeeded, independent of `result`.
638            // Thus, the `.and()` instead of an `.and_then()`.
639            .and(self.pop())
640    }
641
642    /// Processes a precompile instruction
643    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
644    fn process_precompile(
645        &mut self,
646        program_id: &Pubkey,
647        instruction_data: &[u8],
648        message_instruction_datas_iter: impl Iterator<Item = &'ix_data [u8]>,
649    ) -> Result<(), InstructionError> {
650        self.push()?;
651        let instruction_datas: Vec<_> = message_instruction_datas_iter.collect();
652        self.environment_config
653            .epoch_stake_callback
654            .process_precompile(program_id, instruction_data, instruction_datas)
655            .map_err(InstructionError::from)
656            .and(self.pop())
657    }
658
659    /// Calls the instruction's program entrypoint method
660    fn process_executable_chain(
661        &mut self,
662        compute_units_consumed: &mut u64,
663        timings: &mut ExecuteTimings,
664    ) -> Result<(), InstructionError> {
665        let instruction_context = self.transaction_context.get_current_instruction_context()?;
666        let process_executable_chain_time = Measure::start("process_executable_chain_time");
667
668        let builtin_id = {
669            let owner_id = instruction_context.get_program_owner()?;
670            if native_loader::check_id(&owner_id) {
671                *instruction_context.get_program_key()?
672            } else if bpf_loader_deprecated::check_id(&owner_id)
673                || bpf_loader::check_id(&owner_id)
674                || bpf_loader_upgradeable::check_id(&owner_id)
675                || loader_v4::check_id(&owner_id)
676            {
677                owner_id
678            } else {
679                return Err(InstructionError::UnsupportedProgramId);
680            }
681        };
682
683        // The Murmur3 hash value (used by RBPF) of the string "entrypoint"
684        const ENTRYPOINT_KEY: u32 = 0x71E3CF81;
685        let entry = self
686            .program_cache_for_tx_batch
687            .find(&builtin_id)
688            .ok_or(InstructionError::UnsupportedProgramId)?;
689        let function = match &entry.program {
690            ProgramCacheEntryType::Builtin(program) => program
691                .get_function_registry()
692                .lookup_by_key(ENTRYPOINT_KEY)
693                .map(|(_name, (function, _codegen))| function),
694            _ => None,
695        }
696        .ok_or(InstructionError::UnsupportedProgramId)?;
697
698        let program_id = *instruction_context.get_program_key()?;
699        self.transaction_context
700            .set_return_data(program_id, Vec::new())?;
701        let logger = self.get_log_collector();
702        stable_log::program_invoke(&logger, &program_id, self.get_stack_height());
703        let pre_remaining_units = self.get_remaining();
704        // For now, only built-ins are invoked from here, so the VM and its Config are irrelevant.
705        self.memory_contexts
706            .set_memory_context_abi_v1(MemoryContext::new(
707                BpfAllocator::new(0),
708                Vec::new(),
709                // SAFETY:
710                // This path invokes a builtin program, so this mapping is never used.
711                unsafe {
712                    MemoryMapping::new(Vec::new(), &Config::default(), SBPFVersion::Reserved)
713                        .unwrap()
714                },
715            ))?;
716        let mut vm = EbpfVm::new(
717            Arc::clone(
718                &**self
719                    .environment_config
720                    .program_runtime_environments
721                    .get_env_for_execution(),
722            ),
723            SBPFVersion::V0,
724            // Removes lifetime tracking
725            unsafe { std::mem::transmute::<&mut InvokeContext, &mut InvokeContext>(self) },
726            0,
727        );
728        vm.invoke_function(function);
729        let result = match vm.program_result {
730            ProgramResult::Ok(_) => {
731                stable_log::program_success(&logger, &program_id);
732                Ok(())
733            }
734            ProgramResult::Err(ref err) => {
735                if let EbpfError::SyscallError(syscall_error) = err {
736                    if let Some(instruction_err) = syscall_error.downcast_ref::<InstructionError>()
737                    {
738                        stable_log::program_failure(&logger, &program_id, instruction_err);
739                        Err(instruction_err.clone())
740                    } else {
741                        stable_log::program_failure(&logger, &program_id, syscall_error);
742                        Err(InstructionError::ProgramFailedToComplete)
743                    }
744                } else {
745                    stable_log::program_failure(&logger, &program_id, err);
746                    Err(InstructionError::ProgramFailedToComplete)
747                }
748            }
749        };
750        let post_remaining_units = self.get_remaining();
751        *compute_units_consumed = pre_remaining_units.saturating_sub(post_remaining_units);
752
753        if builtin_id == program_id && result.is_ok() && *compute_units_consumed == 0 {
754            return Err(InstructionError::BuiltinProgramsMustConsumeComputeUnits);
755        }
756
757        timings
758            .execute_accessories
759            .process_instructions
760            .process_executable_chain_us += process_executable_chain_time.end_as_us();
761        result
762    }
763
764    /// Get this invocation's LogCollector
765    pub fn get_log_collector(&self) -> Option<Rc<RefCell<LogCollector>>> {
766        self.log_collector.clone()
767    }
768
769    #[cfg(feature = "dev-context-only-utils")]
770    pub fn set_alpenglow_migration_succeeded_for_tests(&mut self, succeeded: bool) {
771        self.environment_config.alpenglow_migration_succeeded = succeeded;
772    }
773
774    /// Get this invocation's compute budget
775    pub fn get_compute_budget(&self) -> &SVMTransactionExecutionBudget {
776        &self.compute_budget
777    }
778
779    /// Get this invocation's compute budget
780    pub fn get_execution_cost(&self) -> &SVMTransactionExecutionCost {
781        &self.execution_cost
782    }
783
784    /// Get the current feature set.
785    pub fn get_feature_set(&self) -> &SVMFeatureSet {
786        self.environment_config.feature_set
787    }
788
789    pub fn get_program_runtime_environment_for_deployment(&self) -> &ProgramRuntimeEnvironment {
790        self.environment_config
791            .program_runtime_environments
792            .get_env_for_deployment()
793    }
794
795    pub fn is_deprecate_legacy_vote_ixs_active(&self) -> bool {
796        self.environment_config
797            .feature_set
798            .deprecate_legacy_vote_ixs
799    }
800
801    pub fn is_alpenglow_migration_succeeded(&self) -> bool {
802        self.environment_config.alpenglow_migration_succeeded
803    }
804
805    /// Get cached epoch total stake.
806    pub fn get_epoch_stake(&self) -> u64 {
807        self.environment_config
808            .epoch_stake_callback
809            .get_epoch_stake()
810    }
811
812    /// Get cached stake for the epoch vote account.
813    pub fn get_epoch_stake_for_vote_account(&self, pubkey: &'a Pubkey) -> u64 {
814        self.environment_config
815            .epoch_stake_callback
816            .get_epoch_stake_for_vote_account(pubkey)
817    }
818
819    pub fn is_precompile(&self, pubkey: &Pubkey) -> bool {
820        self.environment_config
821            .epoch_stake_callback
822            .is_precompile(pubkey)
823    }
824
825    // Should alignment be enforced during user pointer translation
826    pub fn get_check_aligned(&self) -> bool {
827        self.transaction_context
828            .get_current_instruction_context()
829            .and_then(|instruction_context| {
830                let owner_id = instruction_context.get_program_owner();
831                debug_assert!(owner_id.is_ok());
832                owner_id
833            })
834            .map(|owner_key| owner_key != bpf_loader_deprecated::id())
835            .unwrap_or(true)
836    }
837
838    /// Insert a VM register trace
839    pub(crate) fn insert_register_trace(&mut self, register_trace: Vec<[u64; 12]>) {
840        if register_trace.is_empty() {
841            return;
842        }
843        let Ok(instruction_context) = self.transaction_context.get_current_instruction_context()
844        else {
845            return;
846        };
847        self.register_traces
848            .push((instruction_context.get_index_in_trace(), register_trace));
849    }
850
851    /// Iterates over all VM register traces (including CPI)
852    pub fn iterate_vm_traces(
853        &self,
854        callback: &dyn Fn(InstructionContext, &Executable, RegisterTrace),
855    ) {
856        for (index_in_trace, register_trace) in &self.register_traces {
857            let Ok(instruction_context) = self
858                .transaction_context
859                .get_instruction_context_at_index_in_trace(*index_in_trace)
860            else {
861                continue;
862            };
863            let Ok(program_id) = instruction_context.get_program_key() else {
864                continue;
865            };
866            let Some(entry) = self.program_cache_for_tx_batch.find(program_id) else {
867                continue;
868            };
869            let ProgramCacheEntryType::Loaded(ref executable) = entry.program else {
870                continue;
871            };
872            callback(instruction_context, executable, register_trace.as_slice());
873        }
874    }
875}
876
877#[cfg(feature = "dev-context-only-utils")]
878#[macro_export]
879macro_rules! with_mock_invoke_context_with_feature_set {
880    (
881        $invoke_context:ident,
882        $transaction_context:ident,
883        $feature_set:ident,
884        $top_level_instructions:literal,
885        $transaction_accounts:expr,
886        $all_accounts:expr $(,)?
887    ) => {
888        use {
889            solana_svm_callback::InvokeContextCallback,
890            solana_svm_log_collector::LogCollector,
891            $crate::{
892                __private::{DropOnBailOut, Hash, ReadableAccount, Rent, TransactionContext},
893                execution_budget::{SVMTransactionExecutionBudget, SVMTransactionExecutionCost},
894                invoke_context::{EnvironmentConfig, InvokeContext},
895                loaded_programs::{ProgramCacheForTxBatch, ProgramRuntimeEnvironments},
896                sysvar_cache::SysvarCache,
897            },
898        };
899
900        struct MockInvokeContextCallback {}
901        impl InvokeContextCallback for MockInvokeContextCallback {}
902
903        let compute_budget = SVMTransactionExecutionBudget::new_with_defaults(
904            $feature_set.raise_cpi_nesting_limit_to_8,
905        );
906        let mut sysvar_cache = SysvarCache::default();
907        sysvar_cache.fill_missing_entries(|pubkey, callback| {
908            for (key, account) in $all_accounts.iter() {
909                if key == pubkey {
910                    callback(account.data());
911                }
912            }
913        });
914        let mut $transaction_context = TransactionContext::new_with_feature_flags(
915            $transaction_accounts,
916            Rent::default(),
917            compute_budget.max_instruction_stack_depth,
918            compute_budget.max_instruction_trace_length,
919            $top_level_instructions,
920            DropOnBailOut::Disabled,
921        );
922        let program_runtime_environments = ProgramRuntimeEnvironments::mock();
923        let environment_config = EnvironmentConfig::new(
924            Hash::default(),
925            0,
926            false,
927            &MockInvokeContextCallback {},
928            $feature_set,
929            &program_runtime_environments,
930            &sysvar_cache,
931        );
932        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
933        let mut $invoke_context = InvokeContext::new(
934            &mut $transaction_context,
935            &mut program_cache_for_tx_batch,
936            environment_config,
937            Some(LogCollector::new_ref()),
938            compute_budget,
939            SVMTransactionExecutionCost::default(),
940        );
941    };
942    (
943        $invoke_context:ident,
944        $transaction_context:ident,
945        $feature_set:ident,
946        $top_level_instructions:literal,
947        $transaction_accounts:expr $(,)?
948    ) => {
949        let transaction_accounts: Vec<(solana_pubkey::Pubkey, solana_account::AccountSharedData)> =
950            $transaction_accounts;
951        $crate::with_mock_invoke_context_with_feature_set!(
952            $invoke_context,
953            $transaction_context,
954            $feature_set,
955            $top_level_instructions,
956            transaction_accounts,
957            &transaction_accounts
958        );
959    };
960    (
961        $invoke_context:ident,
962        $transaction_context:ident,
963        $feature_set:ident,
964        $transaction_accounts:expr $(,)?
965    ) => {
966        $crate::with_mock_invoke_context_with_feature_set!(
967            $invoke_context,
968            $transaction_context,
969            $feature_set,
970            1,
971            $transaction_accounts
972        );
973    };
974}
975
976#[cfg(feature = "dev-context-only-utils")]
977#[macro_export]
978macro_rules! with_mock_invoke_context {
979    (
980        $invoke_context:ident,
981        $transaction_context:ident,
982        $top_level_instructions:literal,
983        $transaction_accounts:expr $(,)?
984    ) => {
985        let feature_set = &solana_svm_feature_set::SVMFeatureSet::default();
986        $crate::with_mock_invoke_context_with_feature_set!(
987            $invoke_context,
988            $transaction_context,
989            feature_set,
990            $top_level_instructions,
991            $transaction_accounts
992        )
993    };
994    (
995        $invoke_context:ident,
996        $transaction_context:ident,
997        $transaction_accounts:expr $(,)?
998    ) => {
999        with_mock_invoke_context!(
1000            $invoke_context,
1001            $transaction_context,
1002            1,
1003            $transaction_accounts
1004        );
1005    };
1006}
1007
1008#[cfg(feature = "dev-context-only-utils")]
1009pub fn mock_compile_message<A>(
1010    instruction: &Instruction,
1011    accounts: &[(Pubkey, A)],
1012    program_id: &Pubkey,
1013    loader_key: &Pubkey,
1014) -> (SanitizedMessage, Vec<(Pubkey, AccountSharedData)>)
1015where
1016    AccountSharedData: From<A>,
1017    A: Clone,
1018{
1019    let message = Message::new(std::slice::from_ref(instruction), None);
1020    let transaction_accounts: Vec<_> = message
1021        .account_keys
1022        .iter()
1023        .map(|key| {
1024            let account = accounts
1025                .iter()
1026                .find(|(k, _)| k == key)
1027                .map(|(_, a)| AccountSharedData::from(a.clone()))
1028                .unwrap_or_else(|| {
1029                    if key == program_id {
1030                        let mut account = AccountSharedData::new(0, 0, loader_key);
1031                        account.set_executable(true);
1032                        account
1033                    } else {
1034                        AccountSharedData::default()
1035                    }
1036                });
1037            (*key, account)
1038        })
1039        .collect();
1040
1041    let sanitized_message = SanitizedMessage::Legacy(LegacyMessage::new(message, &HashSet::new()));
1042
1043    (sanitized_message, transaction_accounts)
1044}
1045
1046#[cfg(feature = "dev-context-only-utils")]
1047pub fn mock_process_instruction_with_feature_set<
1048    F: FnMut(&mut InvokeContext),
1049    G: FnMut(&mut InvokeContext),
1050>(
1051    program_id: &Pubkey,
1052    instruction_data: &[u8],
1053    mut accounts: Vec<KeyedAccountSharedData>,
1054    instruction_account_metas: Vec<AccountMeta>,
1055    expected_result: Result<(), InstructionError>,
1056    builtin: BuiltinFunctionRegisterer,
1057    mut pre_adjustments: F,
1058    mut post_adjustments: G,
1059    feature_set: &SVMFeatureSet,
1060) -> Vec<AccountSharedData> {
1061    let original_len = accounts.len();
1062    if !accounts
1063        .iter()
1064        .any(|(key, _)| *key == sysvar::epoch_schedule::id())
1065    {
1066        let mut account = AccountSharedData::new(1, solana_epoch_schedule::SIZE, &sysvar::id());
1067        wincode::serialize_into(account.data_as_mut_slice(), &EpochSchedule::default()).unwrap();
1068        accounts.push((sysvar::epoch_schedule::id(), account));
1069    }
1070
1071    let instruction =
1072        Instruction::new_with_bytes(*program_id, instruction_data, instruction_account_metas);
1073    let (sanitized_message, transaction_accounts) =
1074        mock_compile_message(&instruction, &accounts, program_id, &native_loader::id());
1075
1076    let program_owner = accounts
1077        .iter()
1078        .find(|(key, _)| key == program_id)
1079        .map(|(_, acct)| *acct.owner())
1080        .unwrap_or_else(native_loader::id);
1081    let is_builtin = native_loader::check_id(&program_owner);
1082
1083    with_mock_invoke_context_with_feature_set!(
1084        invoke_context,
1085        transaction_context,
1086        feature_set,
1087        1,
1088        transaction_accounts,
1089        &accounts
1090    );
1091
1092    let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1093    program_cache_for_tx_batch.replenish(
1094        if is_builtin {
1095            *program_id
1096        } else {
1097            program_owner
1098        },
1099        Arc::new(ProgramCacheEntry::new_builtin(builtin)),
1100    );
1101    program_cache_for_tx_batch.set_slot_for_tests(
1102        invoke_context
1103            .environment_config
1104            .sysvar_cache()
1105            .get_clock()
1106            .map(|clock| clock.slot)
1107            .unwrap_or(1),
1108    );
1109    invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1110
1111    pre_adjustments(&mut invoke_context);
1112
1113    invoke_context
1114        .prepare_top_level_instructions(&sanitized_message)
1115        .unwrap();
1116
1117    let result = invoke_context.process_instruction(&mut 0, &mut ExecuteTimings::default());
1118    assert_eq!(result, expected_result);
1119    post_adjustments(&mut invoke_context);
1120
1121    let txn_result_keys: Vec<_> = (0..transaction_context.get_number_of_accounts())
1122        .map(|i| *transaction_context.get_key_of_account_at_index(i).unwrap())
1123        .collect();
1124    let txn_result_accounts = transaction_context.deconstruct_without_keys().unwrap();
1125    let txn_result_map = txn_result_keys
1126        .into_iter()
1127        .zip(txn_result_accounts)
1128        .collect::<HashMap<Pubkey, AccountSharedData>>();
1129
1130    accounts
1131        .into_iter()
1132        .take(original_len)
1133        .map(|(key, original)| txn_result_map.get(&key).cloned().unwrap_or(original))
1134        .collect()
1135}
1136
1137#[cfg(feature = "dev-context-only-utils")]
1138pub fn mock_process_instruction<F: FnMut(&mut InvokeContext), G: FnMut(&mut InvokeContext)>(
1139    program_id: &Pubkey,
1140    instruction_data: &[u8],
1141    accounts: Vec<KeyedAccountSharedData>,
1142    instruction_account_metas: Vec<AccountMeta>,
1143    expected_result: Result<(), InstructionError>,
1144    builtin: BuiltinFunctionRegisterer,
1145    pre_adjustments: F,
1146    post_adjustments: G,
1147) -> Vec<AccountSharedData> {
1148    mock_process_instruction_with_feature_set(
1149        program_id,
1150        instruction_data,
1151        accounts,
1152        instruction_account_metas,
1153        expected_result,
1154        builtin,
1155        pre_adjustments,
1156        post_adjustments,
1157        &SVMFeatureSet::all_enabled(),
1158    )
1159}
1160
1161#[cfg(test)]
1162mod tests {
1163    use {
1164        super::*,
1165        crate::execution_budget::{
1166            DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, MAX_INSTRUCTION_STACK_DEPTH,
1167            MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268,
1168        },
1169        openssl::{
1170            ec::{EcGroup, EcKey},
1171            nid::Nid,
1172        },
1173        solana_account::{Account, DUMMY_INHERITABLE_ACCOUNT_FIELDS, ReadableAccount},
1174        solana_ed25519_program::new_ed25519_instruction_with_signature,
1175        solana_keypair::{Address, Keypair},
1176        solana_message::AccountKeys,
1177        solana_precompile_error::PrecompileError,
1178        solana_rent::Rent,
1179        solana_sbpf::program::BuiltinFunctionDefinition,
1180        solana_sdk_ids::{ed25519_program, secp256k1_program, system_program},
1181        solana_secp256k1_program::{
1182            eth_address_from_pubkey, new_secp256k1_instruction_with_signature,
1183        },
1184        solana_secp256r1_program::{new_secp256r1_instruction_with_signature, sign_message},
1185        solana_signer::Signer,
1186        solana_svm_feature_set::SVMFeatureSet,
1187        solana_transaction::{Transaction, sanitized::SanitizedTransaction},
1188        solana_transaction_context::{MAX_ACCOUNTS_PER_INSTRUCTION, MAX_ACCOUNTS_PER_TRANSACTION},
1189        test_case::test_case,
1190    };
1191
1192    #[derive(Debug, wincode::SchemaRead, wincode::SchemaWrite)]
1193    enum MockInstruction {
1194        NoopSuccess,
1195        NoopFail,
1196        ModifyOwned,
1197        ModifyNotOwned,
1198        ModifyReadonly,
1199        UnbalancedPush,
1200        UnbalancedPop,
1201        ConsumeComputeUnits {
1202            compute_units_to_consume: u64,
1203            desired_result: Result<(), InstructionError>,
1204        },
1205        Resize {
1206            new_len: u64,
1207        },
1208    }
1209
1210    const MOCK_BUILTIN_COMPUTE_UNIT_COST: u64 = 1;
1211
1212    declare_process_instruction!(
1213        MockBuiltin,
1214        MOCK_BUILTIN_COMPUTE_UNIT_COST,
1215        |invoke_context| {
1216            let transaction_context = &invoke_context.transaction_context;
1217            let instruction_context = transaction_context.get_current_instruction_context()?;
1218            let instruction_data = instruction_context.get_instruction_data();
1219            let program_id = instruction_context.get_program_key()?;
1220            let instruction_accounts = (0..4)
1221                .map(|instruction_account_index| {
1222                    InstructionAccount::new(instruction_account_index, false, false)
1223                })
1224                .collect::<Vec<_>>();
1225            assert_eq!(
1226                program_id,
1227                instruction_context
1228                    .try_borrow_instruction_account(0)?
1229                    .get_owner()
1230            );
1231            assert_ne!(
1232                instruction_context
1233                    .try_borrow_instruction_account(1)?
1234                    .get_owner(),
1235                instruction_context.get_key_of_instruction_account(0)?
1236            );
1237
1238            if let Ok(instruction) = wincode::deserialize(instruction_data) {
1239                match instruction {
1240                    MockInstruction::NoopSuccess => (),
1241                    MockInstruction::NoopFail => return Err(InstructionError::GenericError),
1242                    MockInstruction::ModifyOwned => instruction_context
1243                        .try_borrow_instruction_account(0)?
1244                        .set_data_from_slice(&[1])?,
1245                    MockInstruction::ModifyNotOwned => instruction_context
1246                        .try_borrow_instruction_account(1)?
1247                        .set_data_from_slice(&[1])?,
1248                    MockInstruction::ModifyReadonly => instruction_context
1249                        .try_borrow_instruction_account(2)?
1250                        .set_data_from_slice(&[1])?,
1251                    MockInstruction::UnbalancedPush => {
1252                        instruction_context
1253                            .try_borrow_instruction_account(0)?
1254                            .checked_add_lamports(1)?;
1255                        let program_id = *transaction_context.get_key_of_account_at_index(3)?;
1256                        let metas = vec![
1257                            AccountMeta::new_readonly(
1258                                *transaction_context.get_key_of_account_at_index(0)?,
1259                                false,
1260                            ),
1261                            AccountMeta::new_readonly(
1262                                *transaction_context.get_key_of_account_at_index(1)?,
1263                                false,
1264                            ),
1265                        ];
1266                        let inner_instruction = Instruction::new_with_wincode(
1267                            program_id,
1268                            &MockInstruction::NoopSuccess,
1269                            metas,
1270                        );
1271                        invoke_context
1272                            .transaction_context
1273                            .configure_top_level_instruction_for_tests(
1274                                3,
1275                                instruction_accounts,
1276                                vec![],
1277                            )
1278                            .unwrap();
1279                        let result = invoke_context.push();
1280                        assert_eq!(result, Err(InstructionError::UnbalancedInstruction));
1281                        result?;
1282                        invoke_context
1283                            .native_invoke_signed(inner_instruction, &[])
1284                            .and(invoke_context.pop())?;
1285                    }
1286                    MockInstruction::UnbalancedPop => instruction_context
1287                        .try_borrow_instruction_account(0)?
1288                        .checked_add_lamports(1)?,
1289                    MockInstruction::ConsumeComputeUnits {
1290                        compute_units_to_consume,
1291                        desired_result,
1292                    } => {
1293                        invoke_context
1294                            .compute_meter
1295                            .consume_checked(compute_units_to_consume)
1296                            .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
1297                        return desired_result;
1298                    }
1299                    MockInstruction::Resize { new_len } => instruction_context
1300                        .try_borrow_instruction_account(0)?
1301                        .set_data_from_slice(&vec![0; new_len as usize])?,
1302                }
1303            } else {
1304                return Err(InstructionError::InvalidInstructionData);
1305            }
1306            Ok(())
1307        }
1308    );
1309
1310    #[test_case(false; "SIMD-0268 disabled")]
1311    #[test_case(true; "SIMD-0268 enabled")]
1312    fn test_instruction_stack_height(simd_0268_active: bool) {
1313        let feature_set = &SVMFeatureSet {
1314            raise_cpi_nesting_limit_to_8: simd_0268_active,
1315            ..SVMFeatureSet::all_enabled()
1316        };
1317        let max_depth = SVMTransactionExecutionBudget::new_with_defaults(simd_0268_active)
1318            .max_instruction_stack_depth;
1319        assert_eq!(
1320            max_depth,
1321            if simd_0268_active {
1322                MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268
1323            } else {
1324                MAX_INSTRUCTION_STACK_DEPTH
1325            },
1326        );
1327
1328        // Set up max_depth + 1 accounts (one extra to trigger the failing push)
1329        // and a matching program account for each.
1330        let mut invoke_stack = vec![];
1331        let mut transaction_accounts = vec![];
1332        let mut instruction_accounts = vec![];
1333        for index in 0..max_depth.saturating_add(1) {
1334            let program_id = solana_pubkey::new_rand();
1335            invoke_stack.push(program_id);
1336            transaction_accounts.push((
1337                solana_pubkey::new_rand(),
1338                AccountSharedData::new(1, 1, &program_id),
1339            ));
1340            instruction_accounts.push(InstructionAccount::new(
1341                index as IndexOfAccount,
1342                false,
1343                true,
1344            ));
1345        }
1346
1347        // Append program accounts after the regular accounts so that
1348        // `first_program_account + depth` indexes the right program.
1349        let first_program_account = transaction_accounts.len();
1350        for (index, program_id) in invoke_stack.iter().enumerate() {
1351            transaction_accounts.push((
1352                *program_id,
1353                AccountSharedData::new(1, 1, &solana_pubkey::Pubkey::default()),
1354            ));
1355            instruction_accounts.push(InstructionAccount::new(
1356                index as IndexOfAccount,
1357                false,
1358                false,
1359            ));
1360        }
1361        with_mock_invoke_context_with_feature_set!(
1362            invoke_context,
1363            transaction_context,
1364            feature_set,
1365            transaction_accounts,
1366        );
1367
1368        // Each push must succeed and the stack height must track.
1369        for depth in 0..max_depth {
1370            assert_eq!(invoke_context.get_stack_height(), depth);
1371            invoke_context
1372                .transaction_context
1373                .configure_top_level_instruction_for_tests(
1374                    (first_program_account.saturating_add(depth)) as IndexOfAccount,
1375                    instruction_accounts.clone(),
1376                    vec![],
1377                )
1378                .unwrap();
1379            assert!(
1380                invoke_context.push().is_ok(),
1381                "push at depth {depth} should succeed (max_depth={max_depth})",
1382            );
1383        }
1384
1385        // At exactly max_depth, one more push must fail with CallDepth.
1386        assert_eq!(invoke_context.get_stack_height(), max_depth);
1387        invoke_context
1388            .transaction_context
1389            .configure_top_level_instruction_for_tests(
1390                (first_program_account.saturating_add(max_depth)) as IndexOfAccount,
1391                instruction_accounts.clone(),
1392                vec![],
1393            )
1394            .unwrap();
1395        assert_eq!(invoke_context.push(), Err(InstructionError::CallDepth),);
1396
1397        // Stack height must not have changed after the rejected push.
1398        assert_eq!(invoke_context.get_stack_height(), max_depth);
1399    }
1400
1401    #[test_case(false; "simd_582_disabled")]
1402    #[test_case(true; "simd_582_enabled")]
1403    fn test_max_instruction_trace_length_top_level(fail_early: bool) {
1404        const MAX_INSTRUCTIONS: usize = 8;
1405        let mut transaction_context = TransactionContext::new(
1406            vec![(
1407                Pubkey::new_unique(),
1408                AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1409            )],
1410            Rent::default(),
1411            1,
1412            MAX_INSTRUCTIONS,
1413            MAX_INSTRUCTIONS,
1414        );
1415        for _ in 0..MAX_INSTRUCTIONS {
1416            transaction_context.push(fail_early).unwrap();
1417            transaction_context
1418                .configure_top_level_instruction_for_tests(
1419                    0,
1420                    vec![InstructionAccount::new(0, false, false)],
1421                    vec![],
1422                )
1423                .unwrap();
1424            transaction_context.pop().unwrap();
1425        }
1426
1427        assert_eq!(
1428            transaction_context.push(fail_early),
1429            Err(InstructionError::MaxInstructionTraceLengthExceeded)
1430        );
1431    }
1432
1433    #[test_case(false; "simd_582_disabled")]
1434    #[test_case(true; "simd_582_enabled")]
1435    fn test_max_instruction_trace_length_cpi(fail_early: bool) {
1436        // Hitting the limit with CPIs
1437        const MAX_INSTRUCTIONS: usize = 8;
1438        let mut transaction_context = TransactionContext::new(
1439            vec![(
1440                Pubkey::new_unique(),
1441                AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1442            )],
1443            Rent::default(),
1444            256,
1445            MAX_INSTRUCTIONS,
1446            2,
1447        );
1448        let num_transaction_accounts = usize::from(transaction_context.get_number_of_accounts());
1449
1450        transaction_context
1451            .configure_instruction_at_index(
1452                0,
1453                0,
1454                vec![InstructionAccount::new(0, false, false)],
1455                vec![u8::MAX; num_transaction_accounts],
1456                Cow::Owned(Vec::new()),
1457                None,
1458            )
1459            .unwrap();
1460
1461        transaction_context
1462            .configure_instruction_at_index(
1463                1,
1464                0,
1465                vec![InstructionAccount::new(0, false, false)],
1466                vec![u8::MAX; num_transaction_accounts],
1467                Cow::Owned(Vec::new()),
1468                None,
1469            )
1470            .unwrap();
1471
1472        for ix_in_trace in (2..).take(MAX_INSTRUCTIONS) {
1473            let result = transaction_context.push(fail_early);
1474            if ix_in_trace > MAX_INSTRUCTIONS && fail_early {
1475                assert_eq!(
1476                    result,
1477                    Err(InstructionError::MaxInstructionTraceLengthExceeded)
1478                );
1479            } else {
1480                assert!(result.is_ok());
1481            }
1482
1483            transaction_context
1484                .configure_next_cpi_for_tests(
1485                    0,
1486                    vec![InstructionAccount::new(0, false, false)],
1487                    Vec::new(),
1488                )
1489                .unwrap();
1490        }
1491
1492        if !fail_early {
1493            assert_eq!(
1494                transaction_context.push(false),
1495                Err(InstructionError::MaxInstructionTraceLengthExceeded)
1496            );
1497        }
1498    }
1499
1500    #[test_case(MockInstruction::NoopSuccess, Ok(()); "NoopSuccess")]
1501    #[test_case(MockInstruction::NoopFail, Err(InstructionError::GenericError); "NoopFail")]
1502    #[test_case(MockInstruction::ModifyOwned, Ok(()); "ModifyOwned")]
1503    #[test_case(MockInstruction::ModifyNotOwned, Err(InstructionError::ExternalAccountDataModified); "ModifyNotOwned")]
1504    #[test_case(MockInstruction::ModifyReadonly, Err(InstructionError::ReadonlyDataModified); "ModifyReadonly")]
1505    #[test_case(MockInstruction::UnbalancedPush, Err(InstructionError::UnbalancedInstruction); "UnbalancedPush")]
1506    #[test_case(MockInstruction::UnbalancedPop, Err(InstructionError::UnbalancedInstruction); "UnbalancedPop")]
1507    fn test_process_instruction_account_modifications(
1508        instruction: MockInstruction,
1509        expected_result: Result<(), InstructionError>,
1510    ) {
1511        let callee_program_id = solana_pubkey::new_rand();
1512        let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1513        let not_owned_account = AccountSharedData::new(84, 1, &solana_pubkey::new_rand());
1514        let readonly_account = AccountSharedData::new(168, 1, &solana_pubkey::new_rand());
1515        let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1516        let mut program_account = AccountSharedData::new(1, 1, &native_loader::id());
1517        program_account.set_executable(true);
1518        let transaction_accounts = vec![
1519            (solana_pubkey::new_rand(), owned_account),
1520            (solana_pubkey::new_rand(), not_owned_account),
1521            (solana_pubkey::new_rand(), readonly_account),
1522            (callee_program_id, program_account),
1523            (solana_pubkey::new_rand(), loader_account),
1524        ];
1525        let metas = vec![
1526            AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1527            AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1528            AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1529        ];
1530        let instruction_accounts = (0..4)
1531            .map(|instruction_account_index| {
1532                InstructionAccount::new(
1533                    instruction_account_index,
1534                    false,
1535                    instruction_account_index < 2,
1536                )
1537            })
1538            .collect::<Vec<_>>();
1539        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1540        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1541        program_cache_for_tx_batch.replenish(
1542            callee_program_id,
1543            Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
1544        );
1545        invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1546
1547        // Account modification tests
1548        invoke_context
1549            .transaction_context
1550            .configure_top_level_instruction_for_tests(4, instruction_accounts, vec![])
1551            .unwrap();
1552        invoke_context.push().unwrap();
1553        let inner_instruction =
1554            Instruction::new_with_wincode(callee_program_id, &instruction, metas);
1555        let result = invoke_context
1556            .native_invoke_signed(inner_instruction, &[])
1557            .and(invoke_context.pop());
1558        assert_eq!(result, expected_result);
1559    }
1560
1561    #[test_case(Ok(()); "Ok")]
1562    #[test_case(Err(InstructionError::GenericError); "GenericError")]
1563    fn test_process_instruction_compute_unit_consumption(
1564        expected_result: Result<(), InstructionError>,
1565    ) {
1566        let callee_program_id = solana_pubkey::new_rand();
1567        let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1568        let not_owned_account = AccountSharedData::new(84, 1, &solana_pubkey::new_rand());
1569        let readonly_account = AccountSharedData::new(168, 1, &solana_pubkey::new_rand());
1570        let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1571        let mut program_account = AccountSharedData::new(1, 1, &native_loader::id());
1572        program_account.set_executable(true);
1573        let transaction_accounts = vec![
1574            (solana_pubkey::new_rand(), owned_account),
1575            (solana_pubkey::new_rand(), not_owned_account),
1576            (solana_pubkey::new_rand(), readonly_account),
1577            (callee_program_id, program_account),
1578            (solana_pubkey::new_rand(), loader_account),
1579        ];
1580        let metas = vec![
1581            AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1582            AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1583            AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1584        ];
1585        let instruction_accounts = (0..4)
1586            .map(|instruction_account_index| {
1587                InstructionAccount::new(
1588                    instruction_account_index,
1589                    false,
1590                    instruction_account_index < 2,
1591                )
1592            })
1593            .collect::<Vec<_>>();
1594        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1595        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1596        program_cache_for_tx_batch.replenish(
1597            callee_program_id,
1598            Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
1599        );
1600        invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1601
1602        // Compute unit consumption tests
1603        let compute_units_to_consume = 10;
1604        invoke_context
1605            .transaction_context
1606            .configure_top_level_instruction_for_tests(4, instruction_accounts, vec![])
1607            .unwrap();
1608        invoke_context.push().unwrap();
1609        let inner_instruction = Instruction::new_with_wincode(
1610            callee_program_id,
1611            &MockInstruction::ConsumeComputeUnits {
1612                compute_units_to_consume,
1613                desired_result: expected_result.clone(),
1614            },
1615            metas,
1616        );
1617
1618        invoke_context
1619            .build_instruction_frame(inner_instruction)
1620            .unwrap();
1621        invoke_context.verify_instruction_accounts(&[]).unwrap();
1622
1623        let mut compute_units_consumed = 0;
1624        let result = invoke_context
1625            .process_instruction(&mut compute_units_consumed, &mut ExecuteTimings::default());
1626
1627        // Because the instruction had compute cost > 0, then regardless of the execution result,
1628        // the number of compute units consumed should be a non-default which is something greater
1629        // than zero.
1630        assert!(compute_units_consumed > 0);
1631        assert_eq!(
1632            compute_units_consumed,
1633            compute_units_to_consume.saturating_add(MOCK_BUILTIN_COMPUTE_UNIT_COST),
1634        );
1635        assert_eq!(result, expected_result);
1636
1637        invoke_context.pop().unwrap();
1638    }
1639
1640    #[test]
1641    fn test_invoke_context_compute_budget() {
1642        let transaction_accounts = vec![(solana_pubkey::new_rand(), AccountSharedData::default())];
1643        let execution_budget = SVMTransactionExecutionBudget {
1644            compute_unit_limit: u64::from(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT),
1645            ..SVMTransactionExecutionBudget::default()
1646        };
1647
1648        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1649        invoke_context.compute_budget = execution_budget;
1650
1651        invoke_context
1652            .transaction_context
1653            .configure_top_level_instruction_for_tests(0, vec![], vec![])
1654            .unwrap();
1655        invoke_context.push().unwrap();
1656        assert_eq!(*invoke_context.get_compute_budget(), execution_budget);
1657        invoke_context.pop().unwrap();
1658    }
1659
1660    #[test_case(0; "Resize the account to *the same size*, so not consuming any additional size")]
1661    #[test_case(1; "Resize the account larger")]
1662    #[test_case(-1; "Resize the account smaller")]
1663    fn test_process_instruction_accounts_resize_delta(resize_delta: i64) {
1664        let program_key = Pubkey::new_unique();
1665        let user_account_data_len = 123u64;
1666        let user_account =
1667            AccountSharedData::new(100, user_account_data_len as usize, &program_key);
1668        let dummy_account = AccountSharedData::new(10, 0, &program_key);
1669        let mut program_account = AccountSharedData::new(500, 500, &native_loader::id());
1670        program_account.set_executable(true);
1671        let transaction_accounts = vec![
1672            (Pubkey::new_unique(), user_account),
1673            (Pubkey::new_unique(), dummy_account),
1674            (program_key, program_account),
1675        ];
1676        let instruction_accounts = vec![
1677            InstructionAccount::new(0, false, true),
1678            InstructionAccount::new(1, false, false),
1679        ];
1680        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1681        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1682        program_cache_for_tx_batch.replenish(
1683            program_key,
1684            Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
1685        );
1686        invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1687
1688        let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
1689        let instruction_data = wincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1690
1691        invoke_context
1692            .transaction_context
1693            .configure_top_level_instruction_for_tests(2, instruction_accounts, instruction_data)
1694            .unwrap();
1695        let result = invoke_context.process_instruction(&mut 0, &mut ExecuteTimings::default());
1696
1697        assert!(result.is_ok());
1698        assert_eq!(
1699            invoke_context.transaction_context.accounts().resize_delta(),
1700            resize_delta
1701        );
1702    }
1703
1704    #[test]
1705    fn test_prepare_instruction_maximum_accounts() {
1706        const MAX_ACCOUNTS_REFERENCED: usize = u16::MAX as usize;
1707        let mut transaction_accounts: Vec<KeyedAccountSharedData> =
1708            Vec::with_capacity(MAX_ACCOUNTS_PER_TRANSACTION);
1709        let mut account_metas: Vec<AccountMeta> = Vec::with_capacity(MAX_ACCOUNTS_REFERENCED);
1710
1711        // Fee-payer
1712        let fee_payer = Keypair::new();
1713        transaction_accounts.push((
1714            fee_payer.pubkey(),
1715            AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1716        ));
1717        account_metas.push(AccountMeta::new(fee_payer.pubkey(), true));
1718
1719        let program_id = Pubkey::new_unique();
1720        let mut program_account = AccountSharedData::new(1, 1, &Pubkey::new_unique());
1721        program_account.set_executable(true);
1722        transaction_accounts.push((program_id, program_account));
1723        account_metas.push(AccountMeta::new_readonly(program_id, false));
1724
1725        for i in 2..MAX_ACCOUNTS_REFERENCED {
1726            // Let's reference 256 unique accounts, and the rest is repeated.
1727            if i < MAX_ACCOUNTS_PER_TRANSACTION {
1728                let key = Pubkey::new_unique();
1729                transaction_accounts
1730                    .push((key, AccountSharedData::new(1, 1, &Pubkey::new_unique())));
1731                account_metas.push(AccountMeta::new_readonly(key, false));
1732            } else {
1733                let repeated_key = transaction_accounts
1734                    .get(i % MAX_ACCOUNTS_PER_TRANSACTION)
1735                    .unwrap()
1736                    .0;
1737                account_metas.push(AccountMeta::new_readonly(repeated_key, false));
1738            }
1739        }
1740
1741        with_mock_invoke_context!(invoke_context, transaction_context, 2, transaction_accounts);
1742
1743        let instruction_1 = Instruction::new_with_bytes(program_id, &[20], account_metas.clone());
1744
1745        let instruction_2 = Instruction::new_with_bytes(
1746            program_id,
1747            &[20],
1748            account_metas.iter().rev().cloned().collect(),
1749        );
1750
1751        let transaction = Transaction::new_with_payer(
1752            &[instruction_1.clone(), instruction_2.clone()],
1753            Some(&fee_payer.pubkey()),
1754        );
1755
1756        let sanitized =
1757            SanitizedTransaction::try_from_legacy_transaction(transaction, &HashSet::new())
1758                .unwrap();
1759
1760        fn test_case_1(invoke_context: &InvokeContext) {
1761            let instruction_context = invoke_context
1762                .transaction_context
1763                .get_next_instruction_context()
1764                .unwrap();
1765            for index_in_instruction in 0..MAX_ACCOUNTS_REFERENCED as IndexOfAccount {
1766                let index_in_transaction = instruction_context
1767                    .get_index_of_instruction_account_in_transaction(index_in_instruction)
1768                    .unwrap();
1769                let other_ix_index = instruction_context
1770                    .get_index_of_account_in_instruction(index_in_transaction)
1771                    .unwrap();
1772                if (index_in_instruction as usize) < MAX_ACCOUNTS_PER_TRANSACTION {
1773                    assert_eq!(index_in_instruction, index_in_transaction);
1774                    assert_eq!(index_in_instruction, other_ix_index);
1775                } else {
1776                    assert_eq!(
1777                        index_in_instruction as usize % MAX_ACCOUNTS_PER_TRANSACTION,
1778                        index_in_transaction as usize
1779                    );
1780                    assert_eq!(
1781                        index_in_instruction as usize % MAX_ACCOUNTS_PER_TRANSACTION,
1782                        other_ix_index as usize
1783                    );
1784                }
1785            }
1786        }
1787
1788        fn test_case_2(invoke_context: &InvokeContext) {
1789            let instruction_context = invoke_context
1790                .transaction_context
1791                .get_next_instruction_context()
1792                .unwrap();
1793            for index_in_instruction in 0..MAX_ACCOUNTS_REFERENCED as IndexOfAccount {
1794                let index_in_transaction = instruction_context
1795                    .get_index_of_instruction_account_in_transaction(index_in_instruction)
1796                    .unwrap();
1797                let other_ix_index = instruction_context
1798                    .get_index_of_account_in_instruction(index_in_transaction)
1799                    .unwrap();
1800                assert_eq!(
1801                    index_in_transaction,
1802                    (MAX_ACCOUNTS_REFERENCED as u16)
1803                        .saturating_sub(index_in_instruction)
1804                        .saturating_sub(1)
1805                        .overflowing_rem(MAX_ACCOUNTS_PER_TRANSACTION as u16)
1806                        .0
1807                );
1808                if (index_in_instruction as usize) < MAX_ACCOUNTS_PER_TRANSACTION {
1809                    assert_eq!(index_in_instruction, other_ix_index);
1810                } else {
1811                    assert_eq!(
1812                        index_in_instruction as usize % MAX_ACCOUNTS_PER_TRANSACTION,
1813                        other_ix_index as usize
1814                    );
1815                }
1816            }
1817        }
1818
1819        invoke_context
1820            .prepare_top_level_instructions(&sanitized)
1821            .unwrap();
1822
1823        test_case_1(&invoke_context);
1824
1825        invoke_context.transaction_context.push(true).unwrap();
1826        invoke_context.transaction_context.pop().unwrap();
1827
1828        test_case_2(&invoke_context);
1829
1830        invoke_context.transaction_context.push(true).unwrap();
1831        invoke_context
1832            .build_instruction_frame(instruction_1)
1833            .unwrap();
1834        invoke_context
1835            .verify_instruction_accounts(&[fee_payer.pubkey()])
1836            .unwrap();
1837        test_case_1(&invoke_context);
1838
1839        invoke_context.transaction_context.push(true).unwrap();
1840        invoke_context
1841            .build_instruction_frame(instruction_2)
1842            .unwrap();
1843        invoke_context
1844            .verify_instruction_accounts(&[fee_payer.pubkey()])
1845            .unwrap();
1846        test_case_2(&invoke_context);
1847    }
1848
1849    #[test]
1850    fn test_duplicated_accounts() {
1851        let mut transaction_accounts: Vec<KeyedAccountSharedData> =
1852            Vec::with_capacity(MAX_ACCOUNTS_PER_TRANSACTION);
1853        let mut account_metas: Vec<AccountMeta> =
1854            Vec::with_capacity(MAX_ACCOUNTS_PER_INSTRUCTION.saturating_sub(1));
1855
1856        // Fee-payer
1857        let fee_payer = Keypair::new();
1858        transaction_accounts.push((
1859            fee_payer.pubkey(),
1860            AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1861        ));
1862        account_metas.push(AccountMeta::new(fee_payer.pubkey(), true));
1863
1864        let program_id = Pubkey::new_unique();
1865        let mut program_account = AccountSharedData::new(1, 1, &Pubkey::new_unique());
1866        program_account.set_executable(true);
1867        transaction_accounts.push((program_id, program_account));
1868        account_metas.push(AccountMeta::new_readonly(program_id, false));
1869
1870        for i in 2..account_metas.capacity() {
1871            if i % 2 == 0 {
1872                let key = Pubkey::new_unique();
1873                transaction_accounts
1874                    .push((key, AccountSharedData::new(1, 1, &Pubkey::new_unique())));
1875                account_metas.push(AccountMeta::new_readonly(key, false));
1876            } else {
1877                let last_key = transaction_accounts.last().unwrap().0;
1878                account_metas.push(AccountMeta::new_readonly(last_key, false));
1879            }
1880        }
1881
1882        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1883
1884        let instruction = Instruction::new_with_bytes(program_id, &[20], account_metas.clone());
1885
1886        let transaction = Transaction::new_with_payer(&[instruction], Some(&fee_payer.pubkey()));
1887
1888        let sanitized =
1889            SanitizedTransaction::try_from_legacy_transaction(transaction, &HashSet::new())
1890                .unwrap();
1891
1892        invoke_context
1893            .prepare_top_level_instructions(&sanitized)
1894            .unwrap();
1895
1896        {
1897            let instruction_context = invoke_context
1898                .transaction_context
1899                .get_next_instruction_context()
1900                .unwrap();
1901            for index_in_instruction in 2..account_metas.len() as IndexOfAccount {
1902                let is_duplicate = instruction_context
1903                    .is_instruction_account_duplicate(index_in_instruction)
1904                    .unwrap();
1905                if index_in_instruction % 2 == 0 {
1906                    assert!(is_duplicate.is_none());
1907                } else {
1908                    assert_eq!(is_duplicate, Some(index_in_instruction.saturating_sub(1)));
1909                }
1910            }
1911        }
1912
1913        invoke_context.transaction_context.push(true).unwrap();
1914
1915        let instruction = Instruction::new_with_bytes(
1916            program_id,
1917            &[20],
1918            account_metas.iter().cloned().rev().collect(),
1919        );
1920
1921        invoke_context.build_instruction_frame(instruction).unwrap();
1922        invoke_context
1923            .verify_instruction_accounts(&[fee_payer.pubkey()])
1924            .unwrap();
1925        let instruction_context = invoke_context
1926            .transaction_context
1927            .get_next_instruction_context()
1928            .unwrap();
1929        for index_in_instruction in 2..account_metas.len().saturating_sub(1) as u16 {
1930            let is_duplicate = instruction_context
1931                .is_instruction_account_duplicate(index_in_instruction)
1932                .unwrap();
1933            if index_in_instruction % 2 == 0 {
1934                assert!(is_duplicate.is_none());
1935            } else {
1936                assert_eq!(is_duplicate, Some(index_in_instruction.saturating_sub(1)));
1937            }
1938        }
1939    }
1940
1941    // Used for native_invoke_signed tests below.
1942    const TEST_CALLER_PROGRAM_ID: Pubkey = Pubkey::new_from_array([1u8; 32]);
1943    const TEST_CALLEE_PROGRAM_ID: Pubkey = Pubkey::new_from_array([2u8; 32]);
1944    const TEST_WRONG_PROGRAM_ID: Pubkey = Pubkey::new_from_array([3u8; 32]);
1945    const TEST_MOCK_EXTRA_KEY: Pubkey = Pubkey::new_from_array([4u8; 32]);
1946    const TEST_ACCOUNT_KEY: Pubkey = Pubkey::new_from_array([5u8; 32]);
1947
1948    /// Runs a `native_invoke_signed` call with the standard test setup and returns
1949    /// the result.
1950    ///
1951    /// Same layout for all tests:
1952    ///   0: target account (writable, signer iff `target_is_signer`)
1953    ///   1: caller program (executable)
1954    ///   2: mock extra (satisfies MockBuiltin's 2-account requirement)
1955    ///   3: callee program (executable)
1956    fn run_native_invoke_signed_test(
1957        target_key: Pubkey,
1958        target_is_signer: bool,
1959        inner_instruction: Instruction,
1960        signer_seeds: &[&[&[u8]]],
1961    ) -> Result<(), InstructionError> {
1962        let target_account = AccountSharedData::new(100, 0, &TEST_CALLEE_PROGRAM_ID);
1963        let mock_extra_account = AccountSharedData::new(0, 1, &system_program::id());
1964        let mut caller_program_account = AccountSharedData::new(1, 1, &native_loader::id());
1965        caller_program_account.set_executable(true);
1966        let mut callee_program_account = AccountSharedData::new(1, 1, &native_loader::id());
1967        callee_program_account.set_executable(true);
1968        let transaction_accounts = vec![
1969            (target_key, target_account),
1970            (TEST_CALLER_PROGRAM_ID, caller_program_account),
1971            (TEST_MOCK_EXTRA_KEY, mock_extra_account),
1972            (TEST_CALLEE_PROGRAM_ID, callee_program_account),
1973        ];
1974
1975        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1976        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1977        program_cache_for_tx_batch.replenish(
1978            TEST_CALLEE_PROGRAM_ID,
1979            Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
1980        );
1981        invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1982
1983        let instruction_accounts = (0..4)
1984            .map(|i| InstructionAccount::new(i, i == 0 && target_is_signer, i < 2))
1985            .collect::<Vec<_>>();
1986        invoke_context
1987            .transaction_context
1988            .configure_top_level_instruction_for_tests(1, instruction_accounts, vec![])
1989            .unwrap();
1990        invoke_context.push().unwrap();
1991
1992        let result = invoke_context.native_invoke_signed(inner_instruction, signer_seeds);
1993        invoke_context.pop().unwrap();
1994        result
1995    }
1996
1997    // Valid PDA seeds grant signer privilege to the derived address.
1998    #[test]
1999    fn test_native_invoke_signed_with_valid_pda_signer() {
2000        let (pda_key, bump_seed) =
2001            Pubkey::find_program_address(&[b"seed"], &TEST_CALLER_PROGRAM_ID);
2002        let instruction = Instruction::new_with_wincode(
2003            TEST_CALLEE_PROGRAM_ID,
2004            &MockInstruction::NoopSuccess,
2005            vec![
2006                AccountMeta::new(pda_key, true),
2007                AccountMeta::new_readonly(TEST_MOCK_EXTRA_KEY, false),
2008            ],
2009        );
2010        let result =
2011            run_native_invoke_signed_test(pda_key, false, instruction, &[&[b"seed", &[bump_seed]]]);
2012        assert!(
2013            result.is_ok(),
2014            "valid PDA signer should succeed: {result:?}"
2015        );
2016    }
2017
2018    // Oversized seeds (>MAX_SEED_LEN) hit `MaxSeedLengthExceeded`
2019    // (discriminant 0) which the broken `as u64` num-traits conversion
2020    // maps to `Custom(0)`.
2021    #[test]
2022    fn test_native_invoke_signed_with_invalid_seeds() {
2023        let instruction = Instruction::new_with_wincode(
2024            TEST_CALLEE_PROGRAM_ID,
2025            &MockInstruction::NoopSuccess,
2026            vec![AccountMeta::new(TEST_ACCOUNT_KEY, true)],
2027        );
2028        let oversized_seed = [0u8; 33];
2029        let result = run_native_invoke_signed_test(
2030            TEST_ACCOUNT_KEY,
2031            false,
2032            instruction,
2033            &[&[&oversized_seed]],
2034        );
2035        assert_eq!(result, Err(InstructionError::Custom(0)));
2036    }
2037
2038    // CPI marks an account as signer but caller provides no seeds —
2039    // signer privilege escalation.
2040    #[test]
2041    fn test_native_invoke_signed_pda_privilege_escalation_without_seeds() {
2042        let (pda_key, _bump_seed) =
2043            Pubkey::find_program_address(&[b"seed"], &TEST_CALLER_PROGRAM_ID);
2044        let instruction = Instruction::new_with_wincode(
2045            TEST_CALLEE_PROGRAM_ID,
2046            &MockInstruction::NoopSuccess,
2047            vec![AccountMeta::new(pda_key, true)],
2048        );
2049        let result = run_native_invoke_signed_test(pda_key, false, instruction, &[]);
2050        assert_eq!(result, Err(InstructionError::PrivilegeEscalation));
2051    }
2052
2053    // Seeds valid for a different program ID don't grant signer privilege
2054    // because native_invoke_signed derives against the caller's own program ID.
2055    #[test]
2056    fn test_native_invoke_signed_uses_caller_program_id_for_pda() {
2057        let (pda_key, bump_seed) = Pubkey::find_program_address(&[b"seed"], &TEST_WRONG_PROGRAM_ID);
2058        let instruction = Instruction::new_with_wincode(
2059            TEST_CALLEE_PROGRAM_ID,
2060            &MockInstruction::NoopSuccess,
2061            vec![AccountMeta::new(pda_key, true)],
2062        );
2063        let result =
2064            run_native_invoke_signed_test(pda_key, false, instruction, &[&[b"seed", &[bump_seed]]]);
2065        assert_eq!(result, Err(InstructionError::PrivilegeEscalation));
2066    }
2067
2068    // Top-level signer privilege carries through CPI without needing seeds.
2069    #[test]
2070    fn test_native_invoke_signed_top_level_signer_needs_no_seeds() {
2071        let (pda_key, _bump_seed) =
2072            Pubkey::find_program_address(&[b"seed"], &TEST_CALLER_PROGRAM_ID);
2073        let instruction = Instruction::new_with_wincode(
2074            TEST_CALLEE_PROGRAM_ID,
2075            &MockInstruction::NoopSuccess,
2076            vec![
2077                AccountMeta::new(pda_key, true),
2078                AccountMeta::new_readonly(TEST_MOCK_EXTRA_KEY, false),
2079            ],
2080        );
2081        let result = run_native_invoke_signed_test(pda_key, true, instruction, &[]);
2082        assert!(
2083            result.is_ok(),
2084            "top-level signer should not need seeds: {result:?}"
2085        );
2086    }
2087
2088    #[test]
2089    fn test_compile_message() {
2090        let program_id = Pubkey::new_from_array([1u8; 32]);
2091        let writable = Pubkey::new_from_array([2u8; 32]);
2092        let loader_key = Pubkey::new_from_array([3u8; 32]);
2093
2094        let instruction = Instruction {
2095            program_id,
2096            accounts: vec![AccountMeta::new(writable, false)],
2097            data: vec![1, 2, 3],
2098        };
2099
2100        let accounts = vec![(
2101            writable,
2102            Account {
2103                lamports: 100,
2104                ..Account::default()
2105            },
2106        )];
2107
2108        let (message, tx_accounts) =
2109            mock_compile_message(&instruction, &accounts, &program_id, &loader_key);
2110
2111        assert_eq!(message.instructions().len(), 1);
2112        assert_eq!(tx_accounts.len(), 2);
2113        assert_eq!(tx_accounts.first().unwrap().0, writable);
2114        assert_eq!(tx_accounts.get(1).unwrap().0, program_id);
2115
2116        // Verify the writable account is NOT promoted to signer.
2117        assert!(!message.is_signer(0));
2118    }
2119
2120    struct MockCallback {}
2121    impl InvokeContextCallback for MockCallback {}
2122
2123    fn create_loadable_account_for_test(name: &str) -> AccountSharedData {
2124        let (lamports, rent_epoch) = DUMMY_INHERITABLE_ACCOUNT_FIELDS;
2125        AccountSharedData::from(Account {
2126            lamports,
2127            owner: native_loader::id(),
2128            data: name.as_bytes().to_vec(),
2129            executable: true,
2130            rent_epoch,
2131        })
2132    }
2133
2134    fn new_sanitized_message(message: Message) -> SanitizedMessage {
2135        SanitizedMessage::try_from_legacy_message(message, &HashSet::new()).unwrap()
2136    }
2137
2138    #[test]
2139    fn test_process_message_readonly_handling() {
2140        #[derive(wincode::SchemaRead, wincode::SchemaWrite)]
2141        enum MockSystemInstruction {
2142            Correct,
2143            TransferLamports { lamports: u64 },
2144            ChangeData { data: u8 },
2145        }
2146
2147        declare_process_instruction!(MockBuiltin, 1, |invoke_context| {
2148            let transaction_context = &invoke_context.transaction_context;
2149            let instruction_context = transaction_context.get_current_instruction_context()?;
2150            let instruction_data = instruction_context.get_instruction_data();
2151            if let Ok(instruction) = wincode::deserialize(instruction_data) {
2152                match instruction {
2153                    MockSystemInstruction::Correct => Ok(()),
2154                    MockSystemInstruction::TransferLamports { lamports } => {
2155                        instruction_context
2156                            .try_borrow_instruction_account(0)?
2157                            .checked_sub_lamports(lamports)?;
2158                        instruction_context
2159                            .try_borrow_instruction_account(1)?
2160                            .checked_add_lamports(lamports)?;
2161                        Ok(())
2162                    }
2163                    MockSystemInstruction::ChangeData { data } => {
2164                        instruction_context
2165                            .try_borrow_instruction_account(1)?
2166                            .set_data_from_slice(&[data])?;
2167                        Ok(())
2168                    }
2169                }
2170            } else {
2171                Err(InstructionError::InvalidInstructionData)
2172            }
2173        });
2174
2175        let writable_pubkey = Pubkey::new_unique();
2176        let readonly_pubkey = Pubkey::new_unique();
2177        let mock_system_program_id = Pubkey::new_unique();
2178
2179        let accounts = vec![
2180            (
2181                writable_pubkey,
2182                AccountSharedData::new(100, 1, &mock_system_program_id),
2183            ),
2184            (
2185                readonly_pubkey,
2186                AccountSharedData::new(0, 1, &mock_system_program_id),
2187            ),
2188            (
2189                mock_system_program_id,
2190                create_loadable_account_for_test("mock_system_program"),
2191            ),
2192        ];
2193        let mut transaction_context =
2194            TransactionContext::new(accounts.clone(), Rent::default(), 1, 3, 1);
2195        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
2196        program_cache_for_tx_batch.replenish(
2197            mock_system_program_id,
2198            Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
2199        );
2200        let account_keys = (0..transaction_context.get_number_of_accounts())
2201            .map(|index| {
2202                *transaction_context
2203                    .get_key_of_account_at_index(index)
2204                    .unwrap()
2205            })
2206            .collect::<Vec<_>>();
2207        let account_metas = vec![
2208            AccountMeta::new(writable_pubkey, true),
2209            AccountMeta::new_readonly(readonly_pubkey, false),
2210        ];
2211
2212        let message = new_sanitized_message(Message::new_with_compiled_instructions(
2213            1,
2214            0,
2215            2,
2216            account_keys.clone(),
2217            Hash::default(),
2218            AccountKeys::new(&account_keys, None).compile_instructions(&[
2219                Instruction::new_with_wincode(
2220                    mock_system_program_id,
2221                    &MockSystemInstruction::Correct,
2222                    account_metas.clone(),
2223                ),
2224            ]),
2225        ));
2226        let sysvar_cache = SysvarCache::default();
2227        let feature_set = SVMFeatureSet::all_enabled();
2228        let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2229        let environment_config = EnvironmentConfig::new(
2230            Hash::default(),
2231            0,
2232            false,
2233            &MockCallback {},
2234            &feature_set,
2235            &program_runtime_environments,
2236            &sysvar_cache,
2237        );
2238        let mut invoke_context = InvokeContext::new(
2239            &mut transaction_context,
2240            &mut program_cache_for_tx_batch,
2241            environment_config,
2242            None,
2243            SVMTransactionExecutionBudget::default(),
2244            SVMTransactionExecutionCost::default(),
2245        );
2246        let result =
2247            invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2248        assert!(result.is_ok());
2249        assert_eq!(
2250            transaction_context
2251                .accounts()
2252                .try_borrow(0)
2253                .unwrap()
2254                .lamports(),
2255            100
2256        );
2257        assert_eq!(
2258            transaction_context
2259                .accounts()
2260                .try_borrow(1)
2261                .unwrap()
2262                .lamports(),
2263            0
2264        );
2265
2266        let message = new_sanitized_message(Message::new_with_compiled_instructions(
2267            1,
2268            0,
2269            2,
2270            account_keys.clone(),
2271            Hash::default(),
2272            AccountKeys::new(&account_keys, None).compile_instructions(&[
2273                Instruction::new_with_wincode(
2274                    mock_system_program_id,
2275                    &MockSystemInstruction::TransferLamports { lamports: 50 },
2276                    account_metas.clone(),
2277                ),
2278            ]),
2279        ));
2280        let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2281        let environment_config = EnvironmentConfig::new(
2282            Hash::default(),
2283            0,
2284            false,
2285            &MockCallback {},
2286            &feature_set,
2287            &program_runtime_environments,
2288            &sysvar_cache,
2289        );
2290        let mut transaction_context =
2291            TransactionContext::new(accounts.clone(), Rent::default(), 1, 3, 1);
2292        let mut invoke_context = InvokeContext::new(
2293            &mut transaction_context,
2294            &mut program_cache_for_tx_batch,
2295            environment_config,
2296            None,
2297            SVMTransactionExecutionBudget::default(),
2298            SVMTransactionExecutionCost::default(),
2299        );
2300        let result =
2301            invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2302        assert_eq!(result, Err((0, InstructionError::ReadonlyLamportChange)));
2303
2304        let message = new_sanitized_message(Message::new_with_compiled_instructions(
2305            1,
2306            0,
2307            2,
2308            account_keys.clone(),
2309            Hash::default(),
2310            AccountKeys::new(&account_keys, None).compile_instructions(&[
2311                Instruction::new_with_wincode(
2312                    mock_system_program_id,
2313                    &MockSystemInstruction::ChangeData { data: 50 },
2314                    account_metas,
2315                ),
2316            ]),
2317        ));
2318        let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2319        let environment_config = EnvironmentConfig::new(
2320            Hash::default(),
2321            0,
2322            false,
2323            &MockCallback {},
2324            &feature_set,
2325            &program_runtime_environments,
2326            &sysvar_cache,
2327        );
2328        let mut transaction_context = TransactionContext::new(accounts, Rent::default(), 1, 3, 1);
2329        let mut invoke_context = InvokeContext::new(
2330            &mut transaction_context,
2331            &mut program_cache_for_tx_batch,
2332            environment_config,
2333            None,
2334            SVMTransactionExecutionBudget::default(),
2335            SVMTransactionExecutionCost::default(),
2336        );
2337        let result =
2338            invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2339        assert_eq!(result, Err((0, InstructionError::ReadonlyDataModified)));
2340    }
2341
2342    #[test]
2343    fn test_process_message_duplicate_accounts() {
2344        #[derive(wincode::SchemaRead, wincode::SchemaWrite)]
2345        enum MockSystemInstruction {
2346            BorrowFail,
2347            MultiBorrowMut,
2348            DoWork { lamports: u64, data: u8 },
2349        }
2350
2351        declare_process_instruction!(MockBuiltin, 1, |invoke_context| {
2352            let transaction_context = &invoke_context.transaction_context;
2353            let instruction_context = transaction_context.get_current_instruction_context()?;
2354            let instruction_data = instruction_context.get_instruction_data();
2355            let mut to_account = instruction_context.try_borrow_instruction_account(1)?;
2356            if let Ok(instruction) = wincode::deserialize(instruction_data) {
2357                match instruction {
2358                    MockSystemInstruction::BorrowFail => {
2359                        let from_account = instruction_context.try_borrow_instruction_account(0)?;
2360                        let dup_account = instruction_context.try_borrow_instruction_account(2)?;
2361                        if from_account.get_lamports() != dup_account.get_lamports() {
2362                            return Err(InstructionError::InvalidArgument);
2363                        }
2364                        Ok(())
2365                    }
2366                    MockSystemInstruction::MultiBorrowMut => {
2367                        let lamports_a = instruction_context
2368                            .try_borrow_instruction_account(0)?
2369                            .get_lamports();
2370                        let lamports_b = instruction_context
2371                            .try_borrow_instruction_account(2)?
2372                            .get_lamports();
2373                        if lamports_a != lamports_b {
2374                            return Err(InstructionError::InvalidArgument);
2375                        }
2376                        Ok(())
2377                    }
2378                    MockSystemInstruction::DoWork { lamports, data } => {
2379                        let mut dup_account =
2380                            instruction_context.try_borrow_instruction_account(2)?;
2381                        dup_account.checked_sub_lamports(lamports)?;
2382                        to_account.checked_add_lamports(lamports)?;
2383                        dup_account.set_data_from_slice(&[data])?;
2384                        drop(dup_account);
2385                        let mut from_account =
2386                            instruction_context.try_borrow_instruction_account(0)?;
2387                        from_account.checked_sub_lamports(lamports)?;
2388                        to_account.checked_add_lamports(lamports)?;
2389                        Ok(())
2390                    }
2391                }
2392            } else {
2393                Err(InstructionError::InvalidInstructionData)
2394            }
2395        });
2396        let mock_program_id = Pubkey::from([2u8; 32]);
2397        let accounts = vec![
2398            (
2399                solana_pubkey::new_rand(),
2400                AccountSharedData::new(100, 1, &mock_program_id),
2401            ),
2402            (
2403                solana_pubkey::new_rand(),
2404                AccountSharedData::new(0, 1, &mock_program_id),
2405            ),
2406            (
2407                mock_program_id,
2408                create_loadable_account_for_test("mock_system_program"),
2409            ),
2410        ];
2411        let mut transaction_context =
2412            TransactionContext::new(accounts.clone(), Rent::default(), 1, 3, 1);
2413        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
2414        program_cache_for_tx_batch.replenish(
2415            mock_program_id,
2416            Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
2417        );
2418        let account_metas = vec![
2419            AccountMeta::new(
2420                *transaction_context.get_key_of_account_at_index(0).unwrap(),
2421                true,
2422            ),
2423            AccountMeta::new(
2424                *transaction_context.get_key_of_account_at_index(1).unwrap(),
2425                false,
2426            ),
2427            AccountMeta::new(
2428                *transaction_context.get_key_of_account_at_index(0).unwrap(),
2429                false,
2430            ),
2431        ];
2432
2433        // Try to borrow mut the same account
2434        let message = new_sanitized_message(Message::new(
2435            &[Instruction::new_with_wincode(
2436                mock_program_id,
2437                &MockSystemInstruction::BorrowFail,
2438                account_metas.clone(),
2439            )],
2440            Some(transaction_context.get_key_of_account_at_index(0).unwrap()),
2441        ));
2442        let sysvar_cache = SysvarCache::default();
2443        let feature_set = SVMFeatureSet::all_enabled();
2444        let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2445        let environment_config = EnvironmentConfig::new(
2446            Hash::default(),
2447            0,
2448            false,
2449            &MockCallback {},
2450            &feature_set,
2451            &program_runtime_environments,
2452            &sysvar_cache,
2453        );
2454        let mut invoke_context = InvokeContext::new(
2455            &mut transaction_context,
2456            &mut program_cache_for_tx_batch,
2457            environment_config,
2458            None,
2459            SVMTransactionExecutionBudget::default(),
2460            SVMTransactionExecutionCost::default(),
2461        );
2462        let result =
2463            invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2464        assert_eq!(result, Err((0, InstructionError::AccountBorrowFailed)));
2465
2466        // Try to borrow mut the same account in a safe way
2467        let message = new_sanitized_message(Message::new(
2468            &[Instruction::new_with_wincode(
2469                mock_program_id,
2470                &MockSystemInstruction::MultiBorrowMut,
2471                account_metas.clone(),
2472            )],
2473            Some(transaction_context.get_key_of_account_at_index(0).unwrap()),
2474        ));
2475        let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2476        let environment_config = EnvironmentConfig::new(
2477            Hash::default(),
2478            0,
2479            false,
2480            &MockCallback {},
2481            &feature_set,
2482            &program_runtime_environments,
2483            &sysvar_cache,
2484        );
2485        let mut transaction_context =
2486            TransactionContext::new(accounts.clone(), Rent::default(), 1, 3, 1);
2487        let mut invoke_context = InvokeContext::new(
2488            &mut transaction_context,
2489            &mut program_cache_for_tx_batch,
2490            environment_config,
2491            None,
2492            SVMTransactionExecutionBudget::default(),
2493            SVMTransactionExecutionCost::default(),
2494        );
2495        let result =
2496            invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2497        assert!(result.is_ok());
2498
2499        // Do work on the same transaction account but at different instruction accounts
2500        let message = new_sanitized_message(Message::new(
2501            &[Instruction::new_with_wincode(
2502                mock_program_id,
2503                &MockSystemInstruction::DoWork {
2504                    lamports: 10,
2505                    data: 42,
2506                },
2507                account_metas,
2508            )],
2509            Some(transaction_context.get_key_of_account_at_index(0).unwrap()),
2510        ));
2511        let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2512        let environment_config = EnvironmentConfig::new(
2513            Hash::default(),
2514            0,
2515            false,
2516            &MockCallback {},
2517            &feature_set,
2518            &program_runtime_environments,
2519            &sysvar_cache,
2520        );
2521        let mut transaction_context = TransactionContext::new(accounts, Rent::default(), 1, 3, 1);
2522        let mut invoke_context = InvokeContext::new(
2523            &mut transaction_context,
2524            &mut program_cache_for_tx_batch,
2525            environment_config,
2526            None,
2527            SVMTransactionExecutionBudget::default(),
2528            SVMTransactionExecutionCost::default(),
2529        );
2530        let result =
2531            invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2532        assert!(result.is_ok());
2533        assert_eq!(
2534            transaction_context
2535                .accounts()
2536                .try_borrow(0)
2537                .unwrap()
2538                .lamports(),
2539            80
2540        );
2541        assert_eq!(
2542            transaction_context
2543                .accounts()
2544                .try_borrow(1)
2545                .unwrap()
2546                .lamports(),
2547            20
2548        );
2549        assert_eq!(
2550            transaction_context.accounts().try_borrow(0).unwrap().data(),
2551            &vec![42]
2552        );
2553    }
2554
2555    fn secp256k1_instruction_for_test() -> Instruction {
2556        let message = b"hello";
2557        let bytes: [u8; 32] = rand::random();
2558        let secret_key = libsecp256k1::SecretKey::parse(&bytes).unwrap();
2559        let pubkey = libsecp256k1::PublicKey::from_secret_key(&secret_key);
2560        let eth_address = eth_address_from_pubkey(&pubkey.serialize()[1..].try_into().unwrap());
2561        let (signature, recovery_id) =
2562            solana_secp256k1_program::sign_message(&secret_key.serialize(), &message[..]).unwrap();
2563        new_secp256k1_instruction_with_signature(
2564            &message[..],
2565            &signature,
2566            recovery_id,
2567            &eth_address,
2568        )
2569    }
2570
2571    fn ed25519_instruction_for_test() -> Instruction {
2572        let keypair = Keypair::new();
2573        let signature = keypair.sign_message(b"hello");
2574        let pubkey = keypair.pubkey().to_bytes();
2575        new_ed25519_instruction_with_signature(b"hello", signature.as_array(), &pubkey)
2576    }
2577
2578    fn secp256r1_instruction_for_test() -> Instruction {
2579        let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap();
2580        let secret_key = EcKey::generate(&group).unwrap();
2581        let signature = sign_message(b"hello", &secret_key.private_key_to_der().unwrap()).unwrap();
2582        let mut ctx = openssl::bn::BigNumContext::new().unwrap();
2583        let pubkey = secret_key
2584            .public_key()
2585            .to_bytes(
2586                &group,
2587                openssl::ec::PointConversionForm::COMPRESSED,
2588                &mut ctx,
2589            )
2590            .unwrap();
2591        new_secp256r1_instruction_with_signature(b"hello", &signature, &pubkey.try_into().unwrap())
2592    }
2593
2594    #[test]
2595    fn test_precompile() {
2596        let mock_program_id = Pubkey::new_unique();
2597        declare_process_instruction!(MockBuiltin, 1, |_invoke_context| {
2598            Err(InstructionError::Custom(0xbabb1e))
2599        });
2600
2601        let mut secp256k1_account = AccountSharedData::new(1, 0, &native_loader::id());
2602        secp256k1_account.set_executable(true);
2603        let mut ed25519_account = AccountSharedData::new(1, 0, &native_loader::id());
2604        ed25519_account.set_executable(true);
2605        let mut secp256r1_account = AccountSharedData::new(1, 0, &native_loader::id());
2606        secp256r1_account.set_executable(true);
2607        let mut mock_program_account = AccountSharedData::new(1, 0, &native_loader::id());
2608        mock_program_account.set_executable(true);
2609
2610        let fee_payer = Pubkey::new_unique();
2611        let accounts_map: HashMap<Address, AccountSharedData> = HashMap::from([
2612            (
2613                fee_payer,
2614                AccountSharedData::new(1, 0, &system_program::id()),
2615            ),
2616            (secp256k1_program::id(), secp256k1_account),
2617            (ed25519_program::id(), ed25519_account),
2618            (solana_secp256r1_program::id(), secp256r1_account),
2619            (mock_program_id, mock_program_account),
2620        ]);
2621
2622        let message = new_sanitized_message(Message::new(
2623            &[
2624                secp256k1_instruction_for_test(),
2625                ed25519_instruction_for_test(),
2626                secp256r1_instruction_for_test(),
2627                Instruction::new_with_bytes(mock_program_id, &[], vec![]),
2628            ],
2629            Some(&fee_payer),
2630        ));
2631
2632        let accounts = message
2633            .account_keys()
2634            .iter()
2635            .map(|key| (*key, accounts_map.get(key).unwrap().clone()))
2636            .collect();
2637        let mut transaction_context = TransactionContext::new(accounts, Rent::default(), 1, 4, 4);
2638
2639        let sysvar_cache = SysvarCache::default();
2640        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
2641        program_cache_for_tx_batch.replenish(
2642            mock_program_id,
2643            Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
2644        );
2645
2646        struct MockCallback {}
2647        impl InvokeContextCallback for MockCallback {
2648            fn is_precompile(&self, program_id: &Pubkey) -> bool {
2649                program_id == &secp256k1_program::id()
2650                    || program_id == &ed25519_program::id()
2651                    || program_id == &solana_secp256r1_program::id()
2652            }
2653
2654            fn process_precompile(
2655                &self,
2656                program_id: &Pubkey,
2657                _data: &[u8],
2658                _instruction_datas: Vec<&[u8]>,
2659            ) -> std::result::Result<(), PrecompileError> {
2660                if self.is_precompile(program_id) {
2661                    Ok(())
2662                } else {
2663                    Err(PrecompileError::InvalidPublicKey)
2664                }
2665            }
2666        }
2667        let feature_set = SVMFeatureSet::all_enabled();
2668        let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2669        let environment_config = EnvironmentConfig::new(
2670            Hash::default(),
2671            0,
2672            false,
2673            &MockCallback {},
2674            &feature_set,
2675            &program_runtime_environments,
2676            &sysvar_cache,
2677        );
2678        let mut invoke_context = InvokeContext::new(
2679            &mut transaction_context,
2680            &mut program_cache_for_tx_batch,
2681            environment_config,
2682            None,
2683            SVMTransactionExecutionBudget::default(),
2684            SVMTransactionExecutionCost::default(),
2685        );
2686        let result =
2687            invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2688
2689        assert_eq!(result, Err((3, InstructionError::Custom(0xbabb1e))));
2690        assert_eq!(
2691            transaction_context.number_of_called_instructions_in_trace(),
2692            4
2693        );
2694    }
2695}