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