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    solana_account::{AccountSharedData, WritableAccount, create_account_shared_data_for_test},
5    solana_epoch_schedule::EpochSchedule,
6    solana_instruction::AccountMeta,
7    solana_message::{LegacyMessage, Message, SanitizedMessage},
8    solana_sdk_ids::sysvar,
9    solana_transaction_context::transaction_accounts::KeyedAccountSharedData,
10    std::collections::{HashMap, HashSet},
11};
12use {
13    crate::{
14        execution_budget::{SVMTransactionExecutionBudget, SVMTransactionExecutionCost},
15        loaded_programs::{
16            ProgramCacheForTxBatch, ProgramRuntimeEnvironment, ProgramRuntimeEnvironments,
17        },
18        memory_context::{MemoryContext, MemoryContexts},
19        program_cache_entry::ProgramCacheEntryType,
20        stable_log,
21        sysvar_cache::SysvarCache,
22    },
23    solana_hash::Hash,
24    solana_instruction::{Instruction, error::InstructionError},
25    solana_pubkey::Pubkey,
26    solana_sbpf::{
27        ebpf::MM_HEAP_START,
28        elf::{ElfError, Executable as GenericExecutable},
29        error::{EbpfError, ProgramResult},
30        memory_region::MemoryMapping,
31        program::{BuiltinProgram, SBPFVersion},
32        vm::{Config, ContextObject, EbpfVm},
33    },
34    solana_sdk_ids::{
35        bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, loader_v4, native_loader,
36    },
37    solana_svm_callback::InvokeContextCallback,
38    solana_svm_feature_set::SVMFeatureSet,
39    solana_svm_log_collector::{LogCollector, ic_msg},
40    solana_svm_measure::measure::Measure,
41    solana_svm_timings::{ExecuteDetailsTimings, ExecuteTimings},
42    solana_svm_transaction::svm_message::SVMMessage,
43    solana_svm_type_overrides::sync::Arc,
44    solana_transaction_context::{
45        IndexOfAccount, MAX_ACCOUNTS_PER_TRANSACTION, instruction::InstructionContext,
46        instruction_accounts::InstructionAccount, transaction::TransactionContext,
47    },
48    std::{
49        alloc::Layout,
50        borrow::Cow,
51        cell::{Cell, RefCell},
52        fmt::{self, Debug},
53        ptr,
54        rc::Rc,
55        time::Duration,
56    },
57};
58
59pub type BuiltinFunctionRegisterer =
60    fn(&mut BuiltinProgram<InvokeContext<'static, 'static>>, &str) -> Result<(), ElfError>;
61pub type Executable = GenericExecutable<InvokeContext<'static, 'static>>;
62pub type RegisterTrace<'a> = &'a [[u64; 12]];
63
64/// Adapter so we can unify the interfaces of built-in programs and syscalls
65#[macro_export]
66macro_rules! declare_process_instruction {
67    ($process_instruction:ident, $cu_to_consume:expr, |$invoke_context:ident| $inner:tt) => {
68        $crate::solana_sbpf::declare_builtin_function!(
69            $process_instruction,
70            fn rust(
71                invoke_context: &mut $crate::invoke_context::InvokeContext<'_, '_>,
72                _arg0: u64,
73                _arg1: u64,
74                _arg2: u64,
75                _arg3: u64,
76                _arg4: u64,
77            ) -> Result<u64, Box<dyn std::error::Error>> {
78                fn process_instruction_inner(
79                    $invoke_context: &mut $crate::invoke_context::InvokeContext,
80                ) -> std::result::Result<(), $crate::__private::InstructionError>
81                    $inner
82
83                let consumption_result = if $cu_to_consume > 0
84                {
85                    invoke_context.compute_meter.consume_checked($cu_to_consume)
86                } else {
87                    Ok(())
88                };
89                consumption_result
90                    .and_then(|_| {
91                        process_instruction_inner(invoke_context)
92                            .map(|_| 0)
93                            .map_err(|err| Box::new(err) as Box<dyn std::error::Error>)
94                    })
95                    .into()
96            }
97        );
98    };
99}
100
101impl ContextObject for InvokeContext<'_, '_> {
102    fn consume(&mut self, amount: u64) {
103        // 1 to 1 instruction to compute unit mapping
104        // ignore overflow, Ebpf will bail if exceeded
105        let compute_meter = self.compute_meter.0.get();
106        self.compute_meter
107            .0
108            .set(compute_meter.saturating_sub(amount));
109    }
110
111    fn get_remaining(&self) -> u64 {
112        self.compute_meter.0.get()
113    }
114
115    fn active_mapping_ptr(&mut self) -> ptr::NonNull<MemoryMapping> {
116        let memory = self
117            .memory_contexts
118            .memory_mapping_mut()
119            .expect("The memory context must have been set for the current instruction");
120        ptr::NonNull::from_mut(memory)
121    }
122}
123
124#[derive(Clone, PartialEq, Eq, Debug)]
125pub struct AllocErr;
126impl fmt::Display for AllocErr {
127    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
128        f.write_str("Error: Memory allocation failed")
129    }
130}
131
132pub struct BpfAllocator {
133    len: u64,
134    pos: u64,
135}
136
137impl BpfAllocator {
138    pub fn new(len: u64) -> Self {
139        Self { len, pos: 0 }
140    }
141
142    pub fn alloc(&mut self, layout: Layout) -> Result<u64, AllocErr> {
143        let bytes_to_align = (self.pos as *const u8).align_offset(layout.align()) as u64;
144        if self
145            .pos
146            .saturating_add(bytes_to_align)
147            .saturating_add(layout.size() as u64)
148            <= self.len
149        {
150            self.pos = self.pos.saturating_add(bytes_to_align);
151            let addr = MM_HEAP_START.saturating_add(self.pos);
152            self.pos = self.pos.saturating_add(layout.size() as u64);
153            Ok(addr)
154        } else {
155            Err(AllocErr)
156        }
157    }
158}
159
160pub struct EnvironmentConfig<'a> {
161    pub blockhash: Hash,
162    pub blockhash_lamports_per_signature: u64,
163    alpenglow_migration_succeeded: bool,
164    epoch_stake_callback: &'a dyn InvokeContextCallback,
165    feature_set: &'a SVMFeatureSet,
166    program_runtime_environments: &'a ProgramRuntimeEnvironments,
167    sysvar_cache: &'a SysvarCache,
168}
169impl<'a> EnvironmentConfig<'a> {
170    pub fn new(
171        blockhash: Hash,
172        blockhash_lamports_per_signature: u64,
173        alpenglow_migration_succeeded: bool,
174        epoch_stake_callback: &'a dyn InvokeContextCallback,
175        feature_set: &'a SVMFeatureSet,
176        program_runtime_environments: &'a ProgramRuntimeEnvironments,
177        sysvar_cache: &'a SysvarCache,
178    ) -> Self {
179        Self {
180            blockhash,
181            blockhash_lamports_per_signature,
182            alpenglow_migration_succeeded,
183            epoch_stake_callback,
184            feature_set,
185            program_runtime_environments,
186            sysvar_cache,
187        }
188    }
189
190    /// Get cached sysvars
191    pub fn sysvar_cache(&self) -> &SysvarCache {
192        self.sysvar_cache
193    }
194}
195
196pub struct ComputeMeter(Cell<u64>);
197
198impl ComputeMeter {
199    /// Consume compute units
200    pub fn consume_checked(&self, amount: u64) -> Result<(), Box<dyn std::error::Error>> {
201        let compute_meter = self.0.get();
202        let exceeded = compute_meter < amount;
203        self.0.set(compute_meter.saturating_sub(amount));
204        if exceeded {
205            return Err(Box::new(InstructionError::ComputationalBudgetExceeded));
206        }
207        Ok(())
208    }
209
210    /// Set compute units
211    ///
212    /// Only use for tests and benchmarks
213    #[cfg(feature = "dev-context-only-utils")]
214    pub fn mock_set_remaining(&self, remaining: u64) {
215        self.0.set(remaining);
216    }
217}
218
219/// Main pipeline from runtime to program execution.
220pub struct InvokeContext<'a, 'ix_data> {
221    /// Information about the currently executing transaction.
222    pub transaction_context: &'a mut TransactionContext<'ix_data>,
223    /// The local program cache for the transaction batch.
224    pub program_cache_for_tx_batch: &'a mut ProgramCacheForTxBatch,
225    /// Runtime configurations used to provision the invocation environment.
226    pub environment_config: EnvironmentConfig<'a>,
227    /// The compute budget for the current invocation.
228    compute_budget: SVMTransactionExecutionBudget,
229    /// The compute cost for the current invocation.
230    execution_cost: SVMTransactionExecutionCost,
231    /// Instruction compute meter, for tracking compute units consumed against
232    /// the designated compute budget during program execution.
233    pub compute_meter: ComputeMeter,
234    log_collector: Option<Rc<RefCell<LogCollector>>>,
235    /// Time spent so far executing nested program calls.
236    pub total_nested_exec_time: Duration,
237    pub timings: ExecuteDetailsTimings,
238    pub memory_contexts: MemoryContexts,
239    /// Pairs of index in TX instruction trace and VM register trace
240    register_traces: Vec<(usize, Vec<[u64; 12]>)>,
241    /// Debug port to use for this executing transaction.
242    #[cfg(feature = "sbpf-debugger")]
243    pub debug_port: Option<u16>,
244}
245
246impl<'a, 'ix_data> InvokeContext<'a, 'ix_data> {
247    pub fn new(
248        transaction_context: &'a mut TransactionContext<'ix_data>,
249        program_cache_for_tx_batch: &'a mut ProgramCacheForTxBatch,
250        environment_config: EnvironmentConfig<'a>,
251        log_collector: Option<Rc<RefCell<LogCollector>>>,
252        compute_budget: SVMTransactionExecutionBudget,
253        execution_cost: SVMTransactionExecutionCost,
254    ) -> Self {
255        Self {
256            transaction_context,
257            program_cache_for_tx_batch,
258            environment_config,
259            log_collector,
260            compute_budget,
261            execution_cost,
262            compute_meter: ComputeMeter(Cell::new(compute_budget.compute_unit_limit)),
263            total_nested_exec_time: Duration::ZERO,
264            timings: ExecuteDetailsTimings::default(),
265            memory_contexts: MemoryContexts::new(),
266            register_traces: Vec::new(),
267            #[cfg(feature = "sbpf-debugger")]
268            debug_port: None,
269        }
270    }
271
272    /// Push a stack frame onto the invocation stack
273    pub fn push(&mut self) -> Result<(), InstructionError> {
274        let instruction_context = self.transaction_context.get_next_instruction_context()?;
275        let program_id = instruction_context
276            .get_program_key()
277            .map_err(|_| InstructionError::UnsupportedProgramId)?;
278        if self.transaction_context.get_instruction_stack_height() != 0 {
279            let contains =
280                (0..self.transaction_context.get_instruction_stack_height()).any(|level| {
281                    self.transaction_context
282                        .get_instruction_context_at_nesting_level(level)
283                        .and_then(|instruction_context| instruction_context.get_program_key())
284                        .map(|program_key| program_key == program_id)
285                        .unwrap_or(false)
286                });
287            let is_last = self
288                .transaction_context
289                .get_current_instruction_context()
290                .and_then(|instruction_context| instruction_context.get_program_key())
291                .map(|program_key| program_key == program_id)
292                .unwrap_or(false);
293            if contains && !is_last {
294                // Reentrancy not allowed unless caller is calling itself
295                return Err(InstructionError::ReentrancyNotAllowed);
296            }
297        }
298
299        self.transaction_context.push()?;
300        self.memory_contexts.push_placeholder();
301        Ok(())
302    }
303
304    /// Pop a stack frame from the invocation stack
305    pub fn pop(&mut self) -> Result<(), InstructionError> {
306        self.memory_contexts.pop();
307        self.transaction_context.pop()
308    }
309
310    /// Current height of the invocation stack, top level instructions are height
311    /// `solana_instruction::TRANSACTION_LEVEL_STACK_HEIGHT`
312    pub fn get_stack_height(&self) -> usize {
313        self.transaction_context.get_instruction_stack_height()
314    }
315
316    /// Entrypoint for a cross-program invocation from a builtin program.
317    ///
318    /// Takes signer seeds and derives PDAs internally via
319    /// `create_program_address`, mirroring the SBF CPI path. This makes
320    /// it structurally impossible for a builtin to vouch for a non-PDA
321    /// address (e.g. a user wallet) as a signer.
322    pub fn native_invoke_signed(
323        &mut self,
324        instruction: Instruction,
325        signer_seeds: &[&[&[u8]]],
326    ) -> Result<(), InstructionError> {
327        let caller_program_id = *self
328            .transaction_context
329            .get_current_instruction_context()?
330            .get_program_key()?;
331        // The conversion from `PubkeyError` to `InstructionError` through
332        // num-traits is incorrect, but it's the existing behavior.
333        let signers = signer_seeds
334            .iter()
335            .map(|seeds| Pubkey::create_program_address(seeds, &caller_program_id))
336            .collect::<Result<Vec<Pubkey>, solana_pubkey::PubkeyError>>()
337            .map_err(|e| e as u64)?;
338        self.prepare_next_cpi_instruction(instruction, &signers)?;
339        let mut compute_units_consumed = 0;
340        self.process_instruction(&mut compute_units_consumed, &mut ExecuteTimings::default())?;
341        Ok(())
342    }
343
344    /// Helper to prepare for process_instruction() when the instruction is not a top level one,
345    /// and depends on `AccountMeta`s
346    pub fn prepare_next_cpi_instruction(
347        &mut self,
348        instruction: Instruction,
349        signers: &[Pubkey],
350    ) -> Result<(), InstructionError> {
351        // We reference accounts by an u8 index, so we have a total of 256 accounts.
352        let mut transaction_callee_map: Vec<u16> = vec![u16::MAX; MAX_ACCOUNTS_PER_TRANSACTION];
353        let mut instruction_accounts: Vec<InstructionAccount> =
354            Vec::with_capacity(instruction.accounts.len());
355
356        // This code block is necessary to restrict the scope of the immutable borrow of
357        // transaction context (the `instruction_context` variable). At the end of this
358        // function, we must borrow it again as mutable.
359        let program_account_index = {
360            let instruction_context = self.transaction_context.get_current_instruction_context()?;
361
362            for account_meta in instruction.accounts.iter() {
363                let index_in_transaction = self
364                    .transaction_context
365                    .find_index_of_account(&account_meta.pubkey)
366                    .ok_or_else(|| {
367                        ic_msg!(
368                            self,
369                            "Instruction references an unknown account {}",
370                            account_meta.pubkey,
371                        );
372                        InstructionError::MissingAccount
373                    })?;
374
375                debug_assert!((index_in_transaction as usize) < transaction_callee_map.len());
376                let index_in_callee = transaction_callee_map
377                    .get_mut(index_in_transaction as usize)
378                    .unwrap();
379
380                if (*index_in_callee as usize) < instruction_accounts.len() {
381                    let cloned_account = {
382                        let instruction_account = instruction_accounts
383                            .get_mut(*index_in_callee as usize)
384                            .ok_or(InstructionError::MissingAccount)?;
385                        instruction_account.set_is_signer(
386                            instruction_account.is_signer() || account_meta.is_signer,
387                        );
388                        instruction_account.set_is_writable(
389                            instruction_account.is_writable() || account_meta.is_writable,
390                        );
391                        *instruction_account
392                    };
393                    instruction_accounts.push(cloned_account);
394                } else {
395                    *index_in_callee = instruction_accounts.len() as u16;
396                    instruction_accounts.push(InstructionAccount::new(
397                        index_in_transaction,
398                        account_meta.is_signer,
399                        account_meta.is_writable,
400                    ));
401                }
402            }
403
404            for current_index in 0..instruction_accounts.len() {
405                let instruction_account = instruction_accounts.get(current_index).unwrap();
406                let index_in_callee = *transaction_callee_map
407                    .get(instruction_account.index_in_transaction as usize)
408                    .unwrap() as usize;
409
410                if current_index != index_in_callee {
411                    let (is_signer, is_writable) = {
412                        let reference_account = instruction_accounts
413                            .get(index_in_callee)
414                            .ok_or(InstructionError::MissingAccount)?;
415                        (
416                            reference_account.is_signer(),
417                            reference_account.is_writable(),
418                        )
419                    };
420
421                    let current_account = instruction_accounts.get_mut(current_index).unwrap();
422                    current_account.set_is_signer(current_account.is_signer() || is_signer);
423                    current_account.set_is_writable(current_account.is_writable() || is_writable);
424                    // This account is repeated, so there is no need to check for permissions
425                    continue;
426                }
427
428                let index_in_caller = instruction_context.get_index_of_account_in_instruction(
429                    instruction_account.index_in_transaction,
430                )?;
431
432                // This unwrap is safe because instruction.accounts.len() == instruction_accounts.len()
433                let account_key = &instruction.accounts.get(current_index).unwrap().pubkey;
434                // get_index_of_account_in_instruction has already checked if the index is valid.
435                let caller_instruction_account = instruction_context
436                    .instruction_accounts()
437                    .get(index_in_caller as usize)
438                    .unwrap();
439
440                // Readonly in caller cannot become writable in callee
441                if instruction_account.is_writable() && !caller_instruction_account.is_writable() {
442                    ic_msg!(self, "{}'s writable privilege escalated", account_key,);
443                    return Err(InstructionError::PrivilegeEscalation);
444                }
445
446                // To be signed in the callee,
447                // it must be either signed in the caller or by the program
448                if instruction_account.is_signer()
449                    && !(caller_instruction_account.is_signer() || signers.contains(account_key))
450                {
451                    ic_msg!(self, "{}'s signer privilege escalated", account_key,);
452                    return Err(InstructionError::PrivilegeEscalation);
453                }
454            }
455
456            // Find and validate executables / program accounts
457            let callee_program_id = &instruction.program_id;
458            let program_account_index_in_transaction = self
459                .transaction_context
460                .find_index_of_account(callee_program_id);
461            let program_account_index_in_instruction = program_account_index_in_transaction
462                .map(|index| instruction_context.get_index_of_account_in_instruction(index));
463
464            // We first check if the account exists in the transaction, and then see if it is part
465            // of the instruction.
466            if program_account_index_in_instruction.is_none()
467                || program_account_index_in_instruction.unwrap().is_err()
468            {
469                ic_msg!(self, "Unknown program {}", callee_program_id);
470                return Err(InstructionError::MissingAccount);
471            }
472
473            // SAFETY: This unwrap is safe, because we checked the index in instruction in the
474            // previous if-condition.
475            program_account_index_in_transaction.unwrap()
476        };
477
478        // This ? operator should not error out because `fn get_current_instruction_index` is also called
479        // in `get_current_instruction_context`
480        let caller_index = self.transaction_context.get_current_instruction_index()?;
481        self.transaction_context.configure_instruction_at_index(
482            self.transaction_context.get_instruction_trace_length(),
483            program_account_index,
484            instruction_accounts,
485            transaction_callee_map,
486            Cow::Owned(instruction.data),
487            Some(caller_index as u16),
488        )?;
489        Ok(())
490    }
491
492    /// Prepare the instruction trace with all the top level instructions
493    pub fn prepare_top_level_instructions(
494        &mut self,
495        message: &'ix_data impl SVMMessage,
496    ) -> Result<(), (u8, InstructionError)> {
497        for (top_level_instruction_index, (_, instruction)) in
498            message.program_instructions_iter().enumerate()
499        {
500            let mut transaction_callee_map: Vec<u16> = vec![u16::MAX; MAX_ACCOUNTS_PER_TRANSACTION];
501
502            let mut instruction_accounts: Vec<InstructionAccount> =
503                Vec::with_capacity(instruction.accounts.len());
504            for index_in_transaction in instruction.accounts.iter() {
505                let index_in_callee = transaction_callee_map
506                    .get_mut(*index_in_transaction as usize)
507                    .expect("Invalid index in transaction");
508
509                if (*index_in_callee as usize) > instruction_accounts.len() {
510                    *index_in_callee = instruction_accounts.len() as u16;
511                }
512
513                let index_in_transaction = *index_in_transaction as usize;
514                instruction_accounts.push(InstructionAccount::new(
515                    index_in_transaction as IndexOfAccount,
516                    message.is_signer(index_in_transaction),
517                    message.is_writable(index_in_transaction),
518                ));
519            }
520
521            self.transaction_context
522                .configure_instruction_at_index(
523                    top_level_instruction_index,
524                    instruction.program_id_index as u16,
525                    instruction_accounts,
526                    transaction_callee_map,
527                    Cow::Borrowed(instruction.data),
528                    None,
529                )
530                .map_err(|err| (top_level_instruction_index as u8, err))?;
531        }
532        Ok(())
533    }
534
535    /// Processes an instruction and returns how many compute units were used
536    pub fn process_instruction(
537        &mut self,
538        compute_units_consumed: &mut u64,
539        timings: &mut ExecuteTimings,
540    ) -> Result<(), InstructionError> {
541        *compute_units_consumed = 0;
542        self.push()?;
543        self.process_executable_chain(compute_units_consumed, timings)
544            // MUST pop if and only if `push` succeeded, independent of `result`.
545            // Thus, the `.and()` instead of an `.and_then()`.
546            .and(self.pop())
547    }
548
549    /// Processes a precompile instruction
550    pub fn process_precompile(
551        &mut self,
552        program_id: &Pubkey,
553        instruction_data: &[u8],
554        message_instruction_datas_iter: impl Iterator<Item = &'ix_data [u8]>,
555    ) -> Result<(), InstructionError> {
556        self.push()?;
557        let instruction_datas: Vec<_> = message_instruction_datas_iter.collect();
558        self.environment_config
559            .epoch_stake_callback
560            .process_precompile(program_id, instruction_data, instruction_datas)
561            .map_err(InstructionError::from)
562            .and(self.pop())
563    }
564
565    /// Calls the instruction's program entrypoint method
566    fn process_executable_chain(
567        &mut self,
568        compute_units_consumed: &mut u64,
569        timings: &mut ExecuteTimings,
570    ) -> Result<(), InstructionError> {
571        let instruction_context = self.transaction_context.get_current_instruction_context()?;
572        let process_executable_chain_time = Measure::start("process_executable_chain_time");
573
574        let builtin_id = {
575            let owner_id = instruction_context.get_program_owner()?;
576            if native_loader::check_id(&owner_id) {
577                *instruction_context.get_program_key()?
578            } else if bpf_loader_deprecated::check_id(&owner_id)
579                || bpf_loader::check_id(&owner_id)
580                || bpf_loader_upgradeable::check_id(&owner_id)
581                || loader_v4::check_id(&owner_id)
582            {
583                owner_id
584            } else {
585                return Err(InstructionError::UnsupportedProgramId);
586            }
587        };
588
589        // The Murmur3 hash value (used by RBPF) of the string "entrypoint"
590        const ENTRYPOINT_KEY: u32 = 0x71E3CF81;
591        let entry = self
592            .program_cache_for_tx_batch
593            .find(&builtin_id)
594            .ok_or(InstructionError::UnsupportedProgramId)?;
595        let function = match &entry.program {
596            ProgramCacheEntryType::Builtin(program) => program
597                .get_function_registry()
598                .lookup_by_key(ENTRYPOINT_KEY)
599                .map(|(_name, (function, _codegen))| function),
600            _ => None,
601        }
602        .ok_or(InstructionError::UnsupportedProgramId)?;
603
604        let program_id = *instruction_context.get_program_key()?;
605        self.transaction_context
606            .set_return_data(program_id, Vec::new())?;
607        let logger = self.get_log_collector();
608        stable_log::program_invoke(&logger, &program_id, self.get_stack_height());
609        let pre_remaining_units = self.get_remaining();
610        // For now, only built-ins are invoked from here, so the VM and its Config are irrelevant.
611        self.memory_contexts
612            .set_memory_context_abi_v1(MemoryContext::new(
613                BpfAllocator::new(0),
614                Vec::new(),
615                // SAFETY:
616                // This path invokes a builtin program, so this mapping is never used.
617                unsafe {
618                    MemoryMapping::new(Vec::new(), &Config::default(), SBPFVersion::Reserved)
619                        .unwrap()
620                },
621            ))?;
622        let mut vm = EbpfVm::new(
623            Arc::clone(
624                &**self
625                    .environment_config
626                    .program_runtime_environments
627                    .get_env_for_execution(),
628            ),
629            SBPFVersion::V0,
630            // Removes lifetime tracking
631            unsafe { std::mem::transmute::<&mut InvokeContext, &mut InvokeContext>(self) },
632            0,
633        );
634        vm.invoke_function(function);
635        let result = match vm.program_result {
636            ProgramResult::Ok(_) => {
637                stable_log::program_success(&logger, &program_id);
638                Ok(())
639            }
640            ProgramResult::Err(ref err) => {
641                if let EbpfError::SyscallError(syscall_error) = err {
642                    if let Some(instruction_err) = syscall_error.downcast_ref::<InstructionError>()
643                    {
644                        stable_log::program_failure(&logger, &program_id, instruction_err);
645                        Err(instruction_err.clone())
646                    } else {
647                        stable_log::program_failure(&logger, &program_id, syscall_error);
648                        Err(InstructionError::ProgramFailedToComplete)
649                    }
650                } else {
651                    stable_log::program_failure(&logger, &program_id, err);
652                    Err(InstructionError::ProgramFailedToComplete)
653                }
654            }
655        };
656        let post_remaining_units = self.get_remaining();
657        *compute_units_consumed = pre_remaining_units.saturating_sub(post_remaining_units);
658
659        if builtin_id == program_id && result.is_ok() && *compute_units_consumed == 0 {
660            return Err(InstructionError::BuiltinProgramsMustConsumeComputeUnits);
661        }
662
663        timings
664            .execute_accessories
665            .process_instructions
666            .process_executable_chain_us += process_executable_chain_time.end_as_us();
667        result
668    }
669
670    /// Get this invocation's LogCollector
671    pub fn get_log_collector(&self) -> Option<Rc<RefCell<LogCollector>>> {
672        self.log_collector.clone()
673    }
674
675    #[cfg(feature = "dev-context-only-utils")]
676    pub fn set_alpenglow_migration_succeeded_for_tests(&mut self, succeeded: bool) {
677        self.environment_config.alpenglow_migration_succeeded = succeeded;
678    }
679
680    /// Get this invocation's compute budget
681    pub fn get_compute_budget(&self) -> &SVMTransactionExecutionBudget {
682        &self.compute_budget
683    }
684
685    /// Get this invocation's compute budget
686    pub fn get_execution_cost(&self) -> &SVMTransactionExecutionCost {
687        &self.execution_cost
688    }
689
690    /// Get the current feature set.
691    pub fn get_feature_set(&self) -> &SVMFeatureSet {
692        self.environment_config.feature_set
693    }
694
695    pub fn get_program_runtime_environment_for_deployment(&self) -> &ProgramRuntimeEnvironment {
696        self.environment_config
697            .program_runtime_environments
698            .get_env_for_deployment()
699    }
700
701    pub fn is_deprecate_legacy_vote_ixs_active(&self) -> bool {
702        self.environment_config
703            .feature_set
704            .deprecate_legacy_vote_ixs
705    }
706
707    pub fn is_alpenglow_migration_succeeded(&self) -> bool {
708        self.environment_config.alpenglow_migration_succeeded
709    }
710
711    /// Get cached epoch total stake.
712    pub fn get_epoch_stake(&self) -> u64 {
713        self.environment_config
714            .epoch_stake_callback
715            .get_epoch_stake()
716    }
717
718    /// Get cached stake for the epoch vote account.
719    pub fn get_epoch_stake_for_vote_account(&self, pubkey: &'a Pubkey) -> u64 {
720        self.environment_config
721            .epoch_stake_callback
722            .get_epoch_stake_for_vote_account(pubkey)
723    }
724
725    pub fn is_precompile(&self, pubkey: &Pubkey) -> bool {
726        self.environment_config
727            .epoch_stake_callback
728            .is_precompile(pubkey)
729    }
730
731    // Should alignment be enforced during user pointer translation
732    pub fn get_check_aligned(&self) -> bool {
733        self.transaction_context
734            .get_current_instruction_context()
735            .and_then(|instruction_context| {
736                let owner_id = instruction_context.get_program_owner();
737                debug_assert!(owner_id.is_ok());
738                owner_id
739            })
740            .map(|owner_key| owner_key != bpf_loader_deprecated::id())
741            .unwrap_or(true)
742    }
743
744    /// Insert a VM register trace
745    pub fn insert_register_trace(&mut self, register_trace: Vec<[u64; 12]>) {
746        if register_trace.is_empty() {
747            return;
748        }
749        let Ok(instruction_context) = self.transaction_context.get_current_instruction_context()
750        else {
751            return;
752        };
753        self.register_traces
754            .push((instruction_context.get_index_in_trace(), register_trace));
755    }
756
757    /// Iterates over all VM register traces (including CPI)
758    pub fn iterate_vm_traces(
759        &self,
760        callback: &dyn Fn(InstructionContext, &Executable, RegisterTrace),
761    ) {
762        for (index_in_trace, register_trace) in &self.register_traces {
763            let Ok(instruction_context) = self
764                .transaction_context
765                .get_instruction_context_at_index_in_trace(*index_in_trace)
766            else {
767                continue;
768            };
769            let Ok(program_id) = instruction_context.get_program_key() else {
770                continue;
771            };
772            let Some(entry) = self.program_cache_for_tx_batch.find(program_id) else {
773                continue;
774            };
775            let ProgramCacheEntryType::Loaded(ref executable) = entry.program else {
776                continue;
777            };
778            callback(instruction_context, executable, register_trace.as_slice());
779        }
780    }
781}
782
783#[cfg(feature = "dev-context-only-utils")]
784#[macro_export]
785macro_rules! with_mock_invoke_context_with_feature_set {
786    (
787        $invoke_context:ident,
788        $transaction_context:ident,
789        $feature_set:ident,
790        $top_level_instructions:literal,
791        $transaction_accounts:expr,
792        $all_accounts:expr $(,)?
793    ) => {
794        use {
795            solana_svm_callback::InvokeContextCallback,
796            solana_svm_log_collector::LogCollector,
797            $crate::{
798                __private::{Hash, ReadableAccount, Rent, TransactionContext},
799                execution_budget::{SVMTransactionExecutionBudget, SVMTransactionExecutionCost},
800                invoke_context::{EnvironmentConfig, InvokeContext},
801                loaded_programs::{ProgramCacheForTxBatch, ProgramRuntimeEnvironments},
802                sysvar_cache::SysvarCache,
803            },
804        };
805
806        struct MockInvokeContextCallback {}
807        impl InvokeContextCallback for MockInvokeContextCallback {}
808
809        let compute_budget = SVMTransactionExecutionBudget::new_with_defaults(
810            $feature_set.raise_cpi_nesting_limit_to_8,
811        );
812        let mut sysvar_cache = SysvarCache::default();
813        sysvar_cache.fill_missing_entries(|pubkey, callback| {
814            for (key, account) in $all_accounts.iter() {
815                if key == pubkey {
816                    callback(account.data());
817                }
818            }
819        });
820        let mut $transaction_context = TransactionContext::new(
821            $transaction_accounts,
822            Rent::default(),
823            compute_budget.max_instruction_stack_depth,
824            compute_budget.max_instruction_trace_length,
825            $top_level_instructions,
826        );
827        let program_runtime_environments = ProgramRuntimeEnvironments::mock();
828        let environment_config = EnvironmentConfig::new(
829            Hash::default(),
830            0,
831            false,
832            &MockInvokeContextCallback {},
833            $feature_set,
834            &program_runtime_environments,
835            &sysvar_cache,
836        );
837        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
838        let mut $invoke_context = InvokeContext::new(
839            &mut $transaction_context,
840            &mut program_cache_for_tx_batch,
841            environment_config,
842            Some(LogCollector::new_ref()),
843            compute_budget,
844            SVMTransactionExecutionCost::default(),
845        );
846    };
847    (
848        $invoke_context:ident,
849        $transaction_context:ident,
850        $feature_set:ident,
851        $top_level_instructions:literal,
852        $transaction_accounts:expr $(,)?
853    ) => {
854        let transaction_accounts: Vec<(solana_pubkey::Pubkey, solana_account::AccountSharedData)> =
855            $transaction_accounts;
856        $crate::with_mock_invoke_context_with_feature_set!(
857            $invoke_context,
858            $transaction_context,
859            $feature_set,
860            $top_level_instructions,
861            transaction_accounts,
862            &transaction_accounts
863        );
864    };
865    (
866        $invoke_context:ident,
867        $transaction_context:ident,
868        $feature_set:ident,
869        $transaction_accounts:expr $(,)?
870    ) => {
871        $crate::with_mock_invoke_context_with_feature_set!(
872            $invoke_context,
873            $transaction_context,
874            $feature_set,
875            1,
876            $transaction_accounts
877        );
878    };
879}
880
881#[cfg(feature = "dev-context-only-utils")]
882#[macro_export]
883macro_rules! with_mock_invoke_context {
884    (
885        $invoke_context:ident,
886        $transaction_context:ident,
887        $top_level_instructions:literal,
888        $transaction_accounts:expr $(,)?
889    ) => {
890        let feature_set = &solana_svm_feature_set::SVMFeatureSet::default();
891        $crate::with_mock_invoke_context_with_feature_set!(
892            $invoke_context,
893            $transaction_context,
894            feature_set,
895            $top_level_instructions,
896            $transaction_accounts
897        )
898    };
899    (
900        $invoke_context:ident,
901        $transaction_context:ident,
902        $transaction_accounts:expr $(,)?
903    ) => {
904        with_mock_invoke_context!(
905            $invoke_context,
906            $transaction_context,
907            1,
908            $transaction_accounts
909        );
910    };
911}
912
913#[cfg(feature = "dev-context-only-utils")]
914pub fn mock_compile_message<A>(
915    instruction: &Instruction,
916    accounts: &[(Pubkey, A)],
917    program_id: &Pubkey,
918    loader_key: &Pubkey,
919) -> (SanitizedMessage, Vec<(Pubkey, AccountSharedData)>)
920where
921    AccountSharedData: From<A>,
922    A: Clone,
923{
924    let message = Message::new(std::slice::from_ref(instruction), None);
925    let transaction_accounts: Vec<_> = message
926        .account_keys
927        .iter()
928        .map(|key| {
929            let account = accounts
930                .iter()
931                .find(|(k, _)| k == key)
932                .map(|(_, a)| AccountSharedData::from(a.clone()))
933                .unwrap_or_else(|| {
934                    if key == program_id {
935                        let mut account = AccountSharedData::new(0, 0, loader_key);
936                        account.set_executable(true);
937                        account
938                    } else {
939                        AccountSharedData::default()
940                    }
941                });
942            (*key, account)
943        })
944        .collect();
945
946    let sanitized_message = SanitizedMessage::Legacy(LegacyMessage::new(message, &HashSet::new()));
947
948    (sanitized_message, transaction_accounts)
949}
950
951#[cfg(feature = "dev-context-only-utils")]
952pub fn mock_process_instruction_with_feature_set<
953    F: FnMut(&mut InvokeContext),
954    G: FnMut(&mut InvokeContext),
955>(
956    program_id: &Pubkey,
957    instruction_data: &[u8],
958    mut accounts: Vec<KeyedAccountSharedData>,
959    instruction_account_metas: Vec<AccountMeta>,
960    expected_result: Result<(), InstructionError>,
961    builtin: BuiltinFunctionRegisterer,
962    mut pre_adjustments: F,
963    mut post_adjustments: G,
964    feature_set: &SVMFeatureSet,
965) -> Vec<AccountSharedData> {
966    let original_len = accounts.len();
967    if !accounts
968        .iter()
969        .any(|(key, _)| *key == sysvar::epoch_schedule::id())
970    {
971        accounts.push((
972            sysvar::epoch_schedule::id(),
973            create_account_shared_data_for_test(&EpochSchedule::default()),
974        ));
975    }
976
977    let instruction =
978        Instruction::new_with_bytes(*program_id, instruction_data, instruction_account_metas);
979    let (sanitized_message, transaction_accounts) =
980        mock_compile_message(&instruction, &accounts, program_id, &native_loader::id());
981
982    let program_owner = accounts
983        .iter()
984        .find(|(key, _)| key == program_id)
985        .map(|(_, acct)| *acct.owner())
986        .unwrap_or_else(native_loader::id);
987    let is_builtin = native_loader::check_id(&program_owner);
988
989    with_mock_invoke_context_with_feature_set!(
990        invoke_context,
991        transaction_context,
992        feature_set,
993        1,
994        transaction_accounts,
995        &accounts
996    );
997
998    let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
999    program_cache_for_tx_batch.replenish(
1000        if is_builtin {
1001            *program_id
1002        } else {
1003            program_owner
1004        },
1005        Arc::new(ProgramCacheEntry::new_builtin(0, 0, builtin)),
1006    );
1007    program_cache_for_tx_batch.set_slot_for_tests(
1008        invoke_context
1009            .environment_config
1010            .sysvar_cache()
1011            .get_clock()
1012            .map(|clock| clock.slot)
1013            .unwrap_or(1),
1014    );
1015    invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1016
1017    pre_adjustments(&mut invoke_context);
1018
1019    invoke_context
1020        .prepare_top_level_instructions(&sanitized_message)
1021        .unwrap();
1022
1023    let result = invoke_context.process_instruction(&mut 0, &mut ExecuteTimings::default());
1024    assert_eq!(result, expected_result);
1025    post_adjustments(&mut invoke_context);
1026
1027    let txn_result_keys: Vec<_> = (0..transaction_context.get_number_of_accounts())
1028        .map(|i| *transaction_context.get_key_of_account_at_index(i).unwrap())
1029        .collect();
1030    let txn_result_accounts = transaction_context.deconstruct_without_keys().unwrap();
1031    let txn_result_map = txn_result_keys
1032        .into_iter()
1033        .zip(txn_result_accounts)
1034        .collect::<HashMap<Pubkey, AccountSharedData>>();
1035
1036    accounts
1037        .into_iter()
1038        .take(original_len)
1039        .map(|(key, original)| txn_result_map.get(&key).cloned().unwrap_or(original))
1040        .collect()
1041}
1042
1043#[cfg(feature = "dev-context-only-utils")]
1044pub fn mock_process_instruction<F: FnMut(&mut InvokeContext), G: FnMut(&mut InvokeContext)>(
1045    program_id: &Pubkey,
1046    instruction_data: &[u8],
1047    accounts: Vec<KeyedAccountSharedData>,
1048    instruction_account_metas: Vec<AccountMeta>,
1049    expected_result: Result<(), InstructionError>,
1050    builtin: BuiltinFunctionRegisterer,
1051    pre_adjustments: F,
1052    post_adjustments: G,
1053) -> Vec<AccountSharedData> {
1054    mock_process_instruction_with_feature_set(
1055        program_id,
1056        instruction_data,
1057        accounts,
1058        instruction_account_metas,
1059        expected_result,
1060        builtin,
1061        pre_adjustments,
1062        post_adjustments,
1063        &SVMFeatureSet::all_enabled(),
1064    )
1065}
1066
1067#[cfg(test)]
1068mod tests {
1069    use {
1070        super::*,
1071        crate::execution_budget::{
1072            DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, MAX_INSTRUCTION_STACK_DEPTH,
1073            MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268,
1074        },
1075        serde::{Deserialize, Serialize},
1076        solana_account::Account,
1077        solana_keypair::Keypair,
1078        solana_rent::Rent,
1079        solana_sbpf::program::BuiltinFunctionDefinition,
1080        solana_sdk_ids::system_program,
1081        solana_signer::Signer,
1082        solana_svm_feature_set::SVMFeatureSet,
1083        solana_transaction::{Transaction, sanitized::SanitizedTransaction},
1084        solana_transaction_context::MAX_ACCOUNTS_PER_INSTRUCTION,
1085        test_case::test_case,
1086    };
1087
1088    #[derive(Debug, Serialize, Deserialize)]
1089    enum MockInstruction {
1090        NoopSuccess,
1091        NoopFail,
1092        ModifyOwned,
1093        ModifyNotOwned,
1094        ModifyReadonly,
1095        UnbalancedPush,
1096        UnbalancedPop,
1097        ConsumeComputeUnits {
1098            compute_units_to_consume: u64,
1099            desired_result: Result<(), InstructionError>,
1100        },
1101        Resize {
1102            new_len: u64,
1103        },
1104    }
1105
1106    const MOCK_BUILTIN_COMPUTE_UNIT_COST: u64 = 1;
1107
1108    declare_process_instruction!(
1109        MockBuiltin,
1110        MOCK_BUILTIN_COMPUTE_UNIT_COST,
1111        |invoke_context| {
1112            let transaction_context = &invoke_context.transaction_context;
1113            let instruction_context = transaction_context.get_current_instruction_context()?;
1114            let instruction_data = instruction_context.get_instruction_data();
1115            let program_id = instruction_context.get_program_key()?;
1116            let instruction_accounts = (0..4)
1117                .map(|instruction_account_index| {
1118                    InstructionAccount::new(instruction_account_index, false, false)
1119                })
1120                .collect::<Vec<_>>();
1121            assert_eq!(
1122                program_id,
1123                instruction_context
1124                    .try_borrow_instruction_account(0)?
1125                    .get_owner()
1126            );
1127            assert_ne!(
1128                instruction_context
1129                    .try_borrow_instruction_account(1)?
1130                    .get_owner(),
1131                instruction_context.get_key_of_instruction_account(0)?
1132            );
1133
1134            if let Ok(instruction) = bincode::deserialize(instruction_data) {
1135                match instruction {
1136                    MockInstruction::NoopSuccess => (),
1137                    MockInstruction::NoopFail => return Err(InstructionError::GenericError),
1138                    MockInstruction::ModifyOwned => instruction_context
1139                        .try_borrow_instruction_account(0)?
1140                        .set_data_from_slice(&[1])?,
1141                    MockInstruction::ModifyNotOwned => instruction_context
1142                        .try_borrow_instruction_account(1)?
1143                        .set_data_from_slice(&[1])?,
1144                    MockInstruction::ModifyReadonly => instruction_context
1145                        .try_borrow_instruction_account(2)?
1146                        .set_data_from_slice(&[1])?,
1147                    MockInstruction::UnbalancedPush => {
1148                        instruction_context
1149                            .try_borrow_instruction_account(0)?
1150                            .checked_add_lamports(1)?;
1151                        let program_id = *transaction_context.get_key_of_account_at_index(3)?;
1152                        let metas = vec![
1153                            AccountMeta::new_readonly(
1154                                *transaction_context.get_key_of_account_at_index(0)?,
1155                                false,
1156                            ),
1157                            AccountMeta::new_readonly(
1158                                *transaction_context.get_key_of_account_at_index(1)?,
1159                                false,
1160                            ),
1161                        ];
1162                        let inner_instruction = Instruction::new_with_bincode(
1163                            program_id,
1164                            &MockInstruction::NoopSuccess,
1165                            metas,
1166                        );
1167                        invoke_context
1168                            .transaction_context
1169                            .configure_top_level_instruction_for_tests(
1170                                3,
1171                                instruction_accounts,
1172                                vec![],
1173                            )
1174                            .unwrap();
1175                        let result = invoke_context.push();
1176                        assert_eq!(result, Err(InstructionError::UnbalancedInstruction));
1177                        result?;
1178                        invoke_context
1179                            .native_invoke_signed(inner_instruction, &[])
1180                            .and(invoke_context.pop())?;
1181                    }
1182                    MockInstruction::UnbalancedPop => instruction_context
1183                        .try_borrow_instruction_account(0)?
1184                        .checked_add_lamports(1)?,
1185                    MockInstruction::ConsumeComputeUnits {
1186                        compute_units_to_consume,
1187                        desired_result,
1188                    } => {
1189                        invoke_context
1190                            .compute_meter
1191                            .consume_checked(compute_units_to_consume)
1192                            .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
1193                        return desired_result;
1194                    }
1195                    MockInstruction::Resize { new_len } => instruction_context
1196                        .try_borrow_instruction_account(0)?
1197                        .set_data_from_slice(&vec![0; new_len as usize])?,
1198                }
1199            } else {
1200                return Err(InstructionError::InvalidInstructionData);
1201            }
1202            Ok(())
1203        }
1204    );
1205
1206    #[test_case(false; "SIMD-0268 disabled")]
1207    #[test_case(true; "SIMD-0268 enabled")]
1208    fn test_instruction_stack_height(simd_0268_active: bool) {
1209        let feature_set = &SVMFeatureSet {
1210            raise_cpi_nesting_limit_to_8: simd_0268_active,
1211            ..SVMFeatureSet::all_enabled()
1212        };
1213        let max_depth = SVMTransactionExecutionBudget::new_with_defaults(simd_0268_active)
1214            .max_instruction_stack_depth;
1215        assert_eq!(
1216            max_depth,
1217            if simd_0268_active {
1218                MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268
1219            } else {
1220                MAX_INSTRUCTION_STACK_DEPTH
1221            },
1222        );
1223
1224        // Set up max_depth + 1 accounts (one extra to trigger the failing push)
1225        // and a matching program account for each.
1226        let mut invoke_stack = vec![];
1227        let mut transaction_accounts = vec![];
1228        let mut instruction_accounts = vec![];
1229        for index in 0..max_depth.saturating_add(1) {
1230            let program_id = solana_pubkey::new_rand();
1231            invoke_stack.push(program_id);
1232            transaction_accounts.push((
1233                solana_pubkey::new_rand(),
1234                AccountSharedData::new(1, 1, &program_id),
1235            ));
1236            instruction_accounts.push(InstructionAccount::new(
1237                index as IndexOfAccount,
1238                false,
1239                true,
1240            ));
1241        }
1242
1243        // Append program accounts after the regular accounts so that
1244        // `first_program_account + depth` indexes the right program.
1245        let first_program_account = transaction_accounts.len();
1246        for (index, program_id) in invoke_stack.iter().enumerate() {
1247            transaction_accounts.push((
1248                *program_id,
1249                AccountSharedData::new(1, 1, &solana_pubkey::Pubkey::default()),
1250            ));
1251            instruction_accounts.push(InstructionAccount::new(
1252                index as IndexOfAccount,
1253                false,
1254                false,
1255            ));
1256        }
1257        with_mock_invoke_context_with_feature_set!(
1258            invoke_context,
1259            transaction_context,
1260            feature_set,
1261            transaction_accounts,
1262        );
1263
1264        // Each push must succeed and the stack height must track.
1265        for depth in 0..max_depth {
1266            assert_eq!(invoke_context.get_stack_height(), depth);
1267            invoke_context
1268                .transaction_context
1269                .configure_top_level_instruction_for_tests(
1270                    (first_program_account.saturating_add(depth)) as IndexOfAccount,
1271                    instruction_accounts.clone(),
1272                    vec![],
1273                )
1274                .unwrap();
1275            assert!(
1276                invoke_context.push().is_ok(),
1277                "push at depth {depth} should succeed (max_depth={max_depth})",
1278            );
1279        }
1280
1281        // At exactly max_depth, one more push must fail with CallDepth.
1282        assert_eq!(invoke_context.get_stack_height(), max_depth);
1283        invoke_context
1284            .transaction_context
1285            .configure_top_level_instruction_for_tests(
1286                (first_program_account.saturating_add(max_depth)) as IndexOfAccount,
1287                instruction_accounts.clone(),
1288                vec![],
1289            )
1290            .unwrap();
1291        assert_eq!(invoke_context.push(), Err(InstructionError::CallDepth),);
1292
1293        // Stack height must not have changed after the rejected push.
1294        assert_eq!(invoke_context.get_stack_height(), max_depth);
1295    }
1296
1297    #[test]
1298    fn test_max_instruction_trace_length_top_level() {
1299        const MAX_INSTRUCTIONS: usize = 8;
1300        let mut transaction_context = TransactionContext::new(
1301            vec![(
1302                Pubkey::new_unique(),
1303                AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1304            )],
1305            Rent::default(),
1306            1,
1307            MAX_INSTRUCTIONS,
1308            MAX_INSTRUCTIONS,
1309        );
1310        for _ in 0..MAX_INSTRUCTIONS {
1311            transaction_context.push().unwrap();
1312            transaction_context
1313                .configure_top_level_instruction_for_tests(
1314                    0,
1315                    vec![InstructionAccount::new(0, false, false)],
1316                    vec![],
1317                )
1318                .unwrap();
1319            transaction_context.pop().unwrap();
1320        }
1321        assert_eq!(
1322            transaction_context.push(),
1323            Err(InstructionError::MaxInstructionTraceLengthExceeded)
1324        );
1325    }
1326
1327    #[test]
1328    fn test_max_instruction_trace_length_cpi() {
1329        // Hitting the limit with CPIs
1330        const MAX_INSTRUCTIONS: usize = 8;
1331        let mut transaction_context = TransactionContext::new(
1332            vec![(
1333                Pubkey::new_unique(),
1334                AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1335            )],
1336            Rent::default(),
1337            256,
1338            MAX_INSTRUCTIONS,
1339            2,
1340        );
1341
1342        transaction_context
1343            .configure_instruction_at_index(
1344                0,
1345                0,
1346                vec![InstructionAccount::new(0, false, false)],
1347                vec![u16::MAX; 256],
1348                Cow::Owned(Vec::new()),
1349                None,
1350            )
1351            .unwrap();
1352
1353        transaction_context
1354            .configure_instruction_at_index(
1355                1,
1356                0,
1357                vec![InstructionAccount::new(0, false, false)],
1358                vec![u16::MAX; 256],
1359                Cow::Owned(Vec::new()),
1360                None,
1361            )
1362            .unwrap();
1363
1364        for _ in 0..MAX_INSTRUCTIONS {
1365            transaction_context.push().unwrap();
1366            transaction_context
1367                .configure_next_cpi_for_tests(
1368                    0,
1369                    vec![InstructionAccount::new(0, false, false)],
1370                    Vec::new(),
1371                )
1372                .unwrap();
1373        }
1374
1375        assert_eq!(
1376            transaction_context.push(),
1377            Err(InstructionError::MaxInstructionTraceLengthExceeded)
1378        );
1379    }
1380
1381    #[test_case(MockInstruction::NoopSuccess, Ok(()); "NoopSuccess")]
1382    #[test_case(MockInstruction::NoopFail, Err(InstructionError::GenericError); "NoopFail")]
1383    #[test_case(MockInstruction::ModifyOwned, Ok(()); "ModifyOwned")]
1384    #[test_case(MockInstruction::ModifyNotOwned, Err(InstructionError::ExternalAccountDataModified); "ModifyNotOwned")]
1385    #[test_case(MockInstruction::ModifyReadonly, Err(InstructionError::ReadonlyDataModified); "ModifyReadonly")]
1386    #[test_case(MockInstruction::UnbalancedPush, Err(InstructionError::UnbalancedInstruction); "UnbalancedPush")]
1387    #[test_case(MockInstruction::UnbalancedPop, Err(InstructionError::UnbalancedInstruction); "UnbalancedPop")]
1388    fn test_process_instruction_account_modifications(
1389        instruction: MockInstruction,
1390        expected_result: Result<(), InstructionError>,
1391    ) {
1392        let callee_program_id = solana_pubkey::new_rand();
1393        let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1394        let not_owned_account = AccountSharedData::new(84, 1, &solana_pubkey::new_rand());
1395        let readonly_account = AccountSharedData::new(168, 1, &solana_pubkey::new_rand());
1396        let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1397        let mut program_account = AccountSharedData::new(1, 1, &native_loader::id());
1398        program_account.set_executable(true);
1399        let transaction_accounts = vec![
1400            (solana_pubkey::new_rand(), owned_account),
1401            (solana_pubkey::new_rand(), not_owned_account),
1402            (solana_pubkey::new_rand(), readonly_account),
1403            (callee_program_id, program_account),
1404            (solana_pubkey::new_rand(), loader_account),
1405        ];
1406        let metas = vec![
1407            AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1408            AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1409            AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1410        ];
1411        let instruction_accounts = (0..4)
1412            .map(|instruction_account_index| {
1413                InstructionAccount::new(
1414                    instruction_account_index,
1415                    false,
1416                    instruction_account_index < 2,
1417                )
1418            })
1419            .collect::<Vec<_>>();
1420        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1421        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1422        program_cache_for_tx_batch.replenish(
1423            callee_program_id,
1424            Arc::new(ProgramCacheEntry::new_builtin(0, 1, MockBuiltin::register)),
1425        );
1426        invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1427
1428        // Account modification tests
1429        invoke_context
1430            .transaction_context
1431            .configure_top_level_instruction_for_tests(4, instruction_accounts, vec![])
1432            .unwrap();
1433        invoke_context.push().unwrap();
1434        let inner_instruction =
1435            Instruction::new_with_bincode(callee_program_id, &instruction, metas);
1436        let result = invoke_context
1437            .native_invoke_signed(inner_instruction, &[])
1438            .and(invoke_context.pop());
1439        assert_eq!(result, expected_result);
1440    }
1441
1442    #[test_case(Ok(()); "Ok")]
1443    #[test_case(Err(InstructionError::GenericError); "GenericError")]
1444    fn test_process_instruction_compute_unit_consumption(
1445        expected_result: Result<(), InstructionError>,
1446    ) {
1447        let callee_program_id = solana_pubkey::new_rand();
1448        let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1449        let not_owned_account = AccountSharedData::new(84, 1, &solana_pubkey::new_rand());
1450        let readonly_account = AccountSharedData::new(168, 1, &solana_pubkey::new_rand());
1451        let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1452        let mut program_account = AccountSharedData::new(1, 1, &native_loader::id());
1453        program_account.set_executable(true);
1454        let transaction_accounts = vec![
1455            (solana_pubkey::new_rand(), owned_account),
1456            (solana_pubkey::new_rand(), not_owned_account),
1457            (solana_pubkey::new_rand(), readonly_account),
1458            (callee_program_id, program_account),
1459            (solana_pubkey::new_rand(), loader_account),
1460        ];
1461        let metas = vec![
1462            AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1463            AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1464            AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1465        ];
1466        let instruction_accounts = (0..4)
1467            .map(|instruction_account_index| {
1468                InstructionAccount::new(
1469                    instruction_account_index,
1470                    false,
1471                    instruction_account_index < 2,
1472                )
1473            })
1474            .collect::<Vec<_>>();
1475        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1476        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1477        program_cache_for_tx_batch.replenish(
1478            callee_program_id,
1479            Arc::new(ProgramCacheEntry::new_builtin(0, 1, MockBuiltin::register)),
1480        );
1481        invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1482
1483        // Compute unit consumption tests
1484        let compute_units_to_consume = 10;
1485        invoke_context
1486            .transaction_context
1487            .configure_top_level_instruction_for_tests(4, instruction_accounts, vec![])
1488            .unwrap();
1489        invoke_context.push().unwrap();
1490        let inner_instruction = Instruction::new_with_bincode(
1491            callee_program_id,
1492            &MockInstruction::ConsumeComputeUnits {
1493                compute_units_to_consume,
1494                desired_result: expected_result.clone(),
1495            },
1496            metas,
1497        );
1498        invoke_context
1499            .prepare_next_cpi_instruction(inner_instruction, &[])
1500            .unwrap();
1501
1502        let mut compute_units_consumed = 0;
1503        let result = invoke_context
1504            .process_instruction(&mut compute_units_consumed, &mut ExecuteTimings::default());
1505
1506        // Because the instruction had compute cost > 0, then regardless of the execution result,
1507        // the number of compute units consumed should be a non-default which is something greater
1508        // than zero.
1509        assert!(compute_units_consumed > 0);
1510        assert_eq!(
1511            compute_units_consumed,
1512            compute_units_to_consume.saturating_add(MOCK_BUILTIN_COMPUTE_UNIT_COST),
1513        );
1514        assert_eq!(result, expected_result);
1515
1516        invoke_context.pop().unwrap();
1517    }
1518
1519    #[test]
1520    fn test_invoke_context_compute_budget() {
1521        let transaction_accounts = vec![(solana_pubkey::new_rand(), AccountSharedData::default())];
1522        let execution_budget = SVMTransactionExecutionBudget {
1523            compute_unit_limit: u64::from(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT),
1524            ..SVMTransactionExecutionBudget::default()
1525        };
1526
1527        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1528        invoke_context.compute_budget = execution_budget;
1529
1530        invoke_context
1531            .transaction_context
1532            .configure_top_level_instruction_for_tests(0, vec![], vec![])
1533            .unwrap();
1534        invoke_context.push().unwrap();
1535        assert_eq!(*invoke_context.get_compute_budget(), execution_budget);
1536        invoke_context.pop().unwrap();
1537    }
1538
1539    #[test_case(0; "Resize the account to *the same size*, so not consuming any additional size")]
1540    #[test_case(1; "Resize the account larger")]
1541    #[test_case(-1; "Resize the account smaller")]
1542    fn test_process_instruction_accounts_resize_delta(resize_delta: i64) {
1543        let program_key = Pubkey::new_unique();
1544        let user_account_data_len = 123u64;
1545        let user_account =
1546            AccountSharedData::new(100, user_account_data_len as usize, &program_key);
1547        let dummy_account = AccountSharedData::new(10, 0, &program_key);
1548        let mut program_account = AccountSharedData::new(500, 500, &native_loader::id());
1549        program_account.set_executable(true);
1550        let transaction_accounts = vec![
1551            (Pubkey::new_unique(), user_account),
1552            (Pubkey::new_unique(), dummy_account),
1553            (program_key, program_account),
1554        ];
1555        let instruction_accounts = vec![
1556            InstructionAccount::new(0, false, true),
1557            InstructionAccount::new(1, false, false),
1558        ];
1559        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1560        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1561        program_cache_for_tx_batch.replenish(
1562            program_key,
1563            Arc::new(ProgramCacheEntry::new_builtin(0, 0, MockBuiltin::register)),
1564        );
1565        invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1566
1567        let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
1568        let instruction_data = bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1569
1570        invoke_context
1571            .transaction_context
1572            .configure_top_level_instruction_for_tests(2, instruction_accounts, instruction_data)
1573            .unwrap();
1574        let result = invoke_context.process_instruction(&mut 0, &mut ExecuteTimings::default());
1575
1576        assert!(result.is_ok());
1577        assert_eq!(
1578            invoke_context.transaction_context.accounts().resize_delta(),
1579            resize_delta
1580        );
1581    }
1582
1583    #[test]
1584    fn test_prepare_instruction_maximum_accounts() {
1585        const MAX_ACCOUNTS_REFERENCED: usize = u16::MAX as usize;
1586        let mut transaction_accounts: Vec<KeyedAccountSharedData> =
1587            Vec::with_capacity(MAX_ACCOUNTS_PER_TRANSACTION);
1588        let mut account_metas: Vec<AccountMeta> = Vec::with_capacity(MAX_ACCOUNTS_REFERENCED);
1589
1590        // Fee-payer
1591        let fee_payer = Keypair::new();
1592        transaction_accounts.push((
1593            fee_payer.pubkey(),
1594            AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1595        ));
1596        account_metas.push(AccountMeta::new(fee_payer.pubkey(), true));
1597
1598        let program_id = Pubkey::new_unique();
1599        let mut program_account = AccountSharedData::new(1, 1, &Pubkey::new_unique());
1600        program_account.set_executable(true);
1601        transaction_accounts.push((program_id, program_account));
1602        account_metas.push(AccountMeta::new_readonly(program_id, false));
1603
1604        for i in 2..MAX_ACCOUNTS_REFERENCED {
1605            // Let's reference 256 unique accounts, and the rest is repeated.
1606            if i < MAX_ACCOUNTS_PER_TRANSACTION {
1607                let key = Pubkey::new_unique();
1608                transaction_accounts
1609                    .push((key, AccountSharedData::new(1, 1, &Pubkey::new_unique())));
1610                account_metas.push(AccountMeta::new_readonly(key, false));
1611            } else {
1612                let repeated_key = transaction_accounts
1613                    .get(i % MAX_ACCOUNTS_PER_TRANSACTION)
1614                    .unwrap()
1615                    .0;
1616                account_metas.push(AccountMeta::new_readonly(repeated_key, false));
1617            }
1618        }
1619
1620        with_mock_invoke_context!(invoke_context, transaction_context, 2, transaction_accounts);
1621
1622        let instruction_1 = Instruction::new_with_bytes(program_id, &[20], account_metas.clone());
1623
1624        let instruction_2 = Instruction::new_with_bytes(
1625            program_id,
1626            &[20],
1627            account_metas.iter().rev().cloned().collect(),
1628        );
1629
1630        let transaction = Transaction::new_with_payer(
1631            &[instruction_1.clone(), instruction_2.clone()],
1632            Some(&fee_payer.pubkey()),
1633        );
1634
1635        let sanitized =
1636            SanitizedTransaction::try_from_legacy_transaction(transaction, &HashSet::new())
1637                .unwrap();
1638
1639        fn test_case_1(invoke_context: &InvokeContext) {
1640            let instruction_context = invoke_context
1641                .transaction_context
1642                .get_next_instruction_context()
1643                .unwrap();
1644            for index_in_instruction in 0..MAX_ACCOUNTS_REFERENCED as IndexOfAccount {
1645                let index_in_transaction = instruction_context
1646                    .get_index_of_instruction_account_in_transaction(index_in_instruction)
1647                    .unwrap();
1648                let other_ix_index = instruction_context
1649                    .get_index_of_account_in_instruction(index_in_transaction)
1650                    .unwrap();
1651                if (index_in_instruction as usize) < MAX_ACCOUNTS_PER_TRANSACTION {
1652                    assert_eq!(index_in_instruction, index_in_transaction);
1653                    assert_eq!(index_in_instruction, other_ix_index);
1654                } else {
1655                    assert_eq!(
1656                        index_in_instruction as usize % MAX_ACCOUNTS_PER_TRANSACTION,
1657                        index_in_transaction as usize
1658                    );
1659                    assert_eq!(
1660                        index_in_instruction as usize % MAX_ACCOUNTS_PER_TRANSACTION,
1661                        other_ix_index as usize
1662                    );
1663                }
1664            }
1665        }
1666
1667        fn test_case_2(invoke_context: &InvokeContext) {
1668            let instruction_context = invoke_context
1669                .transaction_context
1670                .get_next_instruction_context()
1671                .unwrap();
1672            for index_in_instruction in 0..MAX_ACCOUNTS_REFERENCED as IndexOfAccount {
1673                let index_in_transaction = instruction_context
1674                    .get_index_of_instruction_account_in_transaction(index_in_instruction)
1675                    .unwrap();
1676                let other_ix_index = instruction_context
1677                    .get_index_of_account_in_instruction(index_in_transaction)
1678                    .unwrap();
1679                assert_eq!(
1680                    index_in_transaction,
1681                    (MAX_ACCOUNTS_REFERENCED as u16)
1682                        .saturating_sub(index_in_instruction)
1683                        .saturating_sub(1)
1684                        .overflowing_rem(MAX_ACCOUNTS_PER_TRANSACTION as u16)
1685                        .0
1686                );
1687                if (index_in_instruction as usize) < MAX_ACCOUNTS_PER_TRANSACTION {
1688                    assert_eq!(index_in_instruction, other_ix_index);
1689                } else {
1690                    assert_eq!(
1691                        index_in_instruction as usize % MAX_ACCOUNTS_PER_TRANSACTION,
1692                        other_ix_index as usize
1693                    );
1694                }
1695            }
1696        }
1697
1698        invoke_context
1699            .prepare_top_level_instructions(&sanitized)
1700            .unwrap();
1701
1702        test_case_1(&invoke_context);
1703
1704        invoke_context.transaction_context.push().unwrap();
1705        invoke_context.transaction_context.pop().unwrap();
1706
1707        test_case_2(&invoke_context);
1708
1709        invoke_context.transaction_context.push().unwrap();
1710        invoke_context
1711            .prepare_next_cpi_instruction(instruction_1, &[fee_payer.pubkey()])
1712            .unwrap();
1713        test_case_1(&invoke_context);
1714
1715        invoke_context.transaction_context.push().unwrap();
1716        invoke_context
1717            .prepare_next_cpi_instruction(instruction_2, &[fee_payer.pubkey()])
1718            .unwrap();
1719        test_case_2(&invoke_context);
1720    }
1721
1722    #[test]
1723    fn test_duplicated_accounts() {
1724        let mut transaction_accounts: Vec<KeyedAccountSharedData> =
1725            Vec::with_capacity(MAX_ACCOUNTS_PER_TRANSACTION);
1726        let mut account_metas: Vec<AccountMeta> =
1727            Vec::with_capacity(MAX_ACCOUNTS_PER_INSTRUCTION.saturating_sub(1));
1728
1729        // Fee-payer
1730        let fee_payer = Keypair::new();
1731        transaction_accounts.push((
1732            fee_payer.pubkey(),
1733            AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1734        ));
1735        account_metas.push(AccountMeta::new(fee_payer.pubkey(), true));
1736
1737        let program_id = Pubkey::new_unique();
1738        let mut program_account = AccountSharedData::new(1, 1, &Pubkey::new_unique());
1739        program_account.set_executable(true);
1740        transaction_accounts.push((program_id, program_account));
1741        account_metas.push(AccountMeta::new_readonly(program_id, false));
1742
1743        for i in 2..account_metas.capacity() {
1744            if i % 2 == 0 {
1745                let key = Pubkey::new_unique();
1746                transaction_accounts
1747                    .push((key, AccountSharedData::new(1, 1, &Pubkey::new_unique())));
1748                account_metas.push(AccountMeta::new_readonly(key, false));
1749            } else {
1750                let last_key = transaction_accounts.last().unwrap().0;
1751                account_metas.push(AccountMeta::new_readonly(last_key, false));
1752            }
1753        }
1754
1755        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1756
1757        let instruction = Instruction::new_with_bytes(program_id, &[20], account_metas.clone());
1758
1759        let transaction = Transaction::new_with_payer(&[instruction], Some(&fee_payer.pubkey()));
1760
1761        let sanitized =
1762            SanitizedTransaction::try_from_legacy_transaction(transaction, &HashSet::new())
1763                .unwrap();
1764
1765        invoke_context
1766            .prepare_top_level_instructions(&sanitized)
1767            .unwrap();
1768
1769        {
1770            let instruction_context = invoke_context
1771                .transaction_context
1772                .get_next_instruction_context()
1773                .unwrap();
1774            for index_in_instruction in 2..account_metas.len() as IndexOfAccount {
1775                let is_duplicate = instruction_context
1776                    .is_instruction_account_duplicate(index_in_instruction)
1777                    .unwrap();
1778                if index_in_instruction % 2 == 0 {
1779                    assert!(is_duplicate.is_none());
1780                } else {
1781                    assert_eq!(is_duplicate, Some(index_in_instruction.saturating_sub(1)));
1782                }
1783            }
1784        }
1785
1786        invoke_context.transaction_context.push().unwrap();
1787
1788        let instruction = Instruction::new_with_bytes(
1789            program_id,
1790            &[20],
1791            account_metas.iter().cloned().rev().collect(),
1792        );
1793
1794        invoke_context
1795            .prepare_next_cpi_instruction(instruction, &[fee_payer.pubkey()])
1796            .unwrap();
1797        let instruction_context = invoke_context
1798            .transaction_context
1799            .get_next_instruction_context()
1800            .unwrap();
1801        for index_in_instruction in 2..account_metas.len().saturating_sub(1) as u16 {
1802            let is_duplicate = instruction_context
1803                .is_instruction_account_duplicate(index_in_instruction)
1804                .unwrap();
1805            if index_in_instruction % 2 == 0 {
1806                assert!(is_duplicate.is_none());
1807            } else {
1808                assert_eq!(is_duplicate, Some(index_in_instruction.saturating_sub(1)));
1809            }
1810        }
1811    }
1812
1813    // Used for native_invoke_signed tests below.
1814    const TEST_CALLER_PROGRAM_ID: Pubkey = Pubkey::new_from_array([1u8; 32]);
1815    const TEST_CALLEE_PROGRAM_ID: Pubkey = Pubkey::new_from_array([2u8; 32]);
1816    const TEST_WRONG_PROGRAM_ID: Pubkey = Pubkey::new_from_array([3u8; 32]);
1817    const TEST_MOCK_EXTRA_KEY: Pubkey = Pubkey::new_from_array([4u8; 32]);
1818    const TEST_ACCOUNT_KEY: Pubkey = Pubkey::new_from_array([5u8; 32]);
1819
1820    /// Runs a `native_invoke_signed` call with the standard test setup and returns
1821    /// the result.
1822    ///
1823    /// Same layout for all tests:
1824    ///   0: target account (writable, signer iff `target_is_signer`)
1825    ///   1: caller program (executable)
1826    ///   2: mock extra (satisfies MockBuiltin's 2-account requirement)
1827    ///   3: callee program (executable)
1828    fn run_native_invoke_signed_test(
1829        target_key: Pubkey,
1830        target_is_signer: bool,
1831        inner_instruction: Instruction,
1832        signer_seeds: &[&[&[u8]]],
1833    ) -> Result<(), InstructionError> {
1834        let target_account = AccountSharedData::new(100, 0, &TEST_CALLEE_PROGRAM_ID);
1835        let mock_extra_account = AccountSharedData::new(0, 1, &system_program::id());
1836        let mut caller_program_account = AccountSharedData::new(1, 1, &native_loader::id());
1837        caller_program_account.set_executable(true);
1838        let mut callee_program_account = AccountSharedData::new(1, 1, &native_loader::id());
1839        callee_program_account.set_executable(true);
1840        let transaction_accounts = vec![
1841            (target_key, target_account),
1842            (TEST_CALLER_PROGRAM_ID, caller_program_account),
1843            (TEST_MOCK_EXTRA_KEY, mock_extra_account),
1844            (TEST_CALLEE_PROGRAM_ID, callee_program_account),
1845        ];
1846
1847        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1848        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1849        program_cache_for_tx_batch.replenish(
1850            TEST_CALLEE_PROGRAM_ID,
1851            Arc::new(ProgramCacheEntry::new_builtin(0, 1, MockBuiltin::register)),
1852        );
1853        invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1854
1855        let instruction_accounts = (0..4)
1856            .map(|i| InstructionAccount::new(i, i == 0 && target_is_signer, i < 2))
1857            .collect::<Vec<_>>();
1858        invoke_context
1859            .transaction_context
1860            .configure_top_level_instruction_for_tests(1, instruction_accounts, vec![])
1861            .unwrap();
1862        invoke_context.push().unwrap();
1863
1864        let result = invoke_context.native_invoke_signed(inner_instruction, signer_seeds);
1865        invoke_context.pop().unwrap();
1866        result
1867    }
1868
1869    // Valid PDA seeds grant signer privilege to the derived address.
1870    #[test]
1871    fn test_native_invoke_signed_with_valid_pda_signer() {
1872        let (pda_key, bump_seed) =
1873            Pubkey::find_program_address(&[b"seed"], &TEST_CALLER_PROGRAM_ID);
1874        let instruction = Instruction::new_with_bincode(
1875            TEST_CALLEE_PROGRAM_ID,
1876            &MockInstruction::NoopSuccess,
1877            vec![
1878                AccountMeta::new(pda_key, true),
1879                AccountMeta::new_readonly(TEST_MOCK_EXTRA_KEY, false),
1880            ],
1881        );
1882        let result =
1883            run_native_invoke_signed_test(pda_key, false, instruction, &[&[b"seed", &[bump_seed]]]);
1884        assert!(
1885            result.is_ok(),
1886            "valid PDA signer should succeed: {result:?}"
1887        );
1888    }
1889
1890    // Oversized seeds (>MAX_SEED_LEN) hit `MaxSeedLengthExceeded`
1891    // (discriminant 0) which the broken `as u64` num-traits conversion
1892    // maps to `Custom(0)`.
1893    #[test]
1894    fn test_native_invoke_signed_with_invalid_seeds() {
1895        let instruction = Instruction::new_with_bincode(
1896            TEST_CALLEE_PROGRAM_ID,
1897            &MockInstruction::NoopSuccess,
1898            vec![AccountMeta::new(TEST_ACCOUNT_KEY, true)],
1899        );
1900        let oversized_seed = [0u8; 33];
1901        let result = run_native_invoke_signed_test(
1902            TEST_ACCOUNT_KEY,
1903            false,
1904            instruction,
1905            &[&[&oversized_seed]],
1906        );
1907        assert_eq!(result, Err(InstructionError::Custom(0)));
1908    }
1909
1910    // CPI marks an account as signer but caller provides no seeds —
1911    // signer privilege escalation.
1912    #[test]
1913    fn test_native_invoke_signed_pda_privilege_escalation_without_seeds() {
1914        let (pda_key, _bump_seed) =
1915            Pubkey::find_program_address(&[b"seed"], &TEST_CALLER_PROGRAM_ID);
1916        let instruction = Instruction::new_with_bincode(
1917            TEST_CALLEE_PROGRAM_ID,
1918            &MockInstruction::NoopSuccess,
1919            vec![AccountMeta::new(pda_key, true)],
1920        );
1921        let result = run_native_invoke_signed_test(pda_key, false, instruction, &[]);
1922        assert_eq!(result, Err(InstructionError::PrivilegeEscalation));
1923    }
1924
1925    // Seeds valid for a different program ID don't grant signer privilege
1926    // because native_invoke_signed derives against the caller's own program ID.
1927    #[test]
1928    fn test_native_invoke_signed_uses_caller_program_id_for_pda() {
1929        let (pda_key, bump_seed) = Pubkey::find_program_address(&[b"seed"], &TEST_WRONG_PROGRAM_ID);
1930        let instruction = Instruction::new_with_bincode(
1931            TEST_CALLEE_PROGRAM_ID,
1932            &MockInstruction::NoopSuccess,
1933            vec![AccountMeta::new(pda_key, true)],
1934        );
1935        let result =
1936            run_native_invoke_signed_test(pda_key, false, instruction, &[&[b"seed", &[bump_seed]]]);
1937        assert_eq!(result, Err(InstructionError::PrivilegeEscalation));
1938    }
1939
1940    // Top-level signer privilege carries through CPI without needing seeds.
1941    #[test]
1942    fn test_native_invoke_signed_top_level_signer_needs_no_seeds() {
1943        let (pda_key, _bump_seed) =
1944            Pubkey::find_program_address(&[b"seed"], &TEST_CALLER_PROGRAM_ID);
1945        let instruction = Instruction::new_with_bincode(
1946            TEST_CALLEE_PROGRAM_ID,
1947            &MockInstruction::NoopSuccess,
1948            vec![
1949                AccountMeta::new(pda_key, true),
1950                AccountMeta::new_readonly(TEST_MOCK_EXTRA_KEY, false),
1951            ],
1952        );
1953        let result = run_native_invoke_signed_test(pda_key, true, instruction, &[]);
1954        assert!(
1955            result.is_ok(),
1956            "top-level signer should not need seeds: {result:?}"
1957        );
1958    }
1959
1960    #[test]
1961    fn test_compile_message() {
1962        let program_id = Pubkey::new_from_array([1u8; 32]);
1963        let writable = Pubkey::new_from_array([2u8; 32]);
1964        let loader_key = Pubkey::new_from_array([3u8; 32]);
1965
1966        let instruction = Instruction {
1967            program_id,
1968            accounts: vec![AccountMeta::new(writable, false)],
1969            data: vec![1, 2, 3],
1970        };
1971
1972        let accounts = vec![(
1973            writable,
1974            Account {
1975                lamports: 100,
1976                ..Account::default()
1977            },
1978        )];
1979
1980        let (message, tx_accounts) =
1981            mock_compile_message(&instruction, &accounts, &program_id, &loader_key);
1982
1983        assert_eq!(message.instructions().len(), 1);
1984        assert_eq!(tx_accounts.len(), 2);
1985        assert_eq!(tx_accounts.first().unwrap().0, writable);
1986        assert_eq!(tx_accounts.get(1).unwrap().0, program_id);
1987
1988        // Verify the writable account is NOT promoted to signer.
1989        assert!(!message.is_signer(0));
1990    }
1991}