Skip to main content

solana_program_runtime/
invoke_context.rs

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