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::{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(
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        );
896        let program_runtime_environments = ProgramRuntimeEnvironments::mock();
897        let environment_config = EnvironmentConfig::new(
898            Hash::default(),
899            0,
900            false,
901            &MockInvokeContextCallback {},
902            $feature_set,
903            &program_runtime_environments,
904            &sysvar_cache,
905        );
906        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
907        let mut $invoke_context = InvokeContext::new(
908            &mut $transaction_context,
909            &mut program_cache_for_tx_batch,
910            environment_config,
911            Some(LogCollector::new_ref()),
912            compute_budget,
913            SVMTransactionExecutionCost::default(),
914        );
915    };
916    (
917        $invoke_context:ident,
918        $transaction_context:ident,
919        $feature_set:ident,
920        $top_level_instructions:literal,
921        $transaction_accounts:expr $(,)?
922    ) => {
923        let transaction_accounts: Vec<(solana_pubkey::Pubkey, solana_account::AccountSharedData)> =
924            $transaction_accounts;
925        $crate::with_mock_invoke_context_with_feature_set!(
926            $invoke_context,
927            $transaction_context,
928            $feature_set,
929            $top_level_instructions,
930            transaction_accounts,
931            &transaction_accounts
932        );
933    };
934    (
935        $invoke_context:ident,
936        $transaction_context:ident,
937        $feature_set:ident,
938        $transaction_accounts:expr $(,)?
939    ) => {
940        $crate::with_mock_invoke_context_with_feature_set!(
941            $invoke_context,
942            $transaction_context,
943            $feature_set,
944            1,
945            $transaction_accounts
946        );
947    };
948}
949
950#[cfg(feature = "dev-context-only-utils")]
951#[macro_export]
952macro_rules! with_mock_invoke_context {
953    (
954        $invoke_context:ident,
955        $transaction_context:ident,
956        $top_level_instructions:literal,
957        $transaction_accounts:expr $(,)?
958    ) => {
959        let feature_set = &solana_svm_feature_set::SVMFeatureSet::default();
960        $crate::with_mock_invoke_context_with_feature_set!(
961            $invoke_context,
962            $transaction_context,
963            feature_set,
964            $top_level_instructions,
965            $transaction_accounts
966        )
967    };
968    (
969        $invoke_context:ident,
970        $transaction_context:ident,
971        $transaction_accounts:expr $(,)?
972    ) => {
973        with_mock_invoke_context!(
974            $invoke_context,
975            $transaction_context,
976            1,
977            $transaction_accounts
978        );
979    };
980}
981
982#[cfg(feature = "dev-context-only-utils")]
983pub fn mock_compile_message<A>(
984    instruction: &Instruction,
985    accounts: &[(Pubkey, A)],
986    program_id: &Pubkey,
987    loader_key: &Pubkey,
988) -> (SanitizedMessage, Vec<(Pubkey, AccountSharedData)>)
989where
990    AccountSharedData: From<A>,
991    A: Clone,
992{
993    let message = Message::new(std::slice::from_ref(instruction), None);
994    let transaction_accounts: Vec<_> = message
995        .account_keys
996        .iter()
997        .map(|key| {
998            let account = accounts
999                .iter()
1000                .find(|(k, _)| k == key)
1001                .map(|(_, a)| AccountSharedData::from(a.clone()))
1002                .unwrap_or_else(|| {
1003                    if key == program_id {
1004                        let mut account = AccountSharedData::new(0, 0, loader_key);
1005                        account.set_executable(true);
1006                        account
1007                    } else {
1008                        AccountSharedData::default()
1009                    }
1010                });
1011            (*key, account)
1012        })
1013        .collect();
1014
1015    let sanitized_message = SanitizedMessage::Legacy(LegacyMessage::new(message, &HashSet::new()));
1016
1017    (sanitized_message, transaction_accounts)
1018}
1019
1020#[cfg(feature = "dev-context-only-utils")]
1021pub fn mock_process_instruction_with_feature_set<
1022    F: FnMut(&mut InvokeContext),
1023    G: FnMut(&mut InvokeContext),
1024>(
1025    program_id: &Pubkey,
1026    instruction_data: &[u8],
1027    mut accounts: Vec<KeyedAccountSharedData>,
1028    instruction_account_metas: Vec<AccountMeta>,
1029    expected_result: Result<(), InstructionError>,
1030    builtin: BuiltinFunctionRegisterer,
1031    mut pre_adjustments: F,
1032    mut post_adjustments: G,
1033    feature_set: &SVMFeatureSet,
1034) -> Vec<AccountSharedData> {
1035    let original_len = accounts.len();
1036    if !accounts
1037        .iter()
1038        .any(|(key, _)| *key == sysvar::epoch_schedule::id())
1039    {
1040        let mut account = AccountSharedData::new(1, solana_epoch_schedule::SIZE, &sysvar::id());
1041        wincode::serialize_into(account.data_as_mut_slice(), &EpochSchedule::default()).unwrap();
1042        accounts.push((sysvar::epoch_schedule::id(), account));
1043    }
1044
1045    let instruction =
1046        Instruction::new_with_bytes(*program_id, instruction_data, instruction_account_metas);
1047    let (sanitized_message, transaction_accounts) =
1048        mock_compile_message(&instruction, &accounts, program_id, &native_loader::id());
1049
1050    let program_owner = accounts
1051        .iter()
1052        .find(|(key, _)| key == program_id)
1053        .map(|(_, acct)| *acct.owner())
1054        .unwrap_or_else(native_loader::id);
1055    let is_builtin = native_loader::check_id(&program_owner);
1056
1057    with_mock_invoke_context_with_feature_set!(
1058        invoke_context,
1059        transaction_context,
1060        feature_set,
1061        1,
1062        transaction_accounts,
1063        &accounts
1064    );
1065
1066    let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1067    program_cache_for_tx_batch.replenish(
1068        if is_builtin {
1069            *program_id
1070        } else {
1071            program_owner
1072        },
1073        Arc::new(ProgramCacheEntry::new_builtin(builtin)),
1074    );
1075    program_cache_for_tx_batch.set_slot_for_tests(
1076        invoke_context
1077            .environment_config
1078            .sysvar_cache()
1079            .get_clock()
1080            .map(|clock| clock.slot)
1081            .unwrap_or(1),
1082    );
1083    invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1084
1085    pre_adjustments(&mut invoke_context);
1086
1087    invoke_context
1088        .prepare_top_level_instructions(&sanitized_message)
1089        .unwrap();
1090
1091    let result = invoke_context.process_instruction(&mut 0, &mut ExecuteTimings::default());
1092    assert_eq!(result, expected_result);
1093    post_adjustments(&mut invoke_context);
1094
1095    let txn_result_keys: Vec<_> = (0..transaction_context.get_number_of_accounts())
1096        .map(|i| *transaction_context.get_key_of_account_at_index(i).unwrap())
1097        .collect();
1098    let txn_result_accounts = transaction_context.deconstruct_without_keys().unwrap();
1099    let txn_result_map = txn_result_keys
1100        .into_iter()
1101        .zip(txn_result_accounts)
1102        .collect::<HashMap<Pubkey, AccountSharedData>>();
1103
1104    accounts
1105        .into_iter()
1106        .take(original_len)
1107        .map(|(key, original)| txn_result_map.get(&key).cloned().unwrap_or(original))
1108        .collect()
1109}
1110
1111#[cfg(feature = "dev-context-only-utils")]
1112pub fn mock_process_instruction<F: FnMut(&mut InvokeContext), G: FnMut(&mut InvokeContext)>(
1113    program_id: &Pubkey,
1114    instruction_data: &[u8],
1115    accounts: Vec<KeyedAccountSharedData>,
1116    instruction_account_metas: Vec<AccountMeta>,
1117    expected_result: Result<(), InstructionError>,
1118    builtin: BuiltinFunctionRegisterer,
1119    pre_adjustments: F,
1120    post_adjustments: G,
1121) -> Vec<AccountSharedData> {
1122    mock_process_instruction_with_feature_set(
1123        program_id,
1124        instruction_data,
1125        accounts,
1126        instruction_account_metas,
1127        expected_result,
1128        builtin,
1129        pre_adjustments,
1130        post_adjustments,
1131        &SVMFeatureSet::all_enabled(),
1132    )
1133}
1134
1135#[cfg(test)]
1136mod tests {
1137    use {
1138        super::*,
1139        crate::execution_budget::{
1140            DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, MAX_INSTRUCTION_STACK_DEPTH,
1141            MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268,
1142        },
1143        openssl::{
1144            ec::{EcGroup, EcKey},
1145            nid::Nid,
1146        },
1147        serde::{Deserialize, Serialize},
1148        solana_account::{Account, DUMMY_INHERITABLE_ACCOUNT_FIELDS, ReadableAccount},
1149        solana_ed25519_program::new_ed25519_instruction_with_signature,
1150        solana_keypair::{Address, Keypair},
1151        solana_message::AccountKeys,
1152        solana_precompile_error::PrecompileError,
1153        solana_rent::Rent,
1154        solana_sbpf::program::BuiltinFunctionDefinition,
1155        solana_sdk_ids::{ed25519_program, secp256k1_program, system_program},
1156        solana_secp256k1_program::{
1157            eth_address_from_pubkey, new_secp256k1_instruction_with_signature,
1158        },
1159        solana_secp256r1_program::{new_secp256r1_instruction_with_signature, sign_message},
1160        solana_signer::Signer,
1161        solana_svm_feature_set::SVMFeatureSet,
1162        solana_transaction::{Transaction, sanitized::SanitizedTransaction},
1163        solana_transaction_context::{MAX_ACCOUNTS_PER_INSTRUCTION, MAX_ACCOUNTS_PER_TRANSACTION},
1164        test_case::test_case,
1165    };
1166
1167    #[derive(Debug, Serialize, Deserialize)]
1168    enum MockInstruction {
1169        NoopSuccess,
1170        NoopFail,
1171        ModifyOwned,
1172        ModifyNotOwned,
1173        ModifyReadonly,
1174        UnbalancedPush,
1175        UnbalancedPop,
1176        ConsumeComputeUnits {
1177            compute_units_to_consume: u64,
1178            desired_result: Result<(), InstructionError>,
1179        },
1180        Resize {
1181            new_len: u64,
1182        },
1183    }
1184
1185    const MOCK_BUILTIN_COMPUTE_UNIT_COST: u64 = 1;
1186
1187    declare_process_instruction!(
1188        MockBuiltin,
1189        MOCK_BUILTIN_COMPUTE_UNIT_COST,
1190        |invoke_context| {
1191            let transaction_context = &invoke_context.transaction_context;
1192            let instruction_context = transaction_context.get_current_instruction_context()?;
1193            let instruction_data = instruction_context.get_instruction_data();
1194            let program_id = instruction_context.get_program_key()?;
1195            let instruction_accounts = (0..4)
1196                .map(|instruction_account_index| {
1197                    InstructionAccount::new(instruction_account_index, false, false)
1198                })
1199                .collect::<Vec<_>>();
1200            assert_eq!(
1201                program_id,
1202                instruction_context
1203                    .try_borrow_instruction_account(0)?
1204                    .get_owner()
1205            );
1206            assert_ne!(
1207                instruction_context
1208                    .try_borrow_instruction_account(1)?
1209                    .get_owner(),
1210                instruction_context.get_key_of_instruction_account(0)?
1211            );
1212
1213            if let Ok(instruction) = bincode::deserialize(instruction_data) {
1214                match instruction {
1215                    MockInstruction::NoopSuccess => (),
1216                    MockInstruction::NoopFail => return Err(InstructionError::GenericError),
1217                    MockInstruction::ModifyOwned => instruction_context
1218                        .try_borrow_instruction_account(0)?
1219                        .set_data_from_slice(&[1])?,
1220                    MockInstruction::ModifyNotOwned => instruction_context
1221                        .try_borrow_instruction_account(1)?
1222                        .set_data_from_slice(&[1])?,
1223                    MockInstruction::ModifyReadonly => instruction_context
1224                        .try_borrow_instruction_account(2)?
1225                        .set_data_from_slice(&[1])?,
1226                    MockInstruction::UnbalancedPush => {
1227                        instruction_context
1228                            .try_borrow_instruction_account(0)?
1229                            .checked_add_lamports(1)?;
1230                        let program_id = *transaction_context.get_key_of_account_at_index(3)?;
1231                        let metas = vec![
1232                            AccountMeta::new_readonly(
1233                                *transaction_context.get_key_of_account_at_index(0)?,
1234                                false,
1235                            ),
1236                            AccountMeta::new_readonly(
1237                                *transaction_context.get_key_of_account_at_index(1)?,
1238                                false,
1239                            ),
1240                        ];
1241                        let inner_instruction = Instruction::new_with_bincode(
1242                            program_id,
1243                            &MockInstruction::NoopSuccess,
1244                            metas,
1245                        );
1246                        invoke_context
1247                            .transaction_context
1248                            .configure_top_level_instruction_for_tests(
1249                                3,
1250                                instruction_accounts,
1251                                vec![],
1252                            )
1253                            .unwrap();
1254                        let result = invoke_context.push();
1255                        assert_eq!(result, Err(InstructionError::UnbalancedInstruction));
1256                        result?;
1257                        invoke_context
1258                            .native_invoke_signed(inner_instruction, &[])
1259                            .and(invoke_context.pop())?;
1260                    }
1261                    MockInstruction::UnbalancedPop => instruction_context
1262                        .try_borrow_instruction_account(0)?
1263                        .checked_add_lamports(1)?,
1264                    MockInstruction::ConsumeComputeUnits {
1265                        compute_units_to_consume,
1266                        desired_result,
1267                    } => {
1268                        invoke_context
1269                            .compute_meter
1270                            .consume_checked(compute_units_to_consume)
1271                            .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
1272                        return desired_result;
1273                    }
1274                    MockInstruction::Resize { new_len } => instruction_context
1275                        .try_borrow_instruction_account(0)?
1276                        .set_data_from_slice(&vec![0; new_len as usize])?,
1277                }
1278            } else {
1279                return Err(InstructionError::InvalidInstructionData);
1280            }
1281            Ok(())
1282        }
1283    );
1284
1285    #[test_case(false; "SIMD-0268 disabled")]
1286    #[test_case(true; "SIMD-0268 enabled")]
1287    fn test_instruction_stack_height(simd_0268_active: bool) {
1288        let feature_set = &SVMFeatureSet {
1289            raise_cpi_nesting_limit_to_8: simd_0268_active,
1290            ..SVMFeatureSet::all_enabled()
1291        };
1292        let max_depth = SVMTransactionExecutionBudget::new_with_defaults(simd_0268_active)
1293            .max_instruction_stack_depth;
1294        assert_eq!(
1295            max_depth,
1296            if simd_0268_active {
1297                MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268
1298            } else {
1299                MAX_INSTRUCTION_STACK_DEPTH
1300            },
1301        );
1302
1303        // Set up max_depth + 1 accounts (one extra to trigger the failing push)
1304        // and a matching program account for each.
1305        let mut invoke_stack = vec![];
1306        let mut transaction_accounts = vec![];
1307        let mut instruction_accounts = vec![];
1308        for index in 0..max_depth.saturating_add(1) {
1309            let program_id = solana_pubkey::new_rand();
1310            invoke_stack.push(program_id);
1311            transaction_accounts.push((
1312                solana_pubkey::new_rand(),
1313                AccountSharedData::new(1, 1, &program_id),
1314            ));
1315            instruction_accounts.push(InstructionAccount::new(
1316                index as IndexOfAccount,
1317                false,
1318                true,
1319            ));
1320        }
1321
1322        // Append program accounts after the regular accounts so that
1323        // `first_program_account + depth` indexes the right program.
1324        let first_program_account = transaction_accounts.len();
1325        for (index, program_id) in invoke_stack.iter().enumerate() {
1326            transaction_accounts.push((
1327                *program_id,
1328                AccountSharedData::new(1, 1, &solana_pubkey::Pubkey::default()),
1329            ));
1330            instruction_accounts.push(InstructionAccount::new(
1331                index as IndexOfAccount,
1332                false,
1333                false,
1334            ));
1335        }
1336        with_mock_invoke_context_with_feature_set!(
1337            invoke_context,
1338            transaction_context,
1339            feature_set,
1340            transaction_accounts,
1341        );
1342
1343        // Each push must succeed and the stack height must track.
1344        for depth in 0..max_depth {
1345            assert_eq!(invoke_context.get_stack_height(), depth);
1346            invoke_context
1347                .transaction_context
1348                .configure_top_level_instruction_for_tests(
1349                    (first_program_account.saturating_add(depth)) as IndexOfAccount,
1350                    instruction_accounts.clone(),
1351                    vec![],
1352                )
1353                .unwrap();
1354            assert!(
1355                invoke_context.push().is_ok(),
1356                "push at depth {depth} should succeed (max_depth={max_depth})",
1357            );
1358        }
1359
1360        // At exactly max_depth, one more push must fail with CallDepth.
1361        assert_eq!(invoke_context.get_stack_height(), max_depth);
1362        invoke_context
1363            .transaction_context
1364            .configure_top_level_instruction_for_tests(
1365                (first_program_account.saturating_add(max_depth)) as IndexOfAccount,
1366                instruction_accounts.clone(),
1367                vec![],
1368            )
1369            .unwrap();
1370        assert_eq!(invoke_context.push(), Err(InstructionError::CallDepth),);
1371
1372        // Stack height must not have changed after the rejected push.
1373        assert_eq!(invoke_context.get_stack_height(), max_depth);
1374    }
1375
1376    #[test]
1377    fn test_max_instruction_trace_length_top_level() {
1378        const MAX_INSTRUCTIONS: usize = 8;
1379        let mut transaction_context = TransactionContext::new(
1380            vec![(
1381                Pubkey::new_unique(),
1382                AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1383            )],
1384            Rent::default(),
1385            1,
1386            MAX_INSTRUCTIONS,
1387            MAX_INSTRUCTIONS,
1388        );
1389        for _ in 0..MAX_INSTRUCTIONS {
1390            transaction_context.push().unwrap();
1391            transaction_context
1392                .configure_top_level_instruction_for_tests(
1393                    0,
1394                    vec![InstructionAccount::new(0, false, false)],
1395                    vec![],
1396                )
1397                .unwrap();
1398            transaction_context.pop().unwrap();
1399        }
1400        assert_eq!(
1401            transaction_context.push(),
1402            Err(InstructionError::MaxInstructionTraceLengthExceeded)
1403        );
1404    }
1405
1406    #[test]
1407    fn test_max_instruction_trace_length_cpi() {
1408        // Hitting the limit with CPIs
1409        const MAX_INSTRUCTIONS: usize = 8;
1410        let mut transaction_context = TransactionContext::new(
1411            vec![(
1412                Pubkey::new_unique(),
1413                AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1414            )],
1415            Rent::default(),
1416            256,
1417            MAX_INSTRUCTIONS,
1418            2,
1419        );
1420        let num_transaction_accounts = usize::from(transaction_context.get_number_of_accounts());
1421
1422        transaction_context
1423            .configure_instruction_at_index(
1424                0,
1425                0,
1426                vec![InstructionAccount::new(0, false, false)],
1427                vec![u8::MAX; num_transaction_accounts],
1428                Cow::Owned(Vec::new()),
1429                None,
1430            )
1431            .unwrap();
1432
1433        transaction_context
1434            .configure_instruction_at_index(
1435                1,
1436                0,
1437                vec![InstructionAccount::new(0, false, false)],
1438                vec![u8::MAX; num_transaction_accounts],
1439                Cow::Owned(Vec::new()),
1440                None,
1441            )
1442            .unwrap();
1443
1444        for _ in 0..MAX_INSTRUCTIONS {
1445            transaction_context.push().unwrap();
1446            transaction_context
1447                .configure_next_cpi_for_tests(
1448                    0,
1449                    vec![InstructionAccount::new(0, false, false)],
1450                    Vec::new(),
1451                )
1452                .unwrap();
1453        }
1454
1455        assert_eq!(
1456            transaction_context.push(),
1457            Err(InstructionError::MaxInstructionTraceLengthExceeded)
1458        );
1459    }
1460
1461    #[test_case(MockInstruction::NoopSuccess, Ok(()); "NoopSuccess")]
1462    #[test_case(MockInstruction::NoopFail, Err(InstructionError::GenericError); "NoopFail")]
1463    #[test_case(MockInstruction::ModifyOwned, Ok(()); "ModifyOwned")]
1464    #[test_case(MockInstruction::ModifyNotOwned, Err(InstructionError::ExternalAccountDataModified); "ModifyNotOwned")]
1465    #[test_case(MockInstruction::ModifyReadonly, Err(InstructionError::ReadonlyDataModified); "ModifyReadonly")]
1466    #[test_case(MockInstruction::UnbalancedPush, Err(InstructionError::UnbalancedInstruction); "UnbalancedPush")]
1467    #[test_case(MockInstruction::UnbalancedPop, Err(InstructionError::UnbalancedInstruction); "UnbalancedPop")]
1468    fn test_process_instruction_account_modifications(
1469        instruction: MockInstruction,
1470        expected_result: Result<(), InstructionError>,
1471    ) {
1472        let callee_program_id = solana_pubkey::new_rand();
1473        let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1474        let not_owned_account = AccountSharedData::new(84, 1, &solana_pubkey::new_rand());
1475        let readonly_account = AccountSharedData::new(168, 1, &solana_pubkey::new_rand());
1476        let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1477        let mut program_account = AccountSharedData::new(1, 1, &native_loader::id());
1478        program_account.set_executable(true);
1479        let transaction_accounts = vec![
1480            (solana_pubkey::new_rand(), owned_account),
1481            (solana_pubkey::new_rand(), not_owned_account),
1482            (solana_pubkey::new_rand(), readonly_account),
1483            (callee_program_id, program_account),
1484            (solana_pubkey::new_rand(), loader_account),
1485        ];
1486        let metas = vec![
1487            AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1488            AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1489            AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1490        ];
1491        let instruction_accounts = (0..4)
1492            .map(|instruction_account_index| {
1493                InstructionAccount::new(
1494                    instruction_account_index,
1495                    false,
1496                    instruction_account_index < 2,
1497                )
1498            })
1499            .collect::<Vec<_>>();
1500        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1501        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1502        program_cache_for_tx_batch.replenish(
1503            callee_program_id,
1504            Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
1505        );
1506        invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1507
1508        // Account modification tests
1509        invoke_context
1510            .transaction_context
1511            .configure_top_level_instruction_for_tests(4, instruction_accounts, vec![])
1512            .unwrap();
1513        invoke_context.push().unwrap();
1514        let inner_instruction =
1515            Instruction::new_with_bincode(callee_program_id, &instruction, metas);
1516        let result = invoke_context
1517            .native_invoke_signed(inner_instruction, &[])
1518            .and(invoke_context.pop());
1519        assert_eq!(result, expected_result);
1520    }
1521
1522    #[test_case(Ok(()); "Ok")]
1523    #[test_case(Err(InstructionError::GenericError); "GenericError")]
1524    fn test_process_instruction_compute_unit_consumption(
1525        expected_result: Result<(), InstructionError>,
1526    ) {
1527        let callee_program_id = solana_pubkey::new_rand();
1528        let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1529        let not_owned_account = AccountSharedData::new(84, 1, &solana_pubkey::new_rand());
1530        let readonly_account = AccountSharedData::new(168, 1, &solana_pubkey::new_rand());
1531        let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1532        let mut program_account = AccountSharedData::new(1, 1, &native_loader::id());
1533        program_account.set_executable(true);
1534        let transaction_accounts = vec![
1535            (solana_pubkey::new_rand(), owned_account),
1536            (solana_pubkey::new_rand(), not_owned_account),
1537            (solana_pubkey::new_rand(), readonly_account),
1538            (callee_program_id, program_account),
1539            (solana_pubkey::new_rand(), loader_account),
1540        ];
1541        let metas = vec![
1542            AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1543            AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1544            AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1545        ];
1546        let instruction_accounts = (0..4)
1547            .map(|instruction_account_index| {
1548                InstructionAccount::new(
1549                    instruction_account_index,
1550                    false,
1551                    instruction_account_index < 2,
1552                )
1553            })
1554            .collect::<Vec<_>>();
1555        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1556        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1557        program_cache_for_tx_batch.replenish(
1558            callee_program_id,
1559            Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
1560        );
1561        invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1562
1563        // Compute unit consumption tests
1564        let compute_units_to_consume = 10;
1565        invoke_context
1566            .transaction_context
1567            .configure_top_level_instruction_for_tests(4, instruction_accounts, vec![])
1568            .unwrap();
1569        invoke_context.push().unwrap();
1570        let inner_instruction = Instruction::new_with_bincode(
1571            callee_program_id,
1572            &MockInstruction::ConsumeComputeUnits {
1573                compute_units_to_consume,
1574                desired_result: expected_result.clone(),
1575            },
1576            metas,
1577        );
1578        invoke_context
1579            .prepare_next_cpi_instruction(inner_instruction, &[])
1580            .unwrap();
1581
1582        let mut compute_units_consumed = 0;
1583        let result = invoke_context
1584            .process_instruction(&mut compute_units_consumed, &mut ExecuteTimings::default());
1585
1586        // Because the instruction had compute cost > 0, then regardless of the execution result,
1587        // the number of compute units consumed should be a non-default which is something greater
1588        // than zero.
1589        assert!(compute_units_consumed > 0);
1590        assert_eq!(
1591            compute_units_consumed,
1592            compute_units_to_consume.saturating_add(MOCK_BUILTIN_COMPUTE_UNIT_COST),
1593        );
1594        assert_eq!(result, expected_result);
1595
1596        invoke_context.pop().unwrap();
1597    }
1598
1599    #[test]
1600    fn test_invoke_context_compute_budget() {
1601        let transaction_accounts = vec![(solana_pubkey::new_rand(), AccountSharedData::default())];
1602        let execution_budget = SVMTransactionExecutionBudget {
1603            compute_unit_limit: u64::from(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT),
1604            ..SVMTransactionExecutionBudget::default()
1605        };
1606
1607        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1608        invoke_context.compute_budget = execution_budget;
1609
1610        invoke_context
1611            .transaction_context
1612            .configure_top_level_instruction_for_tests(0, vec![], vec![])
1613            .unwrap();
1614        invoke_context.push().unwrap();
1615        assert_eq!(*invoke_context.get_compute_budget(), execution_budget);
1616        invoke_context.pop().unwrap();
1617    }
1618
1619    #[test_case(0; "Resize the account to *the same size*, so not consuming any additional size")]
1620    #[test_case(1; "Resize the account larger")]
1621    #[test_case(-1; "Resize the account smaller")]
1622    fn test_process_instruction_accounts_resize_delta(resize_delta: i64) {
1623        let program_key = Pubkey::new_unique();
1624        let user_account_data_len = 123u64;
1625        let user_account =
1626            AccountSharedData::new(100, user_account_data_len as usize, &program_key);
1627        let dummy_account = AccountSharedData::new(10, 0, &program_key);
1628        let mut program_account = AccountSharedData::new(500, 500, &native_loader::id());
1629        program_account.set_executable(true);
1630        let transaction_accounts = vec![
1631            (Pubkey::new_unique(), user_account),
1632            (Pubkey::new_unique(), dummy_account),
1633            (program_key, program_account),
1634        ];
1635        let instruction_accounts = vec![
1636            InstructionAccount::new(0, false, true),
1637            InstructionAccount::new(1, false, false),
1638        ];
1639        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1640        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1641        program_cache_for_tx_batch.replenish(
1642            program_key,
1643            Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
1644        );
1645        invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1646
1647        let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
1648        let instruction_data = bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1649
1650        invoke_context
1651            .transaction_context
1652            .configure_top_level_instruction_for_tests(2, instruction_accounts, instruction_data)
1653            .unwrap();
1654        let result = invoke_context.process_instruction(&mut 0, &mut ExecuteTimings::default());
1655
1656        assert!(result.is_ok());
1657        assert_eq!(
1658            invoke_context.transaction_context.accounts().resize_delta(),
1659            resize_delta
1660        );
1661    }
1662
1663    #[test]
1664    fn test_prepare_instruction_maximum_accounts() {
1665        const MAX_ACCOUNTS_REFERENCED: usize = u16::MAX as usize;
1666        let mut transaction_accounts: Vec<KeyedAccountSharedData> =
1667            Vec::with_capacity(MAX_ACCOUNTS_PER_TRANSACTION);
1668        let mut account_metas: Vec<AccountMeta> = Vec::with_capacity(MAX_ACCOUNTS_REFERENCED);
1669
1670        // Fee-payer
1671        let fee_payer = Keypair::new();
1672        transaction_accounts.push((
1673            fee_payer.pubkey(),
1674            AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1675        ));
1676        account_metas.push(AccountMeta::new(fee_payer.pubkey(), true));
1677
1678        let program_id = Pubkey::new_unique();
1679        let mut program_account = AccountSharedData::new(1, 1, &Pubkey::new_unique());
1680        program_account.set_executable(true);
1681        transaction_accounts.push((program_id, program_account));
1682        account_metas.push(AccountMeta::new_readonly(program_id, false));
1683
1684        for i in 2..MAX_ACCOUNTS_REFERENCED {
1685            // Let's reference 256 unique accounts, and the rest is repeated.
1686            if i < MAX_ACCOUNTS_PER_TRANSACTION {
1687                let key = Pubkey::new_unique();
1688                transaction_accounts
1689                    .push((key, AccountSharedData::new(1, 1, &Pubkey::new_unique())));
1690                account_metas.push(AccountMeta::new_readonly(key, false));
1691            } else {
1692                let repeated_key = transaction_accounts
1693                    .get(i % MAX_ACCOUNTS_PER_TRANSACTION)
1694                    .unwrap()
1695                    .0;
1696                account_metas.push(AccountMeta::new_readonly(repeated_key, false));
1697            }
1698        }
1699
1700        with_mock_invoke_context!(invoke_context, transaction_context, 2, transaction_accounts);
1701
1702        let instruction_1 = Instruction::new_with_bytes(program_id, &[20], account_metas.clone());
1703
1704        let instruction_2 = Instruction::new_with_bytes(
1705            program_id,
1706            &[20],
1707            account_metas.iter().rev().cloned().collect(),
1708        );
1709
1710        let transaction = Transaction::new_with_payer(
1711            &[instruction_1.clone(), instruction_2.clone()],
1712            Some(&fee_payer.pubkey()),
1713        );
1714
1715        let sanitized =
1716            SanitizedTransaction::try_from_legacy_transaction(transaction, &HashSet::new())
1717                .unwrap();
1718
1719        fn test_case_1(invoke_context: &InvokeContext) {
1720            let instruction_context = invoke_context
1721                .transaction_context
1722                .get_next_instruction_context()
1723                .unwrap();
1724            for index_in_instruction in 0..MAX_ACCOUNTS_REFERENCED as IndexOfAccount {
1725                let index_in_transaction = instruction_context
1726                    .get_index_of_instruction_account_in_transaction(index_in_instruction)
1727                    .unwrap();
1728                let other_ix_index = instruction_context
1729                    .get_index_of_account_in_instruction(index_in_transaction)
1730                    .unwrap();
1731                if (index_in_instruction as usize) < MAX_ACCOUNTS_PER_TRANSACTION {
1732                    assert_eq!(index_in_instruction, index_in_transaction);
1733                    assert_eq!(index_in_instruction, other_ix_index);
1734                } else {
1735                    assert_eq!(
1736                        index_in_instruction as usize % MAX_ACCOUNTS_PER_TRANSACTION,
1737                        index_in_transaction as usize
1738                    );
1739                    assert_eq!(
1740                        index_in_instruction as usize % MAX_ACCOUNTS_PER_TRANSACTION,
1741                        other_ix_index as usize
1742                    );
1743                }
1744            }
1745        }
1746
1747        fn test_case_2(invoke_context: &InvokeContext) {
1748            let instruction_context = invoke_context
1749                .transaction_context
1750                .get_next_instruction_context()
1751                .unwrap();
1752            for index_in_instruction in 0..MAX_ACCOUNTS_REFERENCED as IndexOfAccount {
1753                let index_in_transaction = instruction_context
1754                    .get_index_of_instruction_account_in_transaction(index_in_instruction)
1755                    .unwrap();
1756                let other_ix_index = instruction_context
1757                    .get_index_of_account_in_instruction(index_in_transaction)
1758                    .unwrap();
1759                assert_eq!(
1760                    index_in_transaction,
1761                    (MAX_ACCOUNTS_REFERENCED as u16)
1762                        .saturating_sub(index_in_instruction)
1763                        .saturating_sub(1)
1764                        .overflowing_rem(MAX_ACCOUNTS_PER_TRANSACTION as u16)
1765                        .0
1766                );
1767                if (index_in_instruction as usize) < MAX_ACCOUNTS_PER_TRANSACTION {
1768                    assert_eq!(index_in_instruction, other_ix_index);
1769                } else {
1770                    assert_eq!(
1771                        index_in_instruction as usize % MAX_ACCOUNTS_PER_TRANSACTION,
1772                        other_ix_index as usize
1773                    );
1774                }
1775            }
1776        }
1777
1778        invoke_context
1779            .prepare_top_level_instructions(&sanitized)
1780            .unwrap();
1781
1782        test_case_1(&invoke_context);
1783
1784        invoke_context.transaction_context.push().unwrap();
1785        invoke_context.transaction_context.pop().unwrap();
1786
1787        test_case_2(&invoke_context);
1788
1789        invoke_context.transaction_context.push().unwrap();
1790        invoke_context
1791            .prepare_next_cpi_instruction(instruction_1, &[fee_payer.pubkey()])
1792            .unwrap();
1793        test_case_1(&invoke_context);
1794
1795        invoke_context.transaction_context.push().unwrap();
1796        invoke_context
1797            .prepare_next_cpi_instruction(instruction_2, &[fee_payer.pubkey()])
1798            .unwrap();
1799        test_case_2(&invoke_context);
1800    }
1801
1802    #[test]
1803    fn test_duplicated_accounts() {
1804        let mut transaction_accounts: Vec<KeyedAccountSharedData> =
1805            Vec::with_capacity(MAX_ACCOUNTS_PER_TRANSACTION);
1806        let mut account_metas: Vec<AccountMeta> =
1807            Vec::with_capacity(MAX_ACCOUNTS_PER_INSTRUCTION.saturating_sub(1));
1808
1809        // Fee-payer
1810        let fee_payer = Keypair::new();
1811        transaction_accounts.push((
1812            fee_payer.pubkey(),
1813            AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1814        ));
1815        account_metas.push(AccountMeta::new(fee_payer.pubkey(), true));
1816
1817        let program_id = Pubkey::new_unique();
1818        let mut program_account = AccountSharedData::new(1, 1, &Pubkey::new_unique());
1819        program_account.set_executable(true);
1820        transaction_accounts.push((program_id, program_account));
1821        account_metas.push(AccountMeta::new_readonly(program_id, false));
1822
1823        for i in 2..account_metas.capacity() {
1824            if i % 2 == 0 {
1825                let key = Pubkey::new_unique();
1826                transaction_accounts
1827                    .push((key, AccountSharedData::new(1, 1, &Pubkey::new_unique())));
1828                account_metas.push(AccountMeta::new_readonly(key, false));
1829            } else {
1830                let last_key = transaction_accounts.last().unwrap().0;
1831                account_metas.push(AccountMeta::new_readonly(last_key, false));
1832            }
1833        }
1834
1835        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1836
1837        let instruction = Instruction::new_with_bytes(program_id, &[20], account_metas.clone());
1838
1839        let transaction = Transaction::new_with_payer(&[instruction], Some(&fee_payer.pubkey()));
1840
1841        let sanitized =
1842            SanitizedTransaction::try_from_legacy_transaction(transaction, &HashSet::new())
1843                .unwrap();
1844
1845        invoke_context
1846            .prepare_top_level_instructions(&sanitized)
1847            .unwrap();
1848
1849        {
1850            let instruction_context = invoke_context
1851                .transaction_context
1852                .get_next_instruction_context()
1853                .unwrap();
1854            for index_in_instruction in 2..account_metas.len() as IndexOfAccount {
1855                let is_duplicate = instruction_context
1856                    .is_instruction_account_duplicate(index_in_instruction)
1857                    .unwrap();
1858                if index_in_instruction % 2 == 0 {
1859                    assert!(is_duplicate.is_none());
1860                } else {
1861                    assert_eq!(is_duplicate, Some(index_in_instruction.saturating_sub(1)));
1862                }
1863            }
1864        }
1865
1866        invoke_context.transaction_context.push().unwrap();
1867
1868        let instruction = Instruction::new_with_bytes(
1869            program_id,
1870            &[20],
1871            account_metas.iter().cloned().rev().collect(),
1872        );
1873
1874        invoke_context
1875            .prepare_next_cpi_instruction(instruction, &[fee_payer.pubkey()])
1876            .unwrap();
1877        let instruction_context = invoke_context
1878            .transaction_context
1879            .get_next_instruction_context()
1880            .unwrap();
1881        for index_in_instruction in 2..account_metas.len().saturating_sub(1) as u16 {
1882            let is_duplicate = instruction_context
1883                .is_instruction_account_duplicate(index_in_instruction)
1884                .unwrap();
1885            if index_in_instruction % 2 == 0 {
1886                assert!(is_duplicate.is_none());
1887            } else {
1888                assert_eq!(is_duplicate, Some(index_in_instruction.saturating_sub(1)));
1889            }
1890        }
1891    }
1892
1893    // Used for native_invoke_signed tests below.
1894    const TEST_CALLER_PROGRAM_ID: Pubkey = Pubkey::new_from_array([1u8; 32]);
1895    const TEST_CALLEE_PROGRAM_ID: Pubkey = Pubkey::new_from_array([2u8; 32]);
1896    const TEST_WRONG_PROGRAM_ID: Pubkey = Pubkey::new_from_array([3u8; 32]);
1897    const TEST_MOCK_EXTRA_KEY: Pubkey = Pubkey::new_from_array([4u8; 32]);
1898    const TEST_ACCOUNT_KEY: Pubkey = Pubkey::new_from_array([5u8; 32]);
1899
1900    /// Runs a `native_invoke_signed` call with the standard test setup and returns
1901    /// the result.
1902    ///
1903    /// Same layout for all tests:
1904    ///   0: target account (writable, signer iff `target_is_signer`)
1905    ///   1: caller program (executable)
1906    ///   2: mock extra (satisfies MockBuiltin's 2-account requirement)
1907    ///   3: callee program (executable)
1908    fn run_native_invoke_signed_test(
1909        target_key: Pubkey,
1910        target_is_signer: bool,
1911        inner_instruction: Instruction,
1912        signer_seeds: &[&[&[u8]]],
1913    ) -> Result<(), InstructionError> {
1914        let target_account = AccountSharedData::new(100, 0, &TEST_CALLEE_PROGRAM_ID);
1915        let mock_extra_account = AccountSharedData::new(0, 1, &system_program::id());
1916        let mut caller_program_account = AccountSharedData::new(1, 1, &native_loader::id());
1917        caller_program_account.set_executable(true);
1918        let mut callee_program_account = AccountSharedData::new(1, 1, &native_loader::id());
1919        callee_program_account.set_executable(true);
1920        let transaction_accounts = vec![
1921            (target_key, target_account),
1922            (TEST_CALLER_PROGRAM_ID, caller_program_account),
1923            (TEST_MOCK_EXTRA_KEY, mock_extra_account),
1924            (TEST_CALLEE_PROGRAM_ID, callee_program_account),
1925        ];
1926
1927        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1928        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1929        program_cache_for_tx_batch.replenish(
1930            TEST_CALLEE_PROGRAM_ID,
1931            Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
1932        );
1933        invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1934
1935        let instruction_accounts = (0..4)
1936            .map(|i| InstructionAccount::new(i, i == 0 && target_is_signer, i < 2))
1937            .collect::<Vec<_>>();
1938        invoke_context
1939            .transaction_context
1940            .configure_top_level_instruction_for_tests(1, instruction_accounts, vec![])
1941            .unwrap();
1942        invoke_context.push().unwrap();
1943
1944        let result = invoke_context.native_invoke_signed(inner_instruction, signer_seeds);
1945        invoke_context.pop().unwrap();
1946        result
1947    }
1948
1949    // Valid PDA seeds grant signer privilege to the derived address.
1950    #[test]
1951    fn test_native_invoke_signed_with_valid_pda_signer() {
1952        let (pda_key, bump_seed) =
1953            Pubkey::find_program_address(&[b"seed"], &TEST_CALLER_PROGRAM_ID);
1954        let instruction = Instruction::new_with_bincode(
1955            TEST_CALLEE_PROGRAM_ID,
1956            &MockInstruction::NoopSuccess,
1957            vec![
1958                AccountMeta::new(pda_key, true),
1959                AccountMeta::new_readonly(TEST_MOCK_EXTRA_KEY, false),
1960            ],
1961        );
1962        let result =
1963            run_native_invoke_signed_test(pda_key, false, instruction, &[&[b"seed", &[bump_seed]]]);
1964        assert!(
1965            result.is_ok(),
1966            "valid PDA signer should succeed: {result:?}"
1967        );
1968    }
1969
1970    // Oversized seeds (>MAX_SEED_LEN) hit `MaxSeedLengthExceeded`
1971    // (discriminant 0) which the broken `as u64` num-traits conversion
1972    // maps to `Custom(0)`.
1973    #[test]
1974    fn test_native_invoke_signed_with_invalid_seeds() {
1975        let instruction = Instruction::new_with_bincode(
1976            TEST_CALLEE_PROGRAM_ID,
1977            &MockInstruction::NoopSuccess,
1978            vec![AccountMeta::new(TEST_ACCOUNT_KEY, true)],
1979        );
1980        let oversized_seed = [0u8; 33];
1981        let result = run_native_invoke_signed_test(
1982            TEST_ACCOUNT_KEY,
1983            false,
1984            instruction,
1985            &[&[&oversized_seed]],
1986        );
1987        assert_eq!(result, Err(InstructionError::Custom(0)));
1988    }
1989
1990    // CPI marks an account as signer but caller provides no seeds —
1991    // signer privilege escalation.
1992    #[test]
1993    fn test_native_invoke_signed_pda_privilege_escalation_without_seeds() {
1994        let (pda_key, _bump_seed) =
1995            Pubkey::find_program_address(&[b"seed"], &TEST_CALLER_PROGRAM_ID);
1996        let instruction = Instruction::new_with_bincode(
1997            TEST_CALLEE_PROGRAM_ID,
1998            &MockInstruction::NoopSuccess,
1999            vec![AccountMeta::new(pda_key, true)],
2000        );
2001        let result = run_native_invoke_signed_test(pda_key, false, instruction, &[]);
2002        assert_eq!(result, Err(InstructionError::PrivilegeEscalation));
2003    }
2004
2005    // Seeds valid for a different program ID don't grant signer privilege
2006    // because native_invoke_signed derives against the caller's own program ID.
2007    #[test]
2008    fn test_native_invoke_signed_uses_caller_program_id_for_pda() {
2009        let (pda_key, bump_seed) = Pubkey::find_program_address(&[b"seed"], &TEST_WRONG_PROGRAM_ID);
2010        let instruction = Instruction::new_with_bincode(
2011            TEST_CALLEE_PROGRAM_ID,
2012            &MockInstruction::NoopSuccess,
2013            vec![AccountMeta::new(pda_key, true)],
2014        );
2015        let result =
2016            run_native_invoke_signed_test(pda_key, false, instruction, &[&[b"seed", &[bump_seed]]]);
2017        assert_eq!(result, Err(InstructionError::PrivilegeEscalation));
2018    }
2019
2020    // Top-level signer privilege carries through CPI without needing seeds.
2021    #[test]
2022    fn test_native_invoke_signed_top_level_signer_needs_no_seeds() {
2023        let (pda_key, _bump_seed) =
2024            Pubkey::find_program_address(&[b"seed"], &TEST_CALLER_PROGRAM_ID);
2025        let instruction = Instruction::new_with_bincode(
2026            TEST_CALLEE_PROGRAM_ID,
2027            &MockInstruction::NoopSuccess,
2028            vec![
2029                AccountMeta::new(pda_key, true),
2030                AccountMeta::new_readonly(TEST_MOCK_EXTRA_KEY, false),
2031            ],
2032        );
2033        let result = run_native_invoke_signed_test(pda_key, true, instruction, &[]);
2034        assert!(
2035            result.is_ok(),
2036            "top-level signer should not need seeds: {result:?}"
2037        );
2038    }
2039
2040    #[test]
2041    fn test_compile_message() {
2042        let program_id = Pubkey::new_from_array([1u8; 32]);
2043        let writable = Pubkey::new_from_array([2u8; 32]);
2044        let loader_key = Pubkey::new_from_array([3u8; 32]);
2045
2046        let instruction = Instruction {
2047            program_id,
2048            accounts: vec![AccountMeta::new(writable, false)],
2049            data: vec![1, 2, 3],
2050        };
2051
2052        let accounts = vec![(
2053            writable,
2054            Account {
2055                lamports: 100,
2056                ..Account::default()
2057            },
2058        )];
2059
2060        let (message, tx_accounts) =
2061            mock_compile_message(&instruction, &accounts, &program_id, &loader_key);
2062
2063        assert_eq!(message.instructions().len(), 1);
2064        assert_eq!(tx_accounts.len(), 2);
2065        assert_eq!(tx_accounts.first().unwrap().0, writable);
2066        assert_eq!(tx_accounts.get(1).unwrap().0, program_id);
2067
2068        // Verify the writable account is NOT promoted to signer.
2069        assert!(!message.is_signer(0));
2070    }
2071
2072    struct MockCallback {}
2073    impl InvokeContextCallback for MockCallback {}
2074
2075    fn create_loadable_account_for_test(name: &str) -> AccountSharedData {
2076        let (lamports, rent_epoch) = DUMMY_INHERITABLE_ACCOUNT_FIELDS;
2077        AccountSharedData::from(Account {
2078            lamports,
2079            owner: native_loader::id(),
2080            data: name.as_bytes().to_vec(),
2081            executable: true,
2082            rent_epoch,
2083        })
2084    }
2085
2086    fn new_sanitized_message(message: Message) -> SanitizedMessage {
2087        SanitizedMessage::try_from_legacy_message(message, &HashSet::new()).unwrap()
2088    }
2089
2090    #[test]
2091    fn test_process_message_readonly_handling() {
2092        #[derive(serde::Serialize, serde::Deserialize)]
2093        enum MockSystemInstruction {
2094            Correct,
2095            TransferLamports { lamports: u64 },
2096            ChangeData { data: u8 },
2097        }
2098
2099        declare_process_instruction!(MockBuiltin, 1, |invoke_context| {
2100            let transaction_context = &invoke_context.transaction_context;
2101            let instruction_context = transaction_context.get_current_instruction_context()?;
2102            let instruction_data = instruction_context.get_instruction_data();
2103            if let Ok(instruction) = bincode::deserialize(instruction_data) {
2104                match instruction {
2105                    MockSystemInstruction::Correct => Ok(()),
2106                    MockSystemInstruction::TransferLamports { lamports } => {
2107                        instruction_context
2108                            .try_borrow_instruction_account(0)?
2109                            .checked_sub_lamports(lamports)?;
2110                        instruction_context
2111                            .try_borrow_instruction_account(1)?
2112                            .checked_add_lamports(lamports)?;
2113                        Ok(())
2114                    }
2115                    MockSystemInstruction::ChangeData { data } => {
2116                        instruction_context
2117                            .try_borrow_instruction_account(1)?
2118                            .set_data_from_slice(&[data])?;
2119                        Ok(())
2120                    }
2121                }
2122            } else {
2123                Err(InstructionError::InvalidInstructionData)
2124            }
2125        });
2126
2127        let writable_pubkey = Pubkey::new_unique();
2128        let readonly_pubkey = Pubkey::new_unique();
2129        let mock_system_program_id = Pubkey::new_unique();
2130
2131        let accounts = vec![
2132            (
2133                writable_pubkey,
2134                AccountSharedData::new(100, 1, &mock_system_program_id),
2135            ),
2136            (
2137                readonly_pubkey,
2138                AccountSharedData::new(0, 1, &mock_system_program_id),
2139            ),
2140            (
2141                mock_system_program_id,
2142                create_loadable_account_for_test("mock_system_program"),
2143            ),
2144        ];
2145        let mut transaction_context =
2146            TransactionContext::new(accounts.clone(), Rent::default(), 1, 3, 1);
2147        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
2148        program_cache_for_tx_batch.replenish(
2149            mock_system_program_id,
2150            Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
2151        );
2152        let account_keys = (0..transaction_context.get_number_of_accounts())
2153            .map(|index| {
2154                *transaction_context
2155                    .get_key_of_account_at_index(index)
2156                    .unwrap()
2157            })
2158            .collect::<Vec<_>>();
2159        let account_metas = vec![
2160            AccountMeta::new(writable_pubkey, true),
2161            AccountMeta::new_readonly(readonly_pubkey, false),
2162        ];
2163
2164        let message = new_sanitized_message(Message::new_with_compiled_instructions(
2165            1,
2166            0,
2167            2,
2168            account_keys.clone(),
2169            Hash::default(),
2170            AccountKeys::new(&account_keys, None).compile_instructions(&[
2171                Instruction::new_with_bincode(
2172                    mock_system_program_id,
2173                    &MockSystemInstruction::Correct,
2174                    account_metas.clone(),
2175                ),
2176            ]),
2177        ));
2178        let sysvar_cache = SysvarCache::default();
2179        let feature_set = SVMFeatureSet::all_enabled();
2180        let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2181        let environment_config = EnvironmentConfig::new(
2182            Hash::default(),
2183            0,
2184            false,
2185            &MockCallback {},
2186            &feature_set,
2187            &program_runtime_environments,
2188            &sysvar_cache,
2189        );
2190        let mut invoke_context = InvokeContext::new(
2191            &mut transaction_context,
2192            &mut program_cache_for_tx_batch,
2193            environment_config,
2194            None,
2195            SVMTransactionExecutionBudget::default(),
2196            SVMTransactionExecutionCost::default(),
2197        );
2198        let result =
2199            invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2200        assert!(result.is_ok());
2201        assert_eq!(
2202            transaction_context
2203                .accounts()
2204                .try_borrow(0)
2205                .unwrap()
2206                .lamports(),
2207            100
2208        );
2209        assert_eq!(
2210            transaction_context
2211                .accounts()
2212                .try_borrow(1)
2213                .unwrap()
2214                .lamports(),
2215            0
2216        );
2217
2218        let message = new_sanitized_message(Message::new_with_compiled_instructions(
2219            1,
2220            0,
2221            2,
2222            account_keys.clone(),
2223            Hash::default(),
2224            AccountKeys::new(&account_keys, None).compile_instructions(&[
2225                Instruction::new_with_bincode(
2226                    mock_system_program_id,
2227                    &MockSystemInstruction::TransferLamports { lamports: 50 },
2228                    account_metas.clone(),
2229                ),
2230            ]),
2231        ));
2232        let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2233        let environment_config = EnvironmentConfig::new(
2234            Hash::default(),
2235            0,
2236            false,
2237            &MockCallback {},
2238            &feature_set,
2239            &program_runtime_environments,
2240            &sysvar_cache,
2241        );
2242        let mut transaction_context =
2243            TransactionContext::new(accounts.clone(), Rent::default(), 1, 3, 1);
2244        let mut invoke_context = InvokeContext::new(
2245            &mut transaction_context,
2246            &mut program_cache_for_tx_batch,
2247            environment_config,
2248            None,
2249            SVMTransactionExecutionBudget::default(),
2250            SVMTransactionExecutionCost::default(),
2251        );
2252        let result =
2253            invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2254        assert_eq!(result, Err((0, InstructionError::ReadonlyLamportChange)));
2255
2256        let message = new_sanitized_message(Message::new_with_compiled_instructions(
2257            1,
2258            0,
2259            2,
2260            account_keys.clone(),
2261            Hash::default(),
2262            AccountKeys::new(&account_keys, None).compile_instructions(&[
2263                Instruction::new_with_bincode(
2264                    mock_system_program_id,
2265                    &MockSystemInstruction::ChangeData { data: 50 },
2266                    account_metas,
2267                ),
2268            ]),
2269        ));
2270        let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2271        let environment_config = EnvironmentConfig::new(
2272            Hash::default(),
2273            0,
2274            false,
2275            &MockCallback {},
2276            &feature_set,
2277            &program_runtime_environments,
2278            &sysvar_cache,
2279        );
2280        let mut transaction_context = TransactionContext::new(accounts, Rent::default(), 1, 3, 1);
2281        let mut invoke_context = InvokeContext::new(
2282            &mut transaction_context,
2283            &mut program_cache_for_tx_batch,
2284            environment_config,
2285            None,
2286            SVMTransactionExecutionBudget::default(),
2287            SVMTransactionExecutionCost::default(),
2288        );
2289        let result =
2290            invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2291        assert_eq!(result, Err((0, InstructionError::ReadonlyDataModified)));
2292    }
2293
2294    #[test]
2295    fn test_process_message_duplicate_accounts() {
2296        #[derive(serde::Serialize, serde::Deserialize)]
2297        enum MockSystemInstruction {
2298            BorrowFail,
2299            MultiBorrowMut,
2300            DoWork { lamports: u64, data: u8 },
2301        }
2302
2303        declare_process_instruction!(MockBuiltin, 1, |invoke_context| {
2304            let transaction_context = &invoke_context.transaction_context;
2305            let instruction_context = transaction_context.get_current_instruction_context()?;
2306            let instruction_data = instruction_context.get_instruction_data();
2307            let mut to_account = instruction_context.try_borrow_instruction_account(1)?;
2308            if let Ok(instruction) = bincode::deserialize(instruction_data) {
2309                match instruction {
2310                    MockSystemInstruction::BorrowFail => {
2311                        let from_account = instruction_context.try_borrow_instruction_account(0)?;
2312                        let dup_account = instruction_context.try_borrow_instruction_account(2)?;
2313                        if from_account.get_lamports() != dup_account.get_lamports() {
2314                            return Err(InstructionError::InvalidArgument);
2315                        }
2316                        Ok(())
2317                    }
2318                    MockSystemInstruction::MultiBorrowMut => {
2319                        let lamports_a = instruction_context
2320                            .try_borrow_instruction_account(0)?
2321                            .get_lamports();
2322                        let lamports_b = instruction_context
2323                            .try_borrow_instruction_account(2)?
2324                            .get_lamports();
2325                        if lamports_a != lamports_b {
2326                            return Err(InstructionError::InvalidArgument);
2327                        }
2328                        Ok(())
2329                    }
2330                    MockSystemInstruction::DoWork { lamports, data } => {
2331                        let mut dup_account =
2332                            instruction_context.try_borrow_instruction_account(2)?;
2333                        dup_account.checked_sub_lamports(lamports)?;
2334                        to_account.checked_add_lamports(lamports)?;
2335                        dup_account.set_data_from_slice(&[data])?;
2336                        drop(dup_account);
2337                        let mut from_account =
2338                            instruction_context.try_borrow_instruction_account(0)?;
2339                        from_account.checked_sub_lamports(lamports)?;
2340                        to_account.checked_add_lamports(lamports)?;
2341                        Ok(())
2342                    }
2343                }
2344            } else {
2345                Err(InstructionError::InvalidInstructionData)
2346            }
2347        });
2348        let mock_program_id = Pubkey::from([2u8; 32]);
2349        let accounts = vec![
2350            (
2351                solana_pubkey::new_rand(),
2352                AccountSharedData::new(100, 1, &mock_program_id),
2353            ),
2354            (
2355                solana_pubkey::new_rand(),
2356                AccountSharedData::new(0, 1, &mock_program_id),
2357            ),
2358            (
2359                mock_program_id,
2360                create_loadable_account_for_test("mock_system_program"),
2361            ),
2362        ];
2363        let mut transaction_context =
2364            TransactionContext::new(accounts.clone(), Rent::default(), 1, 3, 1);
2365        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
2366        program_cache_for_tx_batch.replenish(
2367            mock_program_id,
2368            Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
2369        );
2370        let account_metas = vec![
2371            AccountMeta::new(
2372                *transaction_context.get_key_of_account_at_index(0).unwrap(),
2373                true,
2374            ),
2375            AccountMeta::new(
2376                *transaction_context.get_key_of_account_at_index(1).unwrap(),
2377                false,
2378            ),
2379            AccountMeta::new(
2380                *transaction_context.get_key_of_account_at_index(0).unwrap(),
2381                false,
2382            ),
2383        ];
2384
2385        // Try to borrow mut the same account
2386        let message = new_sanitized_message(Message::new(
2387            &[Instruction::new_with_bincode(
2388                mock_program_id,
2389                &MockSystemInstruction::BorrowFail,
2390                account_metas.clone(),
2391            )],
2392            Some(transaction_context.get_key_of_account_at_index(0).unwrap()),
2393        ));
2394        let sysvar_cache = SysvarCache::default();
2395        let feature_set = SVMFeatureSet::all_enabled();
2396        let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2397        let environment_config = EnvironmentConfig::new(
2398            Hash::default(),
2399            0,
2400            false,
2401            &MockCallback {},
2402            &feature_set,
2403            &program_runtime_environments,
2404            &sysvar_cache,
2405        );
2406        let mut invoke_context = InvokeContext::new(
2407            &mut transaction_context,
2408            &mut program_cache_for_tx_batch,
2409            environment_config,
2410            None,
2411            SVMTransactionExecutionBudget::default(),
2412            SVMTransactionExecutionCost::default(),
2413        );
2414        let result =
2415            invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2416        assert_eq!(result, Err((0, InstructionError::AccountBorrowFailed)));
2417
2418        // Try to borrow mut the same account in a safe way
2419        let message = new_sanitized_message(Message::new(
2420            &[Instruction::new_with_bincode(
2421                mock_program_id,
2422                &MockSystemInstruction::MultiBorrowMut,
2423                account_metas.clone(),
2424            )],
2425            Some(transaction_context.get_key_of_account_at_index(0).unwrap()),
2426        ));
2427        let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2428        let environment_config = EnvironmentConfig::new(
2429            Hash::default(),
2430            0,
2431            false,
2432            &MockCallback {},
2433            &feature_set,
2434            &program_runtime_environments,
2435            &sysvar_cache,
2436        );
2437        let mut transaction_context =
2438            TransactionContext::new(accounts.clone(), Rent::default(), 1, 3, 1);
2439        let mut invoke_context = InvokeContext::new(
2440            &mut transaction_context,
2441            &mut program_cache_for_tx_batch,
2442            environment_config,
2443            None,
2444            SVMTransactionExecutionBudget::default(),
2445            SVMTransactionExecutionCost::default(),
2446        );
2447        let result =
2448            invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2449        assert!(result.is_ok());
2450
2451        // Do work on the same transaction account but at different instruction accounts
2452        let message = new_sanitized_message(Message::new(
2453            &[Instruction::new_with_bincode(
2454                mock_program_id,
2455                &MockSystemInstruction::DoWork {
2456                    lamports: 10,
2457                    data: 42,
2458                },
2459                account_metas,
2460            )],
2461            Some(transaction_context.get_key_of_account_at_index(0).unwrap()),
2462        ));
2463        let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2464        let environment_config = EnvironmentConfig::new(
2465            Hash::default(),
2466            0,
2467            false,
2468            &MockCallback {},
2469            &feature_set,
2470            &program_runtime_environments,
2471            &sysvar_cache,
2472        );
2473        let mut transaction_context = TransactionContext::new(accounts, Rent::default(), 1, 3, 1);
2474        let mut invoke_context = InvokeContext::new(
2475            &mut transaction_context,
2476            &mut program_cache_for_tx_batch,
2477            environment_config,
2478            None,
2479            SVMTransactionExecutionBudget::default(),
2480            SVMTransactionExecutionCost::default(),
2481        );
2482        let result =
2483            invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2484        assert!(result.is_ok());
2485        assert_eq!(
2486            transaction_context
2487                .accounts()
2488                .try_borrow(0)
2489                .unwrap()
2490                .lamports(),
2491            80
2492        );
2493        assert_eq!(
2494            transaction_context
2495                .accounts()
2496                .try_borrow(1)
2497                .unwrap()
2498                .lamports(),
2499            20
2500        );
2501        assert_eq!(
2502            transaction_context.accounts().try_borrow(0).unwrap().data(),
2503            &vec![42]
2504        );
2505    }
2506
2507    fn secp256k1_instruction_for_test() -> Instruction {
2508        let message = b"hello";
2509        let bytes: [u8; 32] = rand::random();
2510        let secret_key = libsecp256k1::SecretKey::parse(&bytes).unwrap();
2511        let pubkey = libsecp256k1::PublicKey::from_secret_key(&secret_key);
2512        let eth_address = eth_address_from_pubkey(&pubkey.serialize()[1..].try_into().unwrap());
2513        let (signature, recovery_id) =
2514            solana_secp256k1_program::sign_message(&secret_key.serialize(), &message[..]).unwrap();
2515        new_secp256k1_instruction_with_signature(
2516            &message[..],
2517            &signature,
2518            recovery_id,
2519            &eth_address,
2520        )
2521    }
2522
2523    fn ed25519_instruction_for_test() -> Instruction {
2524        let keypair = Keypair::new();
2525        let signature = keypair.sign_message(b"hello");
2526        let pubkey = keypair.pubkey().to_bytes();
2527        new_ed25519_instruction_with_signature(b"hello", signature.as_array(), &pubkey)
2528    }
2529
2530    fn secp256r1_instruction_for_test() -> Instruction {
2531        let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap();
2532        let secret_key = EcKey::generate(&group).unwrap();
2533        let signature = sign_message(b"hello", &secret_key.private_key_to_der().unwrap()).unwrap();
2534        let mut ctx = openssl::bn::BigNumContext::new().unwrap();
2535        let pubkey = secret_key
2536            .public_key()
2537            .to_bytes(
2538                &group,
2539                openssl::ec::PointConversionForm::COMPRESSED,
2540                &mut ctx,
2541            )
2542            .unwrap();
2543        new_secp256r1_instruction_with_signature(b"hello", &signature, &pubkey.try_into().unwrap())
2544    }
2545
2546    #[test]
2547    fn test_precompile() {
2548        let mock_program_id = Pubkey::new_unique();
2549        declare_process_instruction!(MockBuiltin, 1, |_invoke_context| {
2550            Err(InstructionError::Custom(0xbabb1e))
2551        });
2552
2553        let mut secp256k1_account = AccountSharedData::new(1, 0, &native_loader::id());
2554        secp256k1_account.set_executable(true);
2555        let mut ed25519_account = AccountSharedData::new(1, 0, &native_loader::id());
2556        ed25519_account.set_executable(true);
2557        let mut secp256r1_account = AccountSharedData::new(1, 0, &native_loader::id());
2558        secp256r1_account.set_executable(true);
2559        let mut mock_program_account = AccountSharedData::new(1, 0, &native_loader::id());
2560        mock_program_account.set_executable(true);
2561
2562        let fee_payer = Pubkey::new_unique();
2563        let accounts_map: HashMap<Address, AccountSharedData> = HashMap::from([
2564            (
2565                fee_payer,
2566                AccountSharedData::new(1, 0, &system_program::id()),
2567            ),
2568            (secp256k1_program::id(), secp256k1_account),
2569            (ed25519_program::id(), ed25519_account),
2570            (solana_secp256r1_program::id(), secp256r1_account),
2571            (mock_program_id, mock_program_account),
2572        ]);
2573
2574        let message = new_sanitized_message(Message::new(
2575            &[
2576                secp256k1_instruction_for_test(),
2577                ed25519_instruction_for_test(),
2578                secp256r1_instruction_for_test(),
2579                Instruction::new_with_bytes(mock_program_id, &[], vec![]),
2580            ],
2581            Some(&fee_payer),
2582        ));
2583
2584        let accounts = message
2585            .account_keys()
2586            .iter()
2587            .map(|key| (*key, accounts_map.get(key).unwrap().clone()))
2588            .collect();
2589        let mut transaction_context = TransactionContext::new(accounts, Rent::default(), 1, 4, 4);
2590
2591        let sysvar_cache = SysvarCache::default();
2592        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
2593        program_cache_for_tx_batch.replenish(
2594            mock_program_id,
2595            Arc::new(ProgramCacheEntry::new_builtin(MockBuiltin::register)),
2596        );
2597
2598        struct MockCallback {}
2599        impl InvokeContextCallback for MockCallback {
2600            fn is_precompile(&self, program_id: &Pubkey) -> bool {
2601                program_id == &secp256k1_program::id()
2602                    || program_id == &ed25519_program::id()
2603                    || program_id == &solana_secp256r1_program::id()
2604            }
2605
2606            fn process_precompile(
2607                &self,
2608                program_id: &Pubkey,
2609                _data: &[u8],
2610                _instruction_datas: Vec<&[u8]>,
2611            ) -> std::result::Result<(), PrecompileError> {
2612                if self.is_precompile(program_id) {
2613                    Ok(())
2614                } else {
2615                    Err(PrecompileError::InvalidPublicKey)
2616                }
2617            }
2618        }
2619        let feature_set = SVMFeatureSet::all_enabled();
2620        let program_runtime_environments = ProgramRuntimeEnvironments::mock();
2621        let environment_config = EnvironmentConfig::new(
2622            Hash::default(),
2623            0,
2624            false,
2625            &MockCallback {},
2626            &feature_set,
2627            &program_runtime_environments,
2628            &sysvar_cache,
2629        );
2630        let mut invoke_context = InvokeContext::new(
2631            &mut transaction_context,
2632            &mut program_cache_for_tx_batch,
2633            environment_config,
2634            None,
2635            SVMTransactionExecutionBudget::default(),
2636            SVMTransactionExecutionCost::default(),
2637        );
2638        let result =
2639            invoke_context.process_message(&message, &mut ExecuteTimings::default(), &mut 0);
2640
2641        assert_eq!(result, Err((3, InstructionError::Custom(0xbabb1e))));
2642        assert_eq!(
2643            transaction_context.number_of_called_instructions_in_trace(),
2644            4
2645        );
2646    }
2647}