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