Skip to main content

solana_program_runtime/
invoke_context.rs

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