Skip to main content

rialo_s_program_runtime/
invoke_context.rs

1// Copyright (c) Subzero Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3// This file is either (a) original to Subzero Labs, Inc. or (b) derived from the Anza codebase and modified by Subzero Labs, Inc.
4
5use std::{
6    alloc::Layout,
7    cell::RefCell,
8    collections::HashSet,
9    fmt::{self, Debug},
10    rc::Rc,
11};
12
13use rialo_hash::Hash;
14use rialo_s_account::{create_account_shared_data_for_test, AccountSharedData, StoredAccount};
15use rialo_s_clock::Slot;
16use rialo_s_compute_budget::compute_budget::ComputeBudget;
17use rialo_s_epoch_schedule::EpochSchedule;
18use rialo_s_feature_set::{
19    lift_cpi_caller_restriction, move_precompile_verification_to_svm,
20    remove_accounts_executable_flag_checks, FeatureSet,
21};
22use rialo_s_instruction::{error::InstructionError, AccountMeta};
23use rialo_s_log_collector::{ic_msg, LogCollector};
24use rialo_s_measure::measure::Measure;
25use rialo_s_precompiles::Precompile;
26use rialo_s_pubkey::Pubkey;
27use rialo_s_sdk_ids::{bpf_loader_deprecated, native_loader, sysvar};
28use rialo_s_stable_layout::stable_instruction::StableInstruction;
29use rialo_s_timings::{ExecuteDetailsTimings, ExecuteTimings};
30use rialo_s_transaction_context::{
31    IndexOfAccount, InstructionAccount, TransactionAccount, TransactionContext,
32};
33use rialo_s_type_overrides::sync::{atomic::Ordering, Arc};
34pub use rialo_stake_cache_interface::{StakeCacheData, StakesHandle, ValidatorAccount};
35use solana_sbpf::{
36    ebpf::MM_HEAP_START,
37    error::{EbpfError, ProgramResult},
38    memory_region::MemoryMapping,
39    program::{BuiltinFunction, SBPFVersion},
40    vm::{Config, ContextObject, EbpfVm},
41};
42
43use crate::{
44    loaded_programs::{ProgramCacheEntryType, ProgramCacheForTx, ProgramRuntimeEnvironments},
45    stable_log,
46    sysvar_cache::SysvarCache,
47};
48
49pub type BuiltinFunctionWithContext = BuiltinFunction<InvokeContext<'static, 'static>>;
50
51/// Adapter so we can unify the interfaces of built-in programs and syscalls
52#[macro_export]
53macro_rules! declare_process_instruction {
54    ($process_instruction:ident, $cu_to_consume:expr, |$invoke_context:ident| $inner:tt) => {
55        $crate::solana_sbpf::declare_builtin_function!(
56            $process_instruction,
57            fn rust(
58                invoke_context: &mut $crate::invoke_context::InvokeContext<'_, '_>,
59                _arg0: u64,
60                _arg1: u64,
61                _arg2: u64,
62                _arg3: u64,
63                _arg4: u64,
64                _memory_mapping: &mut $crate::solana_sbpf::memory_region::MemoryMapping<'_>,
65            ) -> std::result::Result<u64, Box<dyn std::error::Error>> {
66                fn process_instruction_inner(
67                    $invoke_context: &mut $crate::invoke_context::InvokeContext<'_, '_>,
68                ) -> std::result::Result<(), $crate::__private::InstructionError>
69                    $inner
70
71                let consumption_result = if $cu_to_consume > 0
72                {
73                    invoke_context.consume_checked($cu_to_consume)
74                } else {
75                    Ok(())
76                };
77                consumption_result
78                    .and_then(|_| {
79                        process_instruction_inner(invoke_context)
80                            .map(|_| 0)
81                            .map_err(|err| Box::new(err) as Box<dyn std::error::Error>)
82                    })
83                    .into()
84            }
85        );
86    };
87}
88
89/// Account insert error.
90pub enum AccountInsertError {
91    /// Too many accounts have been loaded.
92    TooManyAccounts,
93
94    /// Account has already been loaded.
95    AccountAlreadyLoaded,
96}
97
98/// Runtime account loader.
99///
100/// Used by the RISC-V VM to load accounts into VM during runtime of the program.
101#[allow(clippy::result_unit_err)]
102pub trait RuntimeAccountLoader {
103    /// Attempt to load the account info of `pubkey`.
104    ///
105    /// Returns `Ok(Some(AccountSharedData))` on success.
106    /// Returns `Ok(None)` if the account was not found.
107    /// Returns `Err(())` if there was an error with account loading that should abort the program.
108    fn load_account(&self, pubkey: &Pubkey) -> Result<Option<StoredAccount>, ()>;
109}
110
111impl ContextObject for InvokeContext<'_, '_> {
112    fn trace(&mut self, state: [u64; 12]) {
113        self.syscall_context
114            .last_mut()
115            .unwrap()
116            .as_mut()
117            .unwrap()
118            .trace_log
119            .push(state);
120    }
121
122    fn consume(&mut self, amount: u64) {
123        // 1 to 1 instruction to compute unit mapping
124        // ignore overflow, Ebpf will bail if exceeded
125        let mut compute_meter = self.compute_meter.borrow_mut();
126        *compute_meter = compute_meter.saturating_sub(amount);
127    }
128
129    fn get_remaining(&self) -> u64 {
130        *self.compute_meter.borrow()
131    }
132}
133
134#[derive(Clone, PartialEq, Eq, Debug)]
135pub struct AllocErr;
136impl fmt::Display for AllocErr {
137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138        f.write_str("Error: Memory allocation failed")
139    }
140}
141
142pub struct BpfAllocator {
143    len: u64,
144    pos: u64,
145}
146
147impl BpfAllocator {
148    pub fn new(len: u64) -> Self {
149        Self { len, pos: 0 }
150    }
151
152    pub fn alloc(&mut self, layout: Layout) -> Result<u64, AllocErr> {
153        let bytes_to_align = (self.pos as *const u8).align_offset(layout.align()) as u64;
154        if self
155            .pos
156            .saturating_add(bytes_to_align)
157            .saturating_add(layout.size() as u64)
158            <= self.len
159        {
160            self.pos = self.pos.saturating_add(bytes_to_align);
161            let addr = MM_HEAP_START.saturating_add(self.pos);
162            self.pos = self.pos.saturating_add(layout.size() as u64);
163            Ok(addr)
164        } else {
165            Err(AllocErr)
166        }
167    }
168}
169
170pub struct EnvironmentConfig<'a> {
171    pub blockhash: Hash,
172    pub blockhash_kelvins_per_signature: u64,
173    get_epoch_vote_account_stake_callback: &'a dyn Fn(&'a Pubkey) -> u64,
174    pub feature_set: Arc<FeatureSet>,
175    sysvar_cache: &'a SysvarCache,
176    pub random_seed: u64,
177    /// Handle to freeze the stake cache during instruction execution.
178    /// Builtin programs can call `freeze_stakes()` to perform an O(1) swap
179    /// of the pending stake cache data to the frozen history. The handle also
180    /// contains the pending stake cache data for read-only access, including
181    /// the next epoch number.
182    ///
183    /// The handle also contains the `epoch_rewards_exists_fn` callback for
184    /// checking if an EpochRewards account exists for a given epoch.
185    stakes_handle: StakesHandle,
186}
187impl<'a> EnvironmentConfig<'a> {
188    #[allow(clippy::too_many_arguments)]
189    pub fn new(
190        blockhash: Hash,
191        blockhash_kelvins_per_signature: u64,
192        get_epoch_vote_account_stake_callback: &'a dyn Fn(&'a Pubkey) -> u64,
193        feature_set: Arc<FeatureSet>,
194        sysvar_cache: &'a SysvarCache,
195        random_seed: u64,
196        stakes_handle: StakesHandle,
197    ) -> Self {
198        Self {
199            blockhash,
200            blockhash_kelvins_per_signature,
201            get_epoch_vote_account_stake_callback,
202            feature_set,
203            sysvar_cache,
204            random_seed,
205            stakes_handle,
206        }
207    }
208}
209
210pub struct SyscallContext {
211    pub allocator: BpfAllocator,
212    pub accounts_metadata: Vec<SerializedAccountMetadata>,
213    pub trace_log: Vec<[u64; 12]>,
214}
215
216#[derive(Debug, Clone)]
217pub struct SerializedAccountMetadata {
218    pub original_data_len: usize,
219    pub vm_data_addr: u64,
220    pub vm_key_addr: u64,
221    pub vm_kelvins_addr: u64,
222    pub vm_owner_addr: u64,
223}
224
225/// Main pipeline from runtime to program execution.
226pub struct InvokeContext<'a, 'b> {
227    /// Information about the currently executing transaction.
228    pub transaction_context: &'a mut TransactionContext,
229    /// The local program cache for the transaction batch.
230    pub program_cache_for_tx_batch: &'a mut ProgramCacheForTx<'b>,
231    /// Runtime configurations used to provision the invocation environment.
232    pub environment_config: EnvironmentConfig<'a>,
233    /// The compute budget for the current invocation.
234    compute_budget: ComputeBudget,
235    /// Instruction compute meter, for tracking compute units consumed against
236    /// the designated compute budget during program execution.
237    compute_meter: RefCell<u64>,
238    log_collector: Option<Rc<RefCell<LogCollector>>>,
239    /// Latest measurement not yet accumulated in [ExecuteDetailsTimings::execute_us]
240    pub execute_time: Option<Measure>,
241    pub timings: ExecuteDetailsTimings,
242    pub syscall_context: Vec<Option<SyscallContext>>,
243    traces: Vec<Vec<[u64; 12]>>,
244    pub account_loader: Option<Box<dyn RuntimeAccountLoader + 'a>>,
245    /// Map of accounts already loaded in the VM.
246    ///
247    /// This is shared across CPI calls of the program and is used to prevent programs from reloading accounts
248    /// and/or loading accounts other programs have already loaded.
249    loaded_accounts: HashSet<Pubkey>,
250    /// How many writable accounts can be loaded.
251    num_account_loads: usize,
252}
253
254impl<'a, 'b> InvokeContext<'a, 'b> {
255    #[allow(clippy::too_many_arguments)]
256    pub fn new(
257        transaction_context: &'a mut TransactionContext,
258        program_cache_for_tx_batch: &'a mut ProgramCacheForTx<'b>,
259        environment_config: EnvironmentConfig<'a>,
260        log_collector: Option<Rc<RefCell<LogCollector>>>,
261        compute_budget: ComputeBudget,
262    ) -> Self {
263        let loaded_accounts = transaction_context.account_keys().copied().collect();
264
265        Self {
266            transaction_context,
267            program_cache_for_tx_batch,
268            environment_config,
269            log_collector,
270            compute_budget,
271            compute_meter: RefCell::new(compute_budget.compute_unit_limit),
272            execute_time: None,
273            timings: ExecuteDetailsTimings::default(),
274            syscall_context: Vec::new(),
275            traces: Vec::new(),
276            account_loader: None,
277            loaded_accounts,
278            num_account_loads: 0usize,
279        }
280    }
281
282    pub fn new_with_account_loader(
283        transaction_context: &'a mut TransactionContext,
284        program_cache_for_tx_batch: &'a mut ProgramCacheForTx<'b>,
285        environment_config: EnvironmentConfig<'a>,
286        log_collector: Option<Rc<RefCell<LogCollector>>>,
287        compute_budget: ComputeBudget,
288        account_loader: Box<dyn RuntimeAccountLoader + 'a>,
289        num_account_locks: usize,
290        num_writable_accounts: usize,
291    ) -> Self {
292        let loaded_accounts = transaction_context.account_keys().copied().collect();
293
294        Self {
295            transaction_context,
296            program_cache_for_tx_batch,
297            environment_config,
298            log_collector,
299            compute_budget,
300            compute_meter: RefCell::new(compute_budget.compute_unit_limit),
301            execute_time: None,
302            timings: ExecuteDetailsTimings::default(),
303            syscall_context: Vec::new(),
304            traces: Vec::new(),
305            account_loader: Some(account_loader),
306            loaded_accounts,
307            num_account_loads: num_account_locks.saturating_sub(num_writable_accounts),
308        }
309    }
310
311    /// Attempt to loaded account `pubkey` into `InvokeContext`.
312    ///
313    /// Returns `Ok(())` on success
314    /// Returns `Err(AccountInsertError::TooManyAccounts)` if too many accounts have been loaded.
315    /// Returns `Err(AccountInsertError::AccountAlreadyLoaded)` if account has already been loaded.
316    pub fn add_loaded_account(&mut self, pubkey: Pubkey) -> Result<(), AccountInsertError> {
317        match self.num_account_loads.checked_sub(1) {
318            Some(value) => self.num_account_loads = value,
319            None => return Err(AccountInsertError::TooManyAccounts),
320        }
321
322        if self.loaded_accounts.insert(pubkey) {
323            return Ok(());
324        }
325
326        Err(AccountInsertError::AccountAlreadyLoaded)
327    }
328
329    pub fn get_environments_for_slot(
330        &self,
331        effective_slot: Slot,
332    ) -> Result<&ProgramRuntimeEnvironments, InstructionError> {
333        let epoch_schedule = self.environment_config.sysvar_cache.get_epoch_schedule()?;
334        let epoch = epoch_schedule.get_epoch(effective_slot);
335        Ok(self
336            .program_cache_for_tx_batch
337            .get_environments_for_epoch(epoch))
338    }
339
340    /// Push a stack frame onto the invocation stack
341    pub fn push(&mut self) -> Result<(), InstructionError> {
342        let instruction_context = self
343            .transaction_context
344            .get_instruction_context_at_index_in_trace(
345                self.transaction_context.get_instruction_trace_length(),
346            )?;
347        let program_id = instruction_context
348            .get_last_program_key(self.transaction_context)
349            .map_err(|_| InstructionError::UnsupportedProgramId)?;
350        if self
351            .transaction_context
352            .get_instruction_context_stack_height()
353            != 0
354        {
355            let contains = (0..self
356                .transaction_context
357                .get_instruction_context_stack_height())
358                .any(|level| {
359                    self.transaction_context
360                        .get_instruction_context_at_nesting_level(level)
361                        .and_then(|instruction_context| {
362                            instruction_context
363                                .try_borrow_last_program_account(self.transaction_context)
364                        })
365                        .map(|program_account| program_account.get_key() == program_id)
366                        .unwrap_or(false)
367                });
368            let is_last = self
369                .transaction_context
370                .get_current_instruction_context()
371                .and_then(|instruction_context| {
372                    instruction_context.try_borrow_last_program_account(self.transaction_context)
373                })
374                .map(|program_account| program_account.get_key() == program_id)
375                .unwrap_or(false);
376            if contains && !is_last {
377                // Reentrancy not allowed unless caller is calling itself
378                return Err(InstructionError::ReentrancyNotAllowed);
379            }
380        }
381
382        self.syscall_context.push(None);
383        self.transaction_context.push()
384    }
385
386    /// Pop a stack frame from the invocation stack
387    fn pop(&mut self) -> Result<(), InstructionError> {
388        if let Some(Some(syscall_context)) = self.syscall_context.pop() {
389            self.traces.push(syscall_context.trace_log);
390        }
391        self.transaction_context.pop()
392    }
393
394    /// Current height of the invocation stack, top level instructions are height
395    /// `rialo_s_instruction::TRANSACTION_LEVEL_STACK_HEIGHT`
396    pub fn get_stack_height(&self) -> usize {
397        self.transaction_context
398            .get_instruction_context_stack_height()
399    }
400
401    /// Entrypoint for a cross-program invocation from a builtin program
402    pub fn native_invoke(
403        &mut self,
404        instruction: StableInstruction,
405        signers: &[Pubkey],
406    ) -> Result<(), InstructionError> {
407        let (instruction_accounts, program_indices) =
408            self.prepare_instruction(&instruction, signers)?;
409        let mut compute_units_consumed = 0;
410        self.process_instruction(
411            &instruction.data,
412            &instruction_accounts,
413            &program_indices,
414            &mut compute_units_consumed,
415            &mut ExecuteTimings::default(),
416        )?;
417        Ok(())
418    }
419
420    /// Helper to prepare for process_instruction()
421    #[allow(clippy::type_complexity)]
422    pub fn prepare_instruction(
423        &mut self,
424        instruction: &StableInstruction,
425        signers: &[Pubkey],
426    ) -> Result<(Vec<InstructionAccount>, Vec<IndexOfAccount>), InstructionError> {
427        self.prepare_instruction_inner(instruction.program_id, &instruction.accounts, signers)
428    }
429
430    /// Helper to prepare for process_instruction()
431    pub fn prepare_cpi_instruction(
432        &mut self,
433        program_id: Pubkey,
434        account_metas: &[AccountMeta],
435        signers: &[Pubkey],
436    ) -> Result<(Vec<InstructionAccount>, Vec<IndexOfAccount>), InstructionError> {
437        self.prepare_instruction_inner(program_id, account_metas, signers)
438    }
439
440    pub fn prepare_instruction_inner(
441        &mut self,
442        callee_program_id: Pubkey,
443        account_metas: &[AccountMeta],
444        signers: &[Pubkey],
445    ) -> Result<(Vec<InstructionAccount>, Vec<IndexOfAccount>), InstructionError> {
446        // Finds the index of each account in the instruction by its pubkey.
447        // Then normalizes / unifies the privileges of duplicate accounts.
448        // Note: This is an O(n^2) algorithm,
449        // but performed on a very small slice and requires no heap allocations.
450        let instruction_context = self.transaction_context.get_current_instruction_context()?;
451        let mut deduplicated_instruction_accounts: Vec<InstructionAccount> = Vec::new();
452        let mut duplicate_indicies = Vec::with_capacity(account_metas.len());
453        for (instruction_account_index, account_meta) in account_metas.iter().enumerate() {
454            let index_in_transaction = self
455                .transaction_context
456                .find_index_of_account(&account_meta.pubkey)
457                .ok_or_else(|| {
458                    ic_msg!(
459                        self,
460                        "Instruction references an unknown account {}",
461                        account_meta.pubkey,
462                    );
463                    InstructionError::MissingAccount
464                })?;
465            if let Some(duplicate_index) =
466                deduplicated_instruction_accounts
467                    .iter()
468                    .position(|instruction_account| {
469                        instruction_account.index_in_transaction == index_in_transaction
470                    })
471            {
472                duplicate_indicies.push(duplicate_index);
473                let instruction_account = deduplicated_instruction_accounts
474                    .get_mut(duplicate_index)
475                    .ok_or(InstructionError::NotEnoughAccountKeys)?;
476                instruction_account.is_signer |= account_meta.is_signer;
477                instruction_account.is_writable |= account_meta.is_writable;
478            } else {
479                let index_in_caller = instruction_context
480                    .find_index_of_instruction_account(
481                        self.transaction_context,
482                        &account_meta.pubkey,
483                    )
484                    .ok_or_else(|| {
485                        ic_msg!(
486                            self,
487                            "Instruction references an unknown account {}",
488                            account_meta.pubkey,
489                        );
490                        InstructionError::MissingAccount
491                    })?;
492                duplicate_indicies.push(deduplicated_instruction_accounts.len());
493                deduplicated_instruction_accounts.push(InstructionAccount {
494                    index_in_transaction,
495                    index_in_caller,
496                    index_in_callee: instruction_account_index as IndexOfAccount,
497                    is_signer: account_meta.is_signer,
498                    is_writable: account_meta.is_writable,
499                });
500            }
501        }
502        for instruction_account in deduplicated_instruction_accounts.iter() {
503            let borrowed_account = instruction_context.try_borrow_instruction_account(
504                self.transaction_context,
505                instruction_account.index_in_caller,
506            )?;
507
508            // Readonly in caller cannot become writable in callee
509            if instruction_account.is_writable && !borrowed_account.is_writable() {
510                ic_msg!(
511                    self,
512                    "{}'s writable privilege escalated",
513                    borrowed_account.get_key(),
514                );
515                return Err(InstructionError::PrivilegeEscalation);
516            }
517
518            // To be signed in the callee,
519            // it must be either signed in the caller or by the program
520            if instruction_account.is_signer
521                && !(borrowed_account.is_signer() || signers.contains(borrowed_account.get_key()))
522            {
523                ic_msg!(
524                    self,
525                    "{}'s signer privilege escalated",
526                    borrowed_account.get_key()
527                );
528                return Err(InstructionError::PrivilegeEscalation);
529            }
530        }
531        let instruction_accounts = duplicate_indicies
532            .into_iter()
533            .map(|duplicate_index| {
534                deduplicated_instruction_accounts
535                    .get(duplicate_index)
536                    .cloned()
537                    .ok_or(InstructionError::NotEnoughAccountKeys)
538            })
539            .collect::<Result<Vec<InstructionAccount>, InstructionError>>()?;
540
541        // Find and validate executables / program accounts
542        let program_account_index = if self
543            .get_feature_set()
544            .is_active(&lift_cpi_caller_restriction::id())
545        {
546            match (
547                self.transaction_context
548                    .find_index_of_program_account(&callee_program_id),
549                &self.account_loader,
550            ) {
551                (Some(index), _) => index,
552                (None, Some(account_loader)) => {
553                    match account_loader.load_account(&callee_program_id) {
554                        Ok(Some(account)) => {
555                            self.transaction_context
556                                .add_account((callee_program_id, account.clone()));
557
558                            // program must not exist in `loaded_accounts` since it was not found in transaction context
559                            assert!(self.loaded_accounts.insert(callee_program_id));
560
561                            // program must exist since it was just added into transaction context
562                            self.transaction_context
563                                .find_index_of_program_account(&callee_program_id)
564                                .expect("program to exist")
565                        }
566                        Ok(None) => return Err(InstructionError::MissingAccount),
567                        Err(()) => return Err(InstructionError::GenericError),
568                    }
569                }
570                (None, None) => {
571                    ic_msg!(self, "Unknown program {}", callee_program_id);
572                    return Err(InstructionError::MissingAccount);
573                }
574            }
575        } else {
576            let program_account_index = instruction_context
577                .find_index_of_instruction_account(self.transaction_context, &callee_program_id)
578                .ok_or_else(|| {
579                    ic_msg!(self, "Unknown program {}", callee_program_id);
580                    InstructionError::MissingAccount
581                })?;
582            let borrowed_program_account = instruction_context
583                .try_borrow_instruction_account(self.transaction_context, program_account_index)?;
584            #[allow(deprecated)]
585            if !self
586                .get_feature_set()
587                .is_active(&remove_accounts_executable_flag_checks::id())
588                && !borrowed_program_account.is_executable()
589            {
590                ic_msg!(self, "Account {} is not executable", callee_program_id);
591                return Err(InstructionError::AccountNotExecutable);
592            }
593            borrowed_program_account.get_index_in_transaction()
594        };
595
596        Ok((instruction_accounts, vec![program_account_index]))
597    }
598
599    /// Processes an instruction and returns how many compute units were used
600    pub fn process_instruction(
601        &mut self,
602        instruction_data: &[u8],
603        instruction_accounts: &[InstructionAccount],
604        program_indices: &[IndexOfAccount],
605        compute_units_consumed: &mut u64,
606        timings: &mut ExecuteTimings,
607    ) -> Result<(), InstructionError> {
608        *compute_units_consumed = 0;
609        self.transaction_context
610            .get_next_instruction_context()?
611            .configure(program_indices, instruction_accounts, instruction_data);
612        self.push()?;
613        self.process_executable_chain(compute_units_consumed, timings)
614            // MUST pop if and only if `push` succeeded, independent of `result`.
615            // Thus, the `.and()` instead of an `.and_then()`.
616            .and(self.pop())
617    }
618
619    /// Processes a precompile instruction
620    pub fn process_precompile<'ix_data>(
621        &mut self,
622        precompile: &Precompile,
623        instruction_data: &[u8],
624        instruction_accounts: &[InstructionAccount],
625        program_indices: &[IndexOfAccount],
626        message_instruction_datas_iter: impl Iterator<Item = &'ix_data [u8]>,
627    ) -> Result<(), InstructionError> {
628        self.transaction_context
629            .get_next_instruction_context()?
630            .configure(program_indices, instruction_accounts, instruction_data);
631        self.push()?;
632
633        let feature_set = self.get_feature_set();
634        let move_precompile_verification_to_svm =
635            feature_set.is_active(&move_precompile_verification_to_svm::id());
636        if move_precompile_verification_to_svm {
637            let instruction_datas: Vec<_> = message_instruction_datas_iter.collect();
638            precompile
639                .verify(instruction_data, &instruction_datas, feature_set)
640                .map_err(InstructionError::from)
641                .and(self.pop())
642        } else {
643            self.pop()
644        }
645    }
646
647    /// Calls the instruction's program entrypoint method
648    fn process_executable_chain(
649        &mut self,
650        compute_units_consumed: &mut u64,
651        timings: &mut ExecuteTimings,
652    ) -> Result<(), InstructionError> {
653        let instruction_context = self.transaction_context.get_current_instruction_context()?;
654        let process_executable_chain_time = Measure::start("process_executable_chain_time");
655
656        let builtin_id = {
657            debug_assert!(instruction_context.get_number_of_program_accounts() <= 1);
658            let borrowed_root_account = instruction_context
659                .try_borrow_program_account(self.transaction_context, 0)
660                .map_err(|_| InstructionError::UnsupportedProgramId)?;
661            let owner_id = borrowed_root_account.get_owner();
662            if native_loader::check_id(owner_id) {
663                *borrowed_root_account.get_key()
664            } else {
665                *owner_id
666            }
667        };
668
669        // The Murmur3 hash value (used by RBPF) of the string "entrypoint"
670        const ENTRYPOINT_KEY: u32 = 0x71E3CF81;
671        let entry = self
672            .program_cache_for_tx_batch
673            .find(&builtin_id)
674            .ok_or(InstructionError::UnsupportedProgramId)?;
675        let function = match &entry.program {
676            ProgramCacheEntryType::Builtin(program) => program
677                .get_function_registry()
678                .lookup_by_key(ENTRYPOINT_KEY)
679                .map(|(_name, function)| function),
680            _ => None,
681        }
682        .ok_or(InstructionError::UnsupportedProgramId)?;
683        entry.ix_usage_counter.fetch_add(1, Ordering::Relaxed);
684
685        let program_id = *instruction_context.get_last_program_key(self.transaction_context)?;
686        self.transaction_context
687            .set_return_data(program_id, Vec::new())?;
688        let logger = self.get_log_collector();
689        stable_log::program_invoke(&logger, &program_id, self.get_stack_height());
690        let pre_remaining_units = self.get_remaining();
691        // In program-runtime v2 we will create this VM instance only once per transaction.
692        // `program_runtime_environment_v2.get_config()` will be used instead of `mock_config`.
693        // For now, only built-ins are invoked from here, so the VM and its Config are irrelevant.
694        let mock_config = Config::default();
695        let empty_memory_mapping =
696            MemoryMapping::new(Vec::new(), &mock_config, SBPFVersion::V0).unwrap();
697        let mut vm = EbpfVm::new(
698            self.program_cache_for_tx_batch
699                .environments
700                .program_runtime_v2
701                .clone(),
702            SBPFVersion::V0,
703            // Removes lifetime tracking
704            unsafe {
705                std::mem::transmute::<&mut InvokeContext<'_, '_>, &mut InvokeContext<'_, '_>>(self)
706            },
707            empty_memory_mapping,
708            0,
709        );
710        vm.invoke_function(function);
711        let result = match vm.program_result {
712            ProgramResult::Ok(_) => {
713                stable_log::program_success(&logger, &program_id);
714                Ok(())
715            }
716            ProgramResult::Err(ref err) => {
717                if let EbpfError::SyscallError(syscall_error) = err {
718                    if let Some(instruction_err) = syscall_error.downcast_ref::<InstructionError>()
719                    {
720                        stable_log::program_failure(&logger, &program_id, instruction_err);
721                        Err(instruction_err.clone())
722                    } else {
723                        stable_log::program_failure(&logger, &program_id, syscall_error);
724                        Err(InstructionError::ProgramFailedToComplete)
725                    }
726                } else {
727                    stable_log::program_failure(&logger, &program_id, err);
728                    Err(InstructionError::ProgramFailedToComplete)
729                }
730            }
731        };
732        let post_remaining_units = self.get_remaining();
733        *compute_units_consumed = pre_remaining_units.saturating_sub(post_remaining_units);
734
735        if builtin_id == program_id && result.is_ok() && *compute_units_consumed == 0 {
736            return Err(InstructionError::BuiltinProgramsMustConsumeComputeUnits);
737        }
738
739        timings
740            .execute_accessories
741            .process_instructions
742            .process_executable_chain_us += process_executable_chain_time.end_as_us();
743        result
744    }
745
746    /// Get this invocation's LogCollector
747    pub fn get_log_collector(&self) -> Option<Rc<RefCell<LogCollector>>> {
748        self.log_collector.clone()
749    }
750
751    /// Consume compute units
752    pub fn consume_checked(&self, amount: u64) -> Result<(), Box<dyn std::error::Error>> {
753        let mut compute_meter = self.compute_meter.borrow_mut();
754        let exceeded = *compute_meter < amount;
755        *compute_meter = compute_meter.saturating_sub(amount);
756        if exceeded {
757            return Err(Box::new(InstructionError::ComputationalBudgetExceeded));
758        }
759        Ok(())
760    }
761
762    /// Set compute units
763    ///
764    /// Only use for tests and benchmarks
765    pub fn mock_set_remaining(&self, remaining: u64) {
766        *self.compute_meter.borrow_mut() = remaining;
767    }
768
769    /// Get this invocation's compute budget
770    pub fn get_compute_budget(&self) -> &ComputeBudget {
771        &self.compute_budget
772    }
773
774    /// Get the current feature set.
775    pub fn get_feature_set(&self) -> &FeatureSet {
776        &self.environment_config.feature_set
777    }
778
779    /// Set feature set.
780    ///
781    /// Only use for tests and benchmarks.
782    pub fn mock_set_feature_set(&mut self, feature_set: Arc<FeatureSet>) {
783        self.environment_config.feature_set = feature_set;
784    }
785
786    /// Get cached sysvars
787    pub fn get_sysvar_cache(&self) -> &SysvarCache {
788        self.environment_config.sysvar_cache
789    }
790
791    /// Get cached stake for the epoch vote account.
792    pub fn get_epoch_vote_account_stake(&self, pubkey: &'a Pubkey) -> u64 {
793        (self
794            .environment_config
795            .get_epoch_vote_account_stake_callback)(pubkey)
796    }
797
798    /// Get the CURRENT epoch (the epoch that is actively running).
799    ///
800    /// This is the epoch to use when checking stake activation/deactivation status,
801    /// unbonding periods, lockup periods, etc.
802    ///
803    /// # Implementation Note
804    /// The stake cache stores the NEXT epoch (the epoch being prepared for when
805    /// stake changes become effective). We subtract 1 to get the current epoch.
806    /// `saturating_sub` handles the edge case of epoch 0.
807    pub fn get_current_epoch(&self) -> u64 {
808        self.environment_config
809            .stakes_handle
810            .pending_epoch()
811            .saturating_sub(1)
812    }
813
814    /// Get the NEXT epoch (the epoch being prepared in the stake cache).
815    ///
816    /// This is the epoch that accumulated stake changes will become effective in
817    /// AFTER the next FreezeStakes call. Most instruction logic should use
818    /// `get_current_epoch()` instead; this method is primarily for internal use
819    /// and the FreezeStakes instruction.
820    pub fn get_next_epoch(&self) -> u64 {
821        self.environment_config.stakes_handle.pending_epoch()
822    }
823
824    /// Get the timestamp of the last FreezeStakes call (last epoch boundary).
825    ///
826    /// This is the timestamp from `history.back()` which represents when the most
827    /// recent epoch boundary occurred. Use this for **state transition checks**:
828    /// - A stake is "activating" while `activation_requested >= last_freeze_timestamp`
829    /// - A stake becomes "activated" when `activation_requested < last_freeze_timestamp`
830    /// - A stake is "deactivating" while `deactivation_requested >= last_freeze_timestamp`
831    /// - A stake becomes "deactivated" when `deactivation_requested < last_freeze_timestamp`
832    ///
833    /// Returns 0 if no history exists (should not happen in normal operation since
834    /// `StakingState::new_with_initial_history()` guarantees at least one entry).
835    ///
836    /// **Note:** For duration enforcement (lockup, unbonding), use the current timestamp
837    /// from Clock sysvar instead, to provide real-time guarantees.
838    pub fn get_last_freeze_timestamp(&self) -> u64 {
839        self.environment_config
840            .stakes_handle
841            .last_frozen_timestamp()
842            .unwrap_or(0)
843    }
844
845    /// Set the last freeze timestamp for testing.
846    ///
847    /// This is the primary mock function for timestamp-based tests. It sets up the
848    /// stake cache so that `get_last_freeze_timestamp()` returns the specified timestamp.
849    ///
850    /// The function:
851    /// 1. Sets the pending epoch to 1 (to avoid underflow when computing current epoch)
852    /// 2. Pushes a `StakeCacheData` entry to history with epoch=0 and the specified timestamp
853    ///
854    /// Only use for tests and benchmarks.
855    pub fn mock_set_last_freeze_timestamp(&mut self, timestamp: u64) {
856        // Set pending epoch to 1 to avoid underflow when computing current epoch (pending - 1)
857        self.environment_config.stakes_handle.set_pending_epoch(1);
858
859        // Push a history entry with the specified timestamp
860        let history_entry = StakeCacheData {
861            epoch: 0,
862            timestamp,
863            ..Default::default()
864        };
865        self.environment_config
866            .stakes_handle
867            .push_frozen(history_entry);
868    }
869
870    /// Insert a stake account into the pending cache for testing.
871    ///
872    /// This allows unit tests to set up stake accounts that reference validators,
873    /// which is needed to test the validator withdrawal blocking logic.
874    ///
875    /// Only use for tests and benchmarks.
876    pub fn mock_insert_stake_account(
877        &mut self,
878        pubkey: rialo_s_pubkey::Pubkey,
879        account: rialo_stake_cache_interface::StakeAccount,
880    ) {
881        self.environment_config
882            .stakes_handle
883            .insert_stake_account(pubkey, account);
884    }
885
886    /// Freeze the pending stake cache data.
887    ///
888    /// This performs an O(1) swap of the pending stake cache data using `std::mem::take()`
889    /// and pushes it to the back of the frozen queue. This is typically called by the
890    /// ValidatorRegistry program's FreezeStakes instruction to capture the validator set
891    /// at a specific point.
892    ///
893    /// This method also sets the `epoch_stakes_frozen` flag to signal the Bank to
894    /// propagate `new_commission_rate` → `commission_rate`, etc.
895    ///
896    /// To access the frozen validator data after calling this method, use
897    /// `get_all_validator_accounts_from_last_frozen()`.
898    pub fn freeze_stakes(&self) {
899        self.environment_config.stakes_handle.freeze_stakes();
900    }
901
902    /// Signal that FreezeStakes was requested without performing the swap.
903    ///
904    /// The actual pending → frozen swap is deferred to `finalize_impl()` to ensure
905    /// pending modifications are persisted first. This only sets the `epoch_stakes_frozen`
906    /// flag, which is read by `apply_pending_validator_changes_if_needed()` and consumed
907    /// by `finalize_impl()`.
908    pub fn signal_freeze_stakes(&self) {
909        self.environment_config
910            .stakes_handle
911            .set_epoch_stakes_frozen();
912    }
913
914    /// Get all validator accounts from the last frozen epoch (current epoch state).
915    ///
916    /// Returns a vector of `(pubkey, ValidatorAccount)` pairs for all validators,
917    /// sorted by pubkey. This performs a layered lookup: frozen (newest to oldest) → baseline,
918    /// skipping pending (next epoch changes).
919    ///
920    /// This method allows builtin programs (like TokenomicsGovernance) to access
921    /// the frozen validator data directly without needing CPI return data.
922    pub fn get_all_validator_accounts_from_last_frozen(&self) -> Vec<(Pubkey, ValidatorAccount)> {
923        self.environment_config
924            .stakes_handle
925            .get_all_validator_accounts_from_last_frozen()
926    }
927
928    /// Get the length of the frozen stake history.
929    ///
930    /// Used by DistributeRewards to check if there are enough frozen snapshots to merge.
931    /// We require at least 2 entries to allow popping (to preserve the current epoch snapshot).
932    pub fn frozen_stake_history_len(&self) -> usize {
933        self.environment_config.stakes_handle.frozen_len()
934    }
935
936    /// Request epoch rewards initialization.
937    ///
938    /// Called by DistributeRewards instruction to signal the Bank to create an EpochRewards
939    /// account for tracking partitioned reward distribution. The Bank will detect this signal
940    /// after transaction execution and create the account outside of instruction processing.
941    ///
942    /// # Arguments
943    /// * `epoch` - The epoch number for which rewards are being distributed
944    /// * `total_rewards` - The total amount of rewards to be distributed this epoch
945    pub fn request_epoch_rewards_init(&self, epoch: u64, total_rewards: u64) {
946        self.environment_config
947            .stakes_handle
948            .request_epoch_rewards_init(epoch, total_rewards);
949    }
950
951    /// Get the epoch of the oldest frozen snapshot (front of the queue).
952    ///
953    /// This is the epoch that will be popped by `pop_front_and_merge_to_baseline()`.
954    /// Used by DistributeRewards to know which epoch rewards are being distributed.
955    ///
956    /// Returns `None` if no frozen snapshots exist.
957    pub fn front_frozen_epoch(&self) -> Option<u64> {
958        self.environment_config.stakes_handle.front_frozen_epoch()
959    }
960
961    /// Check if any stake account references the given validator whose unbonding
962    /// period is NOT yet complete.
963    ///
964    /// This performs an O(n) search over all stake accounts using a layered lookup
965    /// (pending → frozen → baseline) to determine if the validator is referenced
966    /// by an active or still-unbonding stake.
967    ///
968    /// Used by ValidatorRegistry's Withdraw handler to prevent withdrawal below
969    /// rent-exempt minimum while stake accounts still reference the validator.
970    ///
971    /// A stake is considered to "reference" the validator if:
972    /// - It is delegated to this validator, AND
973    /// - Either active (no deactivation) OR still unbonding (unbonding not complete)
974    ///
975    /// Stake accounts whose unbonding is complete are NOT counted, since they can
976    /// be fully withdrawn or reactivated to another validator.
977    ///
978    /// # Arguments
979    /// * `validator` - The validator pubkey to check
980    /// * `validator_info` - The validator's info (used to compute unbonding end via `end_of_unbonding`)
981    /// * `last_freeze_timestamp` - Timestamp from last FreezeStakes (epoch boundary)
982    /// * `current_timestamp` - Current block timestamp from Clock sysvar
983    pub fn is_validator_referenced(
984        &self,
985        validator: &Pubkey,
986        validator_info: &rialo_stake_cache_interface::ValidatorInfo,
987        last_freeze_timestamp: u64,
988        current_timestamp: u64,
989    ) -> bool {
990        self.environment_config
991            .stakes_handle
992            .is_validator_referenced(
993                validator,
994                validator_info,
995                last_freeze_timestamp,
996                current_timestamp,
997            )
998    }
999
1000    /// Check if any stake account delegated to the given validator is still within
1001    /// its lockup period.
1002    ///
1003    /// Delegates to `StakesHandle::has_locked_stakers()` which performs an O(n) scan
1004    /// over all stake accounts. A staker is "locked" if their
1005    /// `activation_requested + lockup_period > current_timestamp`.
1006    ///
1007    /// # Arguments
1008    /// * `validator` - The validator pubkey to check
1009    /// * `lockup_period` - The validator's lockup period in milliseconds
1010    /// * `current_timestamp` - Current block timestamp from Clock sysvar (in milliseconds)
1011    ///
1012    /// # Returns
1013    /// `true` if at least one staker is still within their lockup period.
1014    pub fn has_locked_stakers(
1015        &self,
1016        validator: &Pubkey,
1017        lockup_period: u64,
1018        current_timestamp: u64,
1019    ) -> bool {
1020        self.environment_config.stakes_handle.has_locked_stakers(
1021            validator,
1022            lockup_period,
1023            current_timestamp,
1024        )
1025    }
1026
1027    /// Check if a validator is referenced by any stake accounts (excluding the self-bond).
1028    ///
1029    /// This variant excludes the self-bond PDA from the check to prevent circular logic
1030    /// where the self-bond cannot be deactivated because its existence always makes
1031    /// is_validator_referenced() return true.
1032    pub fn is_validator_referenced_excluding_self_bond(
1033        &self,
1034        validator_pubkey: &Pubkey,
1035        validator_info: &rialo_stake_cache_interface::ValidatorInfo,
1036        last_freeze_timestamp: u64,
1037        current_timestamp: u64,
1038    ) -> bool {
1039        self.environment_config
1040            .stakes_handle
1041            .is_validator_referenced_excluding_self_bond(
1042                validator_pubkey,
1043                validator_info,
1044                last_freeze_timestamp,
1045                current_timestamp,
1046            )
1047    }
1048
1049    /// Look up a stake account by pubkey from the pending layer (next epoch state).
1050    ///
1051    /// Searches: pending → frozen (newest to oldest) → baseline.
1052    /// Returns `Some(StakeAccount)` if found, `None` if not found or tombstoned.
1053    pub fn get_stake_account_from_pending(
1054        &self,
1055        pubkey: &Pubkey,
1056    ) -> Option<rialo_stake_cache_interface::StakeAccount> {
1057        self.environment_config
1058            .stakes_handle
1059            .get_stake_account_from_pending(pubkey)
1060    }
1061
1062    /// Check if an epoch rewards initialization request is pending for this block.
1063    ///
1064    /// Used by DistributeRewards to prevent multiple reward distributions in the same block.
1065    /// If this returns true, the DistributeRewards instruction should fail with InvalidArgument.
1066    pub fn is_epoch_rewards_init_pending(&self) -> bool {
1067        self.environment_config
1068            .stakes_handle
1069            .is_epoch_rewards_init_pending()
1070    }
1071
1072    /// Get completed frozen epochs (excludes the last/current epoch).
1073    ///
1074    /// Returns epochs in order from oldest to newest. These are the epochs that have
1075    /// completed and are eligible for reward distribution. We exclude the last frozen
1076    /// epoch because it represents the current epoch that is still accumulating rewards.
1077    pub fn completed_frozen_epochs(&self) -> Vec<u64> {
1078        self.environment_config
1079            .stakes_handle
1080            .completed_frozen_epochs()
1081    }
1082
1083    /// Check if an EpochRewards account exists for the given epoch.
1084    ///
1085    /// Delegates to the StakesHandle which contains the callback provided by Bank
1086    /// to query the StateStore. This allows DistributeRewards to find the first
1087    /// completed frozen epoch that doesn't yet have an EpochRewards account.
1088    pub fn epoch_rewards_exists(&self, epoch: u64) -> bool {
1089        self.environment_config
1090            .stakes_handle
1091            .epoch_rewards_exists(epoch)
1092    }
1093
1094    // Should alignment be enforced during user pointer translation
1095    pub fn get_check_aligned(&self) -> bool {
1096        self.transaction_context
1097            .get_current_instruction_context()
1098            .and_then(|instruction_context| {
1099                let program_account =
1100                    instruction_context.try_borrow_last_program_account(self.transaction_context);
1101                debug_assert!(program_account.is_ok());
1102                program_account
1103            })
1104            .map(|program_account| *program_account.get_owner() != bpf_loader_deprecated::id())
1105            .unwrap_or(true)
1106    }
1107
1108    // Set this instruction syscall context
1109    pub fn set_syscall_context(
1110        &mut self,
1111        syscall_context: SyscallContext,
1112    ) -> Result<(), InstructionError> {
1113        *self
1114            .syscall_context
1115            .last_mut()
1116            .ok_or(InstructionError::CallDepth)? = Some(syscall_context);
1117        Ok(())
1118    }
1119
1120    // Get this instruction's SyscallContext
1121    pub fn get_syscall_context(&self) -> Result<&SyscallContext, InstructionError> {
1122        self.syscall_context
1123            .last()
1124            .and_then(std::option::Option::as_ref)
1125            .ok_or(InstructionError::CallDepth)
1126    }
1127
1128    // Get this instruction's SyscallContext
1129    pub fn get_syscall_context_mut(&mut self) -> Result<&mut SyscallContext, InstructionError> {
1130        self.syscall_context
1131            .last_mut()
1132            .and_then(|syscall_context| syscall_context.as_mut())
1133            .ok_or(InstructionError::CallDepth)
1134    }
1135
1136    /// Return a references to traces
1137    pub fn get_traces(&self) -> &Vec<Vec<[u64; 12]>> {
1138        &self.traces
1139    }
1140}
1141
1142#[macro_export]
1143macro_rules! with_mock_invoke_context {
1144    (
1145        $invoke_context:ident,
1146        $transaction_context:ident,
1147        $entry:expr,
1148        $transaction_accounts:expr $(,)?
1149    ) => {
1150        use rialo_s_compute_budget::compute_budget::ComputeBudget;
1151        use rialo_s_feature_set::FeatureSet;
1152        use rialo_s_log_collector::LogCollector;
1153        use rialo_s_type_overrides::sync::Arc;
1154        use $crate::{
1155            __private::{Hash, ReadableAccount, Rent, TransactionContext},
1156            invoke_context::{EnvironmentConfig, InvokeContext},
1157            loaded_programs::{ProgramCacheEntry, ProgramCacheForTx, ProgramCacheForTxBatch},
1158            sysvar_cache::SysvarCache,
1159        };
1160        let compute_budget = ComputeBudget::default();
1161        let mut $transaction_context = TransactionContext::new(
1162            $transaction_accounts,
1163            Rent::default(),
1164            compute_budget.max_instruction_stack_depth,
1165            compute_budget.max_instruction_trace_length,
1166        );
1167        let mut sysvar_cache = SysvarCache::default();
1168        sysvar_cache.fill_missing_entries(|pubkey, callback| {
1169            for index in 0..$transaction_context.get_number_of_accounts() {
1170                if $transaction_context
1171                    .get_key_of_account_at_index(index)
1172                    .unwrap()
1173                    == pubkey
1174                {
1175                    callback(
1176                        $transaction_context
1177                            .get_account_at_index(index)
1178                            .unwrap()
1179                            .borrow()
1180                            .data(),
1181                    );
1182                }
1183            }
1184        });
1185        // Create a stakes handle with default epoch (0) - tests that need specific epochs
1186        // should set up the stake cache data appropriately.
1187        // The default StakesHandle includes an epoch_rewards_exists_fn that returns false.
1188        let mock_stakes_handle = $crate::invoke_context::StakesHandle::default();
1189        let environment_config = EnvironmentConfig::new(
1190            Hash::default(),
1191            0,
1192            &|_| 0,
1193            Arc::new(FeatureSet::all_enabled()),
1194            &sysvar_cache,
1195            0,
1196            mock_stakes_handle,
1197        );
1198        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1199        if let Some((loader_id, builtin)) = $entry {
1200            program_cache_for_tx_batch.replenish(
1201                loader_id,
1202                Arc::new(ProgramCacheEntry::new_builtin(0, 0, builtin)),
1203            );
1204        }
1205        let mut program_cache_for_tx = ProgramCacheForTx::from_cache(&program_cache_for_tx_batch);
1206        let mut $invoke_context = InvokeContext::new(
1207            &mut $transaction_context,
1208            &mut program_cache_for_tx,
1209            environment_config,
1210            Some(LogCollector::new_ref()),
1211            compute_budget,
1212        );
1213    };
1214    (
1215        $invoke_context:ident,
1216        $transaction_context:ident,
1217        $transaction_accounts:expr $(,)?
1218    ) => {
1219        with_mock_invoke_context!(
1220            $invoke_context,
1221            $transaction_context,
1222            None,
1223            $transaction_accounts
1224        )
1225    };
1226}
1227
1228pub fn mock_process_instruction<
1229    T: Into<StoredAccount>,
1230    F: FnMut(&mut InvokeContext<'_, '_>),
1231    G: FnMut(&mut InvokeContext<'_, '_>),
1232>(
1233    loader_id: &Pubkey,
1234    mut program_indices: Vec<IndexOfAccount>,
1235    instruction_data: &[u8],
1236    transaction_accounts: Vec<(Pubkey, T)>,
1237    instruction_account_metas: Vec<AccountMeta>,
1238    expected_result: Result<(), InstructionError>,
1239    builtin_function: BuiltinFunctionWithContext,
1240    mut pre_adjustments: F,
1241    mut post_adjustments: G,
1242) -> Vec<StoredAccount> {
1243    // Convert to StoredAccount upfront since we need to mutate the vector
1244    let mut transaction_accounts: Vec<TransactionAccount> = transaction_accounts
1245        .into_iter()
1246        .map(|(key, account)| (key, account.into()))
1247        .collect();
1248    let mut instruction_accounts: Vec<InstructionAccount> =
1249        Vec::with_capacity(instruction_account_metas.len());
1250    for (instruction_account_index, account_meta) in instruction_account_metas.iter().enumerate() {
1251        let index_in_transaction = transaction_accounts
1252            .iter()
1253            .position(|(key, _account)| *key == account_meta.pubkey)
1254            .unwrap_or(transaction_accounts.len())
1255            as IndexOfAccount;
1256        let index_in_callee = instruction_accounts
1257            .get(0..instruction_account_index)
1258            .unwrap()
1259            .iter()
1260            .position(|instruction_account| {
1261                instruction_account.index_in_transaction == index_in_transaction
1262            })
1263            .unwrap_or(instruction_account_index) as IndexOfAccount;
1264        instruction_accounts.push(InstructionAccount {
1265            index_in_transaction,
1266            index_in_caller: index_in_transaction,
1267            index_in_callee,
1268            is_signer: account_meta.is_signer,
1269            is_writable: account_meta.is_writable,
1270        });
1271    }
1272    if program_indices.is_empty() {
1273        program_indices.insert(0, transaction_accounts.len() as IndexOfAccount);
1274        let processor_account =
1275            StoredAccount::Data(AccountSharedData::new(0, 0, &native_loader::id()));
1276        transaction_accounts.push((*loader_id, processor_account));
1277    }
1278    let pop_epoch_schedule_account = if !transaction_accounts
1279        .iter()
1280        .any(|(key, _)| *key == sysvar::epoch_schedule::id())
1281    {
1282        transaction_accounts.push((
1283            sysvar::epoch_schedule::id(),
1284            create_account_shared_data_for_test(&EpochSchedule::default()),
1285        ));
1286        true
1287    } else {
1288        false
1289    };
1290    with_mock_invoke_context!(
1291        invoke_context,
1292        transaction_context,
1293        Some((*loader_id, builtin_function)),
1294        transaction_accounts
1295    );
1296    pre_adjustments(&mut invoke_context);
1297    let result = invoke_context.process_instruction(
1298        instruction_data,
1299        &instruction_accounts,
1300        &program_indices,
1301        &mut 0,
1302        &mut ExecuteTimings::default(),
1303    );
1304    assert_eq!(result, expected_result);
1305    post_adjustments(&mut invoke_context);
1306    drop(invoke_context);
1307    let mut transaction_accounts = transaction_context.deconstruct_without_keys().unwrap();
1308    if pop_epoch_schedule_account {
1309        transaction_accounts.pop();
1310    }
1311    transaction_accounts.pop();
1312    transaction_accounts
1313}
1314
1315#[cfg(test)]
1316mod tests {
1317    use rialo_s_compute_budget::compute_budget_limits;
1318    use rialo_s_instruction::Instruction;
1319    use rialo_s_rent::Rent;
1320    use serde::{Deserialize, Serialize};
1321
1322    use super::*;
1323
1324    #[derive(Debug, Serialize, Deserialize)]
1325    enum MockInstruction {
1326        NoopSuccess,
1327        NoopFail,
1328        ModifyOwned,
1329        ModifyNotOwned,
1330        ModifyReadonly,
1331        UnbalancedPush,
1332        UnbalancedPop,
1333        ConsumeComputeUnits {
1334            compute_units_to_consume: u64,
1335            desired_result: Result<(), InstructionError>,
1336        },
1337        Resize {
1338            new_len: u64,
1339        },
1340    }
1341
1342    const MOCK_BUILTIN_COMPUTE_UNIT_COST: u64 = 1;
1343
1344    declare_process_instruction!(
1345        MockBuiltin,
1346        MOCK_BUILTIN_COMPUTE_UNIT_COST,
1347        |invoke_context| {
1348            let transaction_context = &invoke_context.transaction_context;
1349            let instruction_context = transaction_context.get_current_instruction_context()?;
1350            let instruction_data = instruction_context.get_instruction_data();
1351            let program_id = instruction_context.get_last_program_key(transaction_context)?;
1352            let instruction_accounts = (0..4)
1353                .map(|instruction_account_index| InstructionAccount {
1354                    index_in_transaction: instruction_account_index,
1355                    index_in_caller: instruction_account_index,
1356                    index_in_callee: instruction_account_index,
1357                    is_signer: false,
1358                    is_writable: false,
1359                })
1360                .collect::<Vec<_>>();
1361            assert_eq!(
1362                program_id,
1363                instruction_context
1364                    .try_borrow_instruction_account(transaction_context, 0)?
1365                    .get_owner()
1366            );
1367            assert_ne!(
1368                instruction_context
1369                    .try_borrow_instruction_account(transaction_context, 1)?
1370                    .get_owner(),
1371                instruction_context
1372                    .try_borrow_instruction_account(transaction_context, 0)?
1373                    .get_key()
1374            );
1375
1376            if let Ok(instruction) = bincode::deserialize(instruction_data) {
1377                match instruction {
1378                    MockInstruction::NoopSuccess => (),
1379                    MockInstruction::NoopFail => return Err(InstructionError::GenericError),
1380                    MockInstruction::ModifyOwned => instruction_context
1381                        .try_borrow_instruction_account(transaction_context, 0)?
1382                        .set_data_from_slice(&[1])?,
1383                    MockInstruction::ModifyNotOwned => instruction_context
1384                        .try_borrow_instruction_account(transaction_context, 1)?
1385                        .set_data_from_slice(&[1])?,
1386                    MockInstruction::ModifyReadonly => instruction_context
1387                        .try_borrow_instruction_account(transaction_context, 2)?
1388                        .set_data_from_slice(&[1])?,
1389                    MockInstruction::UnbalancedPush => {
1390                        instruction_context
1391                            .try_borrow_instruction_account(transaction_context, 0)?
1392                            .checked_add_kelvins(1)?;
1393                        let program_id = *transaction_context.get_key_of_account_at_index(3)?;
1394                        let metas = vec![
1395                            AccountMeta::new_readonly(
1396                                *transaction_context.get_key_of_account_at_index(0)?,
1397                                false,
1398                            ),
1399                            AccountMeta::new_readonly(
1400                                *transaction_context.get_key_of_account_at_index(1)?,
1401                                false,
1402                            ),
1403                        ];
1404                        let inner_instruction = Instruction::new_with_bincode(
1405                            program_id,
1406                            &MockInstruction::NoopSuccess,
1407                            metas,
1408                        );
1409                        invoke_context
1410                            .transaction_context
1411                            .get_next_instruction_context()
1412                            .unwrap()
1413                            .configure(&[3], &instruction_accounts, &[]);
1414                        let result = invoke_context.push();
1415                        assert_eq!(result, Err(InstructionError::UnbalancedInstruction));
1416                        result?;
1417                        invoke_context
1418                            .native_invoke(inner_instruction.into(), &[])
1419                            .and(invoke_context.pop())?;
1420                    }
1421                    MockInstruction::UnbalancedPop => instruction_context
1422                        .try_borrow_instruction_account(transaction_context, 0)?
1423                        .checked_add_kelvins(1)?,
1424                    MockInstruction::ConsumeComputeUnits {
1425                        compute_units_to_consume,
1426                        desired_result,
1427                    } => {
1428                        invoke_context
1429                            .consume_checked(compute_units_to_consume)
1430                            .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
1431                        return desired_result;
1432                    }
1433                    MockInstruction::Resize { new_len } => instruction_context
1434                        .try_borrow_instruction_account(transaction_context, 0)?
1435                        .set_data(vec![0; new_len as usize])?,
1436                }
1437            } else {
1438                return Err(InstructionError::InvalidInstructionData);
1439            }
1440            Ok(())
1441        }
1442    );
1443
1444    #[test]
1445    fn test_instruction_stack_height() {
1446        let one_more_than_max_depth = ComputeBudget::default()
1447            .max_instruction_stack_depth
1448            .saturating_add(1);
1449        let mut invoke_stack = vec![];
1450        let mut transaction_accounts = vec![];
1451        let mut instruction_accounts = vec![];
1452        for index in 0..one_more_than_max_depth {
1453            invoke_stack.push(rialo_s_pubkey::new_rand());
1454            transaction_accounts.push((
1455                rialo_s_pubkey::new_rand(),
1456                AccountSharedData::new(index as u64, 1, invoke_stack.get(index).unwrap()),
1457            ));
1458            instruction_accounts.push(InstructionAccount {
1459                index_in_transaction: index as IndexOfAccount,
1460                index_in_caller: index as IndexOfAccount,
1461                index_in_callee: instruction_accounts.len() as IndexOfAccount,
1462                is_signer: false,
1463                is_writable: true,
1464            });
1465        }
1466        for (index, program_id) in invoke_stack.iter().enumerate() {
1467            transaction_accounts.push((
1468                *program_id,
1469                AccountSharedData::new(1, 1, &rialo_s_pubkey::Pubkey::default()),
1470            ));
1471            instruction_accounts.push(InstructionAccount {
1472                index_in_transaction: index as IndexOfAccount,
1473                index_in_caller: index as IndexOfAccount,
1474                index_in_callee: index as IndexOfAccount,
1475                is_signer: false,
1476                is_writable: false,
1477            });
1478        }
1479        let transaction_accounts: Vec<(Pubkey, StoredAccount)> = transaction_accounts
1480            .into_iter()
1481            .map(|(k, v)| (k, v.into()))
1482            .collect();
1483        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1484
1485        // Check call depth increases and has a limit
1486        let mut depth_reached = 0;
1487        for _ in 0..invoke_stack.len() {
1488            invoke_context
1489                .transaction_context
1490                .get_next_instruction_context()
1491                .unwrap()
1492                .configure(
1493                    &[one_more_than_max_depth.saturating_add(depth_reached) as IndexOfAccount],
1494                    &instruction_accounts,
1495                    &[],
1496                );
1497            if Err(InstructionError::CallDepth) == invoke_context.push() {
1498                break;
1499            }
1500            depth_reached = depth_reached.saturating_add(1);
1501        }
1502        assert_ne!(depth_reached, 0);
1503        assert!(depth_reached < one_more_than_max_depth);
1504    }
1505
1506    #[test]
1507    fn test_max_instruction_trace_length() {
1508        const MAX_INSTRUCTIONS: usize = 8;
1509        let mut transaction_context = TransactionContext::new::<StoredAccount>(
1510            Vec::new(),
1511            Rent::default(),
1512            1,
1513            MAX_INSTRUCTIONS,
1514        );
1515        for _ in 0..MAX_INSTRUCTIONS {
1516            transaction_context.push().unwrap();
1517            transaction_context.pop().unwrap();
1518        }
1519        assert_eq!(
1520            transaction_context.push(),
1521            Err(InstructionError::MaxInstructionTraceLengthExceeded)
1522        );
1523    }
1524
1525    #[test]
1526    fn test_process_instruction() {
1527        let callee_program_id = rialo_s_pubkey::new_rand();
1528        let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1529        let not_owned_account = AccountSharedData::new(84, 1, &rialo_s_pubkey::new_rand());
1530        let readonly_account = AccountSharedData::new(168, 1, &rialo_s_pubkey::new_rand());
1531        let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1532        let program_account = AccountSharedData::new(1, 1, &native_loader::id());
1533        let transaction_accounts = vec![
1534            (rialo_s_pubkey::new_rand(), owned_account),
1535            (rialo_s_pubkey::new_rand(), not_owned_account),
1536            (rialo_s_pubkey::new_rand(), readonly_account),
1537            (callee_program_id, program_account),
1538            (rialo_s_pubkey::new_rand(), loader_account),
1539        ];
1540        let metas = vec![
1541            AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1542            AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1543            AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1544        ];
1545        let instruction_accounts = (0..4)
1546            .map(|instruction_account_index| InstructionAccount {
1547                index_in_transaction: instruction_account_index,
1548                index_in_caller: instruction_account_index,
1549                index_in_callee: instruction_account_index,
1550                is_signer: false,
1551                is_writable: instruction_account_index < 2,
1552            })
1553            .collect::<Vec<_>>();
1554        let transaction_accounts: Vec<(Pubkey, StoredAccount)> = transaction_accounts
1555            .into_iter()
1556            .map(|(k, v)| (k, v.into()))
1557            .collect();
1558        with_mock_invoke_context!(
1559            invoke_context,
1560            transaction_context,
1561            Some((callee_program_id, MockBuiltin::vm)),
1562            transaction_accounts
1563        );
1564
1565        // Account modification tests
1566        let cases = vec![
1567            (MockInstruction::NoopSuccess, Ok(())),
1568            (
1569                MockInstruction::NoopFail,
1570                Err(InstructionError::GenericError),
1571            ),
1572            (MockInstruction::ModifyOwned, Ok(())),
1573            (
1574                MockInstruction::ModifyNotOwned,
1575                Err(InstructionError::ExternalAccountDataModified),
1576            ),
1577            (
1578                MockInstruction::ModifyReadonly,
1579                Err(InstructionError::ReadonlyDataModified),
1580            ),
1581            (
1582                MockInstruction::UnbalancedPush,
1583                Err(InstructionError::UnbalancedInstruction),
1584            ),
1585            (
1586                MockInstruction::UnbalancedPop,
1587                Err(InstructionError::UnbalancedInstruction),
1588            ),
1589        ];
1590        for case in cases {
1591            invoke_context
1592                .transaction_context
1593                .get_next_instruction_context()
1594                .unwrap()
1595                .configure(&[4], &instruction_accounts, &[]);
1596            invoke_context.push().unwrap();
1597            let inner_instruction =
1598                Instruction::new_with_bincode(callee_program_id, &case.0, metas.clone());
1599            let result = invoke_context
1600                .native_invoke(inner_instruction.into(), &[])
1601                .and(invoke_context.pop());
1602            assert_eq!(result, case.1);
1603        }
1604
1605        // Compute unit consumption tests
1606        let compute_units_to_consume = 10;
1607        let expected_results = vec![Ok(()), Err(InstructionError::GenericError)];
1608        for expected_result in expected_results {
1609            invoke_context
1610                .transaction_context
1611                .get_next_instruction_context()
1612                .unwrap()
1613                .configure(&[4], &instruction_accounts, &[]);
1614            invoke_context.push().unwrap();
1615            let inner_instruction = Instruction::new_with_bincode(
1616                callee_program_id,
1617                &MockInstruction::ConsumeComputeUnits {
1618                    compute_units_to_consume,
1619                    desired_result: expected_result.clone(),
1620                },
1621                metas.clone(),
1622            );
1623            let inner_instruction = StableInstruction::from(inner_instruction);
1624            let (inner_instruction_accounts, program_indices) = invoke_context
1625                .prepare_instruction(&inner_instruction, &[])
1626                .unwrap();
1627
1628            let mut compute_units_consumed = 0;
1629            let result = invoke_context.process_instruction(
1630                &inner_instruction.data,
1631                &inner_instruction_accounts,
1632                &program_indices,
1633                &mut compute_units_consumed,
1634                &mut ExecuteTimings::default(),
1635            );
1636
1637            // Because the instruction had compute cost > 0, then regardless of the execution result,
1638            // the number of compute units consumed should be a non-default which is something greater
1639            // than zero.
1640            assert!(compute_units_consumed > 0);
1641            assert_eq!(
1642                compute_units_consumed,
1643                compute_units_to_consume.saturating_add(MOCK_BUILTIN_COMPUTE_UNIT_COST),
1644            );
1645            assert_eq!(result, expected_result);
1646
1647            invoke_context.pop().unwrap();
1648        }
1649    }
1650
1651    #[test]
1652    fn test_invoke_context_compute_budget() {
1653        let transaction_accounts = vec![(rialo_s_pubkey::new_rand(), AccountSharedData::default())];
1654        let transaction_accounts: Vec<(Pubkey, StoredAccount)> = transaction_accounts
1655            .into_iter()
1656            .map(|(k, v)| (k, v.into()))
1657            .collect();
1658
1659        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1660        invoke_context.compute_budget = ComputeBudget::new(
1661            compute_budget_limits::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64,
1662        );
1663
1664        invoke_context
1665            .transaction_context
1666            .get_next_instruction_context()
1667            .unwrap()
1668            .configure(&[0], &[], &[]);
1669        invoke_context.push().unwrap();
1670        assert_eq!(
1671            *invoke_context.get_compute_budget(),
1672            ComputeBudget::new(
1673                compute_budget_limits::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64
1674            )
1675        );
1676        invoke_context.pop().unwrap();
1677    }
1678
1679    #[test]
1680    fn test_process_instruction_accounts_resize_delta() {
1681        let program_key = Pubkey::new_unique();
1682        let user_account_data_len = 123u64;
1683        let user_account =
1684            AccountSharedData::new(100, user_account_data_len as usize, &program_key);
1685        let dummy_account = AccountSharedData::new(10, 0, &program_key);
1686        let program_account = AccountSharedData::new(500, 500, &native_loader::id());
1687        let transaction_accounts = vec![
1688            (Pubkey::new_unique(), user_account),
1689            (Pubkey::new_unique(), dummy_account),
1690            (program_key, program_account),
1691        ];
1692        let instruction_accounts = [
1693            InstructionAccount {
1694                index_in_transaction: 0,
1695                index_in_caller: 0,
1696                index_in_callee: 0,
1697                is_signer: false,
1698                is_writable: true,
1699            },
1700            InstructionAccount {
1701                index_in_transaction: 1,
1702                index_in_caller: 1,
1703                index_in_callee: 1,
1704                is_signer: false,
1705                is_writable: false,
1706            },
1707        ];
1708        let transaction_accounts: Vec<(Pubkey, StoredAccount)> = transaction_accounts
1709            .into_iter()
1710            .map(|(k, v)| (k, v.into()))
1711            .collect();
1712        with_mock_invoke_context!(
1713            invoke_context,
1714            transaction_context,
1715            Some((program_key, MockBuiltin::vm)),
1716            transaction_accounts
1717        );
1718
1719        // Test: Resize the account to *the same size*, so not consuming any additional size; this must succeed
1720        {
1721            let resize_delta: i64 = 0;
1722            let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
1723            let instruction_data =
1724                bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1725
1726            let result = invoke_context.process_instruction(
1727                &instruction_data,
1728                &instruction_accounts,
1729                &[2],
1730                &mut 0,
1731                &mut ExecuteTimings::default(),
1732            );
1733
1734            assert!(result.is_ok());
1735            assert_eq!(
1736                invoke_context
1737                    .transaction_context
1738                    .accounts_resize_delta()
1739                    .unwrap(),
1740                resize_delta
1741            );
1742        }
1743
1744        // Test: Resize the account larger; this must succeed
1745        {
1746            let resize_delta: i64 = 1;
1747            let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
1748            let instruction_data =
1749                bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1750
1751            let result = invoke_context.process_instruction(
1752                &instruction_data,
1753                &instruction_accounts,
1754                &[2],
1755                &mut 0,
1756                &mut ExecuteTimings::default(),
1757            );
1758
1759            assert!(result.is_ok());
1760            assert_eq!(
1761                invoke_context
1762                    .transaction_context
1763                    .accounts_resize_delta()
1764                    .unwrap(),
1765                resize_delta
1766            );
1767        }
1768
1769        // Test: Resize the account smaller; this must succeed
1770        {
1771            let resize_delta: i64 = -1;
1772            let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
1773            let instruction_data =
1774                bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1775
1776            let result = invoke_context.process_instruction(
1777                &instruction_data,
1778                &instruction_accounts,
1779                &[2],
1780                &mut 0,
1781                &mut ExecuteTimings::default(),
1782            );
1783
1784            assert!(result.is_ok());
1785            assert_eq!(
1786                invoke_context
1787                    .transaction_context
1788                    .accounts_resize_delta()
1789                    .unwrap(),
1790                resize_delta
1791            );
1792        }
1793    }
1794}