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