Skip to main content

solana_program_runtime/
invoke_context.rs

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