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