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