rialo_s_program_runtime/
invoke_context.rs

1use std::{
2    alloc::Layout,
3    cell::RefCell,
4    fmt::{self, Debug},
5    rc::Rc,
6};
7
8use rialo_hash::Hash;
9use rialo_s_account::{create_account_shared_data_for_test, AccountSharedData};
10use rialo_s_clock::Slot;
11use rialo_s_compute_budget::compute_budget::ComputeBudget;
12use rialo_s_epoch_schedule::EpochSchedule;
13use rialo_s_feature_set::{
14    lift_cpi_caller_restriction, move_precompile_verification_to_svm,
15    remove_accounts_executable_flag_checks, FeatureSet,
16};
17use rialo_s_instruction::{error::InstructionError, AccountMeta};
18use rialo_s_log_collector::{ic_msg, LogCollector};
19use rialo_s_measure::measure::Measure;
20use rialo_s_precompiles::Precompile;
21use rialo_s_pubkey::Pubkey;
22use rialo_s_sdk_ids::{bpf_loader_deprecated, native_loader, sysvar};
23use rialo_s_stable_layout::stable_instruction::StableInstruction;
24use rialo_s_timings::{ExecuteDetailsTimings, ExecuteTimings};
25use rialo_s_transaction_context::{
26    IndexOfAccount, InstructionAccount, TransactionAccount, TransactionContext,
27};
28use rialo_s_type_overrides::sync::{atomic::Ordering, Arc};
29use solana_sbpf::{
30    ebpf::MM_HEAP_START,
31    error::{EbpfError, ProgramResult},
32    memory_region::MemoryMapping,
33    program::{BuiltinFunction, SBPFVersion},
34    vm::{Config, ContextObject, EbpfVm},
35};
36
37use crate::{
38    loaded_programs::{
39        ProgramCacheEntry, ProgramCacheEntryType, ProgramCacheForTxBatch,
40        ProgramRuntimeEnvironments,
41    },
42    stable_log,
43    sysvar_cache::SysvarCache,
44};
45
46pub type BuiltinFunctionWithContext = BuiltinFunction<InvokeContext<'static>>;
47
48/// Adapter so we can unify the interfaces of built-in programs and syscalls
49#[macro_export]
50macro_rules! declare_process_instruction {
51    ($process_instruction:ident, $cu_to_consume:expr, |$invoke_context:ident| $inner:tt) => {
52        $crate::solana_sbpf::declare_builtin_function!(
53            $process_instruction,
54            fn rust(
55                invoke_context: &mut $crate::invoke_context::InvokeContext,
56                _arg0: u64,
57                _arg1: u64,
58                _arg2: u64,
59                _arg3: u64,
60                _arg4: u64,
61                _memory_mapping: &mut $crate::solana_sbpf::memory_region::MemoryMapping,
62            ) -> std::result::Result<u64, Box<dyn std::error::Error>> {
63                fn process_instruction_inner(
64                    $invoke_context: &mut $crate::invoke_context::InvokeContext,
65                ) -> std::result::Result<(), $crate::__private::InstructionError>
66                    $inner
67
68                let consumption_result = if $cu_to_consume > 0
69                {
70                    invoke_context.consume_checked($cu_to_consume)
71                } else {
72                    Ok(())
73                };
74                consumption_result
75                    .and_then(|_| {
76                        process_instruction_inner(invoke_context)
77                            .map(|_| 0)
78                            .map_err(|err| Box::new(err) as Box<dyn std::error::Error>)
79                    })
80                    .into()
81            }
82        );
83    };
84}
85
86impl ContextObject for InvokeContext<'_> {
87    fn trace(&mut self, state: [u64; 12]) {
88        self.syscall_context
89            .last_mut()
90            .unwrap()
91            .as_mut()
92            .unwrap()
93            .trace_log
94            .push(state);
95    }
96
97    fn consume(&mut self, amount: u64) {
98        // 1 to 1 instruction to compute unit mapping
99        // ignore overflow, Ebpf will bail if exceeded
100        let mut compute_meter = self.compute_meter.borrow_mut();
101        *compute_meter = compute_meter.saturating_sub(amount);
102    }
103
104    fn get_remaining(&self) -> u64 {
105        *self.compute_meter.borrow()
106    }
107}
108
109#[derive(Clone, PartialEq, Eq, Debug)]
110pub struct AllocErr;
111impl fmt::Display for AllocErr {
112    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
113        f.write_str("Error: Memory allocation failed")
114    }
115}
116
117pub struct BpfAllocator {
118    len: u64,
119    pos: u64,
120}
121
122impl BpfAllocator {
123    pub fn new(len: u64) -> Self {
124        Self { len, pos: 0 }
125    }
126
127    pub fn alloc(&mut self, layout: Layout) -> Result<u64, AllocErr> {
128        let bytes_to_align = (self.pos as *const u8).align_offset(layout.align()) as u64;
129        if self
130            .pos
131            .saturating_add(bytes_to_align)
132            .saturating_add(layout.size() as u64)
133            <= self.len
134        {
135            self.pos = self.pos.saturating_add(bytes_to_align);
136            let addr = MM_HEAP_START.saturating_add(self.pos);
137            self.pos = self.pos.saturating_add(layout.size() as u64);
138            Ok(addr)
139        } else {
140            Err(AllocErr)
141        }
142    }
143}
144
145pub struct EnvironmentConfig<'a> {
146    pub blockhash: Hash,
147    pub blockhash_lamports_per_signature: u64,
148    epoch_total_stake: u64,
149    get_epoch_vote_account_stake_callback: &'a dyn Fn(&'a Pubkey) -> u64,
150    pub feature_set: Arc<FeatureSet>,
151    sysvar_cache: &'a SysvarCache,
152}
153impl<'a> EnvironmentConfig<'a> {
154    pub fn new(
155        blockhash: Hash,
156        blockhash_lamports_per_signature: u64,
157        epoch_total_stake: u64,
158        get_epoch_vote_account_stake_callback: &'a dyn Fn(&'a Pubkey) -> u64,
159        feature_set: Arc<FeatureSet>,
160        sysvar_cache: &'a SysvarCache,
161    ) -> Self {
162        Self {
163            blockhash,
164            blockhash_lamports_per_signature,
165            epoch_total_stake,
166            get_epoch_vote_account_stake_callback,
167            feature_set,
168            sysvar_cache,
169        }
170    }
171}
172
173pub struct SyscallContext {
174    pub allocator: BpfAllocator,
175    pub accounts_metadata: Vec<SerializedAccountMetadata>,
176    pub trace_log: Vec<[u64; 12]>,
177}
178
179#[derive(Debug, Clone)]
180pub struct SerializedAccountMetadata {
181    pub original_data_len: usize,
182    pub vm_data_addr: u64,
183    pub vm_key_addr: u64,
184    pub vm_lamports_addr: u64,
185    pub vm_owner_addr: u64,
186}
187
188/// Main pipeline from runtime to program execution.
189pub struct InvokeContext<'a> {
190    /// Information about the currently executing transaction.
191    pub transaction_context: &'a mut TransactionContext,
192    /// The local program cache for the transaction batch.
193    pub program_cache_for_tx_batch: &'a mut ProgramCacheForTxBatch,
194    /// Runtime configurations used to provision the invocation environment.
195    pub environment_config: EnvironmentConfig<'a>,
196    /// The compute budget for the current invocation.
197    compute_budget: ComputeBudget,
198    /// Instruction compute meter, for tracking compute units consumed against
199    /// the designated compute budget during program execution.
200    compute_meter: RefCell<u64>,
201    log_collector: Option<Rc<RefCell<LogCollector>>>,
202    /// Latest measurement not yet accumulated in [ExecuteDetailsTimings::execute_us]
203    pub execute_time: Option<Measure>,
204    pub timings: ExecuteDetailsTimings,
205    pub syscall_context: Vec<Option<SyscallContext>>,
206    traces: Vec<Vec<[u64; 12]>>,
207}
208
209impl<'a> InvokeContext<'a> {
210    #[allow(clippy::too_many_arguments)]
211    pub fn new(
212        transaction_context: &'a mut TransactionContext,
213        program_cache_for_tx_batch: &'a mut ProgramCacheForTxBatch,
214        environment_config: EnvironmentConfig<'a>,
215        log_collector: Option<Rc<RefCell<LogCollector>>>,
216        compute_budget: ComputeBudget,
217    ) -> Self {
218        Self {
219            transaction_context,
220            program_cache_for_tx_batch,
221            environment_config,
222            log_collector,
223            compute_budget,
224            compute_meter: RefCell::new(compute_budget.compute_unit_limit),
225            execute_time: None,
226            timings: ExecuteDetailsTimings::default(),
227            syscall_context: Vec::new(),
228            traces: Vec::new(),
229        }
230    }
231
232    pub fn get_environments_for_slot(
233        &self,
234        effective_slot: Slot,
235    ) -> Result<&ProgramRuntimeEnvironments, InstructionError> {
236        let epoch_schedule = self.environment_config.sysvar_cache.get_epoch_schedule()?;
237        let epoch = epoch_schedule.get_epoch(effective_slot);
238        Ok(self
239            .program_cache_for_tx_batch
240            .get_environments_for_epoch(epoch))
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        if let Some(Some(syscall_context)) = self.syscall_context.pop() {
292            self.traces.push(syscall_context.trace_log);
293        }
294        self.transaction_context.pop()
295    }
296
297    /// Current height of the invocation stack, top level instructions are height
298    /// `rialo_s_instruction::TRANSACTION_LEVEL_STACK_HEIGHT`
299    pub fn get_stack_height(&self) -> usize {
300        self.transaction_context
301            .get_instruction_context_stack_height()
302    }
303
304    /// Entrypoint for a cross-program invocation from a builtin program
305    pub fn native_invoke(
306        &mut self,
307        instruction: StableInstruction,
308        signers: &[Pubkey],
309    ) -> Result<(), InstructionError> {
310        let (instruction_accounts, program_indices) =
311            self.prepare_instruction(&instruction, signers)?;
312        let mut compute_units_consumed = 0;
313        self.process_instruction(
314            &instruction.data,
315            &instruction_accounts,
316            &program_indices,
317            &mut compute_units_consumed,
318            &mut ExecuteTimings::default(),
319        )?;
320        Ok(())
321    }
322
323    /// Helper to prepare for process_instruction()
324    #[allow(clippy::type_complexity)]
325    pub fn prepare_instruction(
326        &mut self,
327        instruction: &StableInstruction,
328        signers: &[Pubkey],
329    ) -> Result<(Vec<InstructionAccount>, Vec<IndexOfAccount>), InstructionError> {
330        self.prepare_instruction_inner(instruction.program_id, &instruction.accounts, signers)
331    }
332
333    /// Helper to prepare for process_instruction()
334    pub fn prepare_cpi_instruction(
335        &mut self,
336        program_id: Pubkey,
337        account_metas: &[AccountMeta],
338        signers: &[Pubkey],
339    ) -> Result<(Vec<InstructionAccount>, Vec<IndexOfAccount>), InstructionError> {
340        self.prepare_instruction_inner(program_id, account_metas, signers)
341    }
342
343    pub fn prepare_instruction_inner(
344        &mut self,
345        callee_program_id: Pubkey,
346        account_metas: &[AccountMeta],
347        signers: &[Pubkey],
348    ) -> Result<(Vec<InstructionAccount>, Vec<IndexOfAccount>), InstructionError> {
349        // Finds the index of each account in the instruction by its pubkey.
350        // Then normalizes / unifies the privileges of duplicate accounts.
351        // Note: This is an O(n^2) algorithm,
352        // but performed on a very small slice and requires no heap allocations.
353        let instruction_context = self.transaction_context.get_current_instruction_context()?;
354        let mut deduplicated_instruction_accounts: Vec<InstructionAccount> = Vec::new();
355        let mut duplicate_indicies = Vec::with_capacity(account_metas.len() as usize);
356        for (instruction_account_index, account_meta) in account_metas.iter().enumerate() {
357            let index_in_transaction = self
358                .transaction_context
359                .find_index_of_account(&account_meta.pubkey)
360                .ok_or_else(|| {
361                    ic_msg!(
362                        self,
363                        "Instruction references an unknown account {}",
364                        account_meta.pubkey,
365                    );
366                    InstructionError::MissingAccount
367                })?;
368            if let Some(duplicate_index) =
369                deduplicated_instruction_accounts
370                    .iter()
371                    .position(|instruction_account| {
372                        instruction_account.index_in_transaction == index_in_transaction
373                    })
374            {
375                duplicate_indicies.push(duplicate_index);
376                let instruction_account = deduplicated_instruction_accounts
377                    .get_mut(duplicate_index)
378                    .ok_or(InstructionError::NotEnoughAccountKeys)?;
379                instruction_account.is_signer |= account_meta.is_signer;
380                instruction_account.is_writable |= account_meta.is_writable;
381            } else {
382                let index_in_caller = instruction_context
383                    .find_index_of_instruction_account(
384                        self.transaction_context,
385                        &account_meta.pubkey,
386                    )
387                    .ok_or_else(|| {
388                        ic_msg!(
389                            self,
390                            "Instruction references an unknown account {}",
391                            account_meta.pubkey,
392                        );
393                        InstructionError::MissingAccount
394                    })?;
395                duplicate_indicies.push(deduplicated_instruction_accounts.len());
396                deduplicated_instruction_accounts.push(InstructionAccount {
397                    index_in_transaction,
398                    index_in_caller,
399                    index_in_callee: instruction_account_index as IndexOfAccount,
400                    is_signer: account_meta.is_signer,
401                    is_writable: account_meta.is_writable,
402                });
403            }
404        }
405        for instruction_account in deduplicated_instruction_accounts.iter() {
406            let borrowed_account = instruction_context.try_borrow_instruction_account(
407                self.transaction_context,
408                instruction_account.index_in_caller,
409            )?;
410
411            // Readonly in caller cannot become writable in callee
412            if instruction_account.is_writable && !borrowed_account.is_writable() {
413                ic_msg!(
414                    self,
415                    "{}'s writable privilege escalated",
416                    borrowed_account.get_key(),
417                );
418                return Err(InstructionError::PrivilegeEscalation);
419            }
420
421            // To be signed in the callee,
422            // it must be either signed in the caller or by the program
423            if instruction_account.is_signer
424                && !(borrowed_account.is_signer() || signers.contains(borrowed_account.get_key()))
425            {
426                ic_msg!(
427                    self,
428                    "{}'s signer privilege escalated",
429                    borrowed_account.get_key()
430                );
431                return Err(InstructionError::PrivilegeEscalation);
432            }
433        }
434        let instruction_accounts = duplicate_indicies
435            .into_iter()
436            .map(|duplicate_index| {
437                deduplicated_instruction_accounts
438                    .get(duplicate_index)
439                    .cloned()
440                    .ok_or(InstructionError::NotEnoughAccountKeys)
441            })
442            .collect::<Result<Vec<InstructionAccount>, InstructionError>>()?;
443
444        // Find and validate executables / program accounts
445        let program_account_index = if self
446            .get_feature_set()
447            .is_active(&lift_cpi_caller_restriction::id())
448        {
449            self.transaction_context
450                .find_index_of_program_account(&callee_program_id)
451                .ok_or_else(|| {
452                    ic_msg!(self, "Unknown program {}", callee_program_id);
453                    InstructionError::MissingAccount
454                })?
455        } else {
456            let program_account_index = instruction_context
457                .find_index_of_instruction_account(self.transaction_context, &callee_program_id)
458                .ok_or_else(|| {
459                    ic_msg!(self, "Unknown program {}", callee_program_id);
460                    InstructionError::MissingAccount
461                })?;
462            let borrowed_program_account = instruction_context
463                .try_borrow_instruction_account(self.transaction_context, program_account_index)?;
464            #[allow(deprecated)]
465            if !self
466                .get_feature_set()
467                .is_active(&remove_accounts_executable_flag_checks::id())
468                && !borrowed_program_account.is_executable()
469            {
470                ic_msg!(self, "Account {} is not executable", callee_program_id);
471                return Err(InstructionError::AccountNotExecutable);
472            }
473            borrowed_program_account.get_index_in_transaction()
474        };
475
476        Ok((instruction_accounts, vec![program_account_index]))
477    }
478
479    /// Processes an instruction and returns how many compute units were used
480    pub fn process_instruction(
481        &mut self,
482        instruction_data: &[u8],
483        instruction_accounts: &[InstructionAccount],
484        program_indices: &[IndexOfAccount],
485        compute_units_consumed: &mut u64,
486        timings: &mut ExecuteTimings,
487    ) -> Result<(), InstructionError> {
488        *compute_units_consumed = 0;
489        self.transaction_context
490            .get_next_instruction_context()?
491            .configure(program_indices, instruction_accounts, instruction_data);
492        self.push()?;
493        self.process_executable_chain(compute_units_consumed, timings)
494            // MUST pop if and only if `push` succeeded, independent of `result`.
495            // Thus, the `.and()` instead of an `.and_then()`.
496            .and(self.pop())
497    }
498
499    /// Processes a precompile instruction
500    pub fn process_precompile<'ix_data>(
501        &mut self,
502        precompile: &Precompile,
503        instruction_data: &[u8],
504        instruction_accounts: &[InstructionAccount],
505        program_indices: &[IndexOfAccount],
506        message_instruction_datas_iter: impl Iterator<Item = &'ix_data [u8]>,
507    ) -> Result<(), InstructionError> {
508        self.transaction_context
509            .get_next_instruction_context()?
510            .configure(program_indices, instruction_accounts, instruction_data);
511        self.push()?;
512
513        let feature_set = self.get_feature_set();
514        let move_precompile_verification_to_svm =
515            feature_set.is_active(&move_precompile_verification_to_svm::id());
516        if move_precompile_verification_to_svm {
517            let instruction_datas: Vec<_> = message_instruction_datas_iter.collect();
518            precompile
519                .verify(instruction_data, &instruction_datas, feature_set)
520                .map_err(InstructionError::from)
521                .and(self.pop())
522        } else {
523            self.pop()
524        }
525    }
526
527    /// Calls the instruction's program entrypoint method
528    fn process_executable_chain(
529        &mut self,
530        compute_units_consumed: &mut u64,
531        timings: &mut ExecuteTimings,
532    ) -> Result<(), InstructionError> {
533        let instruction_context = self.transaction_context.get_current_instruction_context()?;
534        let process_executable_chain_time = Measure::start("process_executable_chain_time");
535
536        let builtin_id = {
537            debug_assert!(instruction_context.get_number_of_program_accounts() <= 1);
538            let borrowed_root_account = instruction_context
539                .try_borrow_program_account(self.transaction_context, 0)
540                .map_err(|_| InstructionError::UnsupportedProgramId)?;
541            let owner_id = borrowed_root_account.get_owner();
542            if native_loader::check_id(owner_id) {
543                *borrowed_root_account.get_key()
544            } else {
545                *owner_id
546            }
547        };
548
549        // The Murmur3 hash value (used by RBPF) of the string "entrypoint"
550        const ENTRYPOINT_KEY: u32 = 0x71E3CF81;
551        let entry = self
552            .program_cache_for_tx_batch
553            .find(&builtin_id)
554            .ok_or(InstructionError::UnsupportedProgramId)?;
555        let function = match &entry.program {
556            ProgramCacheEntryType::Builtin(program) => program
557                .get_function_registry()
558                .lookup_by_key(ENTRYPOINT_KEY)
559                .map(|(_name, function)| function),
560            _ => None,
561        }
562        .ok_or(InstructionError::UnsupportedProgramId)?;
563        entry.ix_usage_counter.fetch_add(1, Ordering::Relaxed);
564
565        let program_id = *instruction_context.get_last_program_key(self.transaction_context)?;
566        self.transaction_context
567            .set_return_data(program_id, Vec::new())?;
568        let logger = self.get_log_collector();
569        stable_log::program_invoke(&logger, &program_id, self.get_stack_height());
570        let pre_remaining_units = self.get_remaining();
571        // In program-runtime v2 we will create this VM instance only once per transaction.
572        // `program_runtime_environment_v2.get_config()` will be used instead of `mock_config`.
573        // For now, only built-ins are invoked from here, so the VM and its Config are irrelevant.
574        let mock_config = Config::default();
575        let empty_memory_mapping =
576            MemoryMapping::new(Vec::new(), &mock_config, SBPFVersion::V0).unwrap();
577        let mut vm = EbpfVm::new(
578            self.program_cache_for_tx_batch
579                .environments
580                .program_runtime_v2
581                .clone(),
582            SBPFVersion::V0,
583            // Removes lifetime tracking
584            unsafe { std::mem::transmute::<&mut InvokeContext, &mut InvokeContext>(self) },
585            empty_memory_mapping,
586            0,
587        );
588        vm.invoke_function(function);
589        let result = match vm.program_result {
590            ProgramResult::Ok(_) => {
591                stable_log::program_success(&logger, &program_id);
592                Ok(())
593            }
594            ProgramResult::Err(ref err) => {
595                if let EbpfError::SyscallError(syscall_error) = err {
596                    if let Some(instruction_err) = syscall_error.downcast_ref::<InstructionError>()
597                    {
598                        stable_log::program_failure(&logger, &program_id, instruction_err);
599                        Err(instruction_err.clone())
600                    } else {
601                        stable_log::program_failure(&logger, &program_id, syscall_error);
602                        Err(InstructionError::ProgramFailedToComplete)
603                    }
604                } else {
605                    stable_log::program_failure(&logger, &program_id, err);
606                    Err(InstructionError::ProgramFailedToComplete)
607                }
608            }
609        };
610        let post_remaining_units = self.get_remaining();
611        *compute_units_consumed = pre_remaining_units.saturating_sub(post_remaining_units);
612
613        if builtin_id == program_id && result.is_ok() && *compute_units_consumed == 0 {
614            return Err(InstructionError::BuiltinProgramsMustConsumeComputeUnits);
615        }
616
617        timings
618            .execute_accessories
619            .process_instructions
620            .process_executable_chain_us += process_executable_chain_time.end_as_us();
621        result
622    }
623
624    /// Get this invocation's LogCollector
625    pub fn get_log_collector(&self) -> Option<Rc<RefCell<LogCollector>>> {
626        self.log_collector.clone()
627    }
628
629    /// Consume compute units
630    pub fn consume_checked(&self, amount: u64) -> Result<(), Box<dyn std::error::Error>> {
631        let mut compute_meter = self.compute_meter.borrow_mut();
632        let exceeded = *compute_meter < amount;
633        *compute_meter = compute_meter.saturating_sub(amount);
634        if exceeded {
635            return Err(Box::new(InstructionError::ComputationalBudgetExceeded));
636        }
637        Ok(())
638    }
639
640    /// Set compute units
641    ///
642    /// Only use for tests and benchmarks
643    pub fn mock_set_remaining(&self, remaining: u64) {
644        *self.compute_meter.borrow_mut() = remaining;
645    }
646
647    /// Get this invocation's compute budget
648    pub fn get_compute_budget(&self) -> &ComputeBudget {
649        &self.compute_budget
650    }
651
652    /// Get the current feature set.
653    pub fn get_feature_set(&self) -> &FeatureSet {
654        &self.environment_config.feature_set
655    }
656
657    /// Set feature set.
658    ///
659    /// Only use for tests and benchmarks.
660    pub fn mock_set_feature_set(&mut self, feature_set: Arc<FeatureSet>) {
661        self.environment_config.feature_set = feature_set;
662    }
663
664    /// Get cached sysvars
665    pub fn get_sysvar_cache(&self) -> &SysvarCache {
666        self.environment_config.sysvar_cache
667    }
668
669    /// Get cached epoch total stake.
670    pub fn get_epoch_total_stake(&self) -> u64 {
671        self.environment_config.epoch_total_stake
672    }
673
674    /// Get cached stake for the epoch vote account.
675    pub fn get_epoch_vote_account_stake(&self, pubkey: &'a Pubkey) -> u64 {
676        (self
677            .environment_config
678            .get_epoch_vote_account_stake_callback)(pubkey)
679    }
680
681    // Should alignment be enforced during user pointer translation
682    pub fn get_check_aligned(&self) -> bool {
683        self.transaction_context
684            .get_current_instruction_context()
685            .and_then(|instruction_context| {
686                let program_account =
687                    instruction_context.try_borrow_last_program_account(self.transaction_context);
688                debug_assert!(program_account.is_ok());
689                program_account
690            })
691            .map(|program_account| *program_account.get_owner() != bpf_loader_deprecated::id())
692            .unwrap_or(true)
693    }
694
695    // Set this instruction syscall context
696    pub fn set_syscall_context(
697        &mut self,
698        syscall_context: SyscallContext,
699    ) -> Result<(), InstructionError> {
700        *self
701            .syscall_context
702            .last_mut()
703            .ok_or(InstructionError::CallDepth)? = Some(syscall_context);
704        Ok(())
705    }
706
707    // Get this instruction's SyscallContext
708    pub fn get_syscall_context(&self) -> Result<&SyscallContext, InstructionError> {
709        self.syscall_context
710            .last()
711            .and_then(std::option::Option::as_ref)
712            .ok_or(InstructionError::CallDepth)
713    }
714
715    // Get this instruction's SyscallContext
716    pub fn get_syscall_context_mut(&mut self) -> Result<&mut SyscallContext, InstructionError> {
717        self.syscall_context
718            .last_mut()
719            .and_then(|syscall_context| syscall_context.as_mut())
720            .ok_or(InstructionError::CallDepth)
721    }
722
723    /// Return a references to traces
724    pub fn get_traces(&self) -> &Vec<Vec<[u64; 12]>> {
725        &self.traces
726    }
727}
728
729#[macro_export]
730macro_rules! with_mock_invoke_context {
731    (
732        $invoke_context:ident,
733        $transaction_context:ident,
734        $transaction_accounts:expr $(,)?
735    ) => {
736        use rialo_s_compute_budget::compute_budget::ComputeBudget;
737        use rialo_s_feature_set::FeatureSet;
738        use rialo_s_log_collector::LogCollector;
739        use rialo_s_type_overrides::sync::Arc;
740        use $crate::{
741            __private::{Hash, ReadableAccount, Rent, TransactionContext},
742            invoke_context::{EnvironmentConfig, InvokeContext},
743            loaded_programs::ProgramCacheForTxBatch,
744            sysvar_cache::SysvarCache,
745        };
746        let compute_budget = ComputeBudget::default();
747        let mut $transaction_context = TransactionContext::new(
748            $transaction_accounts,
749            Rent::default(),
750            compute_budget.max_instruction_stack_depth,
751            compute_budget.max_instruction_trace_length,
752        );
753        let mut sysvar_cache = SysvarCache::default();
754        sysvar_cache.fill_missing_entries(|pubkey, callback| {
755            for index in 0..$transaction_context.get_number_of_accounts() {
756                if $transaction_context
757                    .get_key_of_account_at_index(index)
758                    .unwrap()
759                    == pubkey
760                {
761                    callback(
762                        $transaction_context
763                            .get_account_at_index(index)
764                            .unwrap()
765                            .borrow()
766                            .data(),
767                    );
768                }
769            }
770        });
771        let environment_config = EnvironmentConfig::new(
772            Hash::default(),
773            0,
774            0,
775            &|_| 0,
776            Arc::new(FeatureSet::all_enabled()),
777            &sysvar_cache,
778        );
779        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
780        let mut $invoke_context = InvokeContext::new(
781            &mut $transaction_context,
782            &mut program_cache_for_tx_batch,
783            environment_config,
784            Some(LogCollector::new_ref()),
785            compute_budget,
786        );
787    };
788}
789
790pub fn mock_process_instruction<F: FnMut(&mut InvokeContext), G: FnMut(&mut InvokeContext)>(
791    loader_id: &Pubkey,
792    mut program_indices: Vec<IndexOfAccount>,
793    instruction_data: &[u8],
794    mut transaction_accounts: Vec<TransactionAccount>,
795    instruction_account_metas: Vec<AccountMeta>,
796    expected_result: Result<(), InstructionError>,
797    builtin_function: BuiltinFunctionWithContext,
798    mut pre_adjustments: F,
799    mut post_adjustments: G,
800) -> Vec<AccountSharedData> {
801    let mut instruction_accounts: Vec<InstructionAccount> =
802        Vec::with_capacity(instruction_account_metas.len());
803    for (instruction_account_index, account_meta) in instruction_account_metas.iter().enumerate() {
804        let index_in_transaction = transaction_accounts
805            .iter()
806            .position(|(key, _account)| *key == account_meta.pubkey)
807            .unwrap_or(transaction_accounts.len())
808            as IndexOfAccount;
809        let index_in_callee = instruction_accounts
810            .get(0..instruction_account_index)
811            .unwrap()
812            .iter()
813            .position(|instruction_account| {
814                instruction_account.index_in_transaction == index_in_transaction
815            })
816            .unwrap_or(instruction_account_index) as IndexOfAccount;
817        instruction_accounts.push(InstructionAccount {
818            index_in_transaction,
819            index_in_caller: index_in_transaction,
820            index_in_callee,
821            is_signer: account_meta.is_signer,
822            is_writable: account_meta.is_writable,
823        });
824    }
825    if program_indices.is_empty() {
826        program_indices.insert(0, transaction_accounts.len() as IndexOfAccount);
827        let processor_account = AccountSharedData::new(0, 0, &native_loader::id());
828        transaction_accounts.push((*loader_id, processor_account));
829    }
830    let pop_epoch_schedule_account = if !transaction_accounts
831        .iter()
832        .any(|(key, _)| *key == sysvar::epoch_schedule::id())
833    {
834        transaction_accounts.push((
835            sysvar::epoch_schedule::id(),
836            create_account_shared_data_for_test(&EpochSchedule::default()),
837        ));
838        true
839    } else {
840        false
841    };
842    with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
843    let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
844    program_cache_for_tx_batch.replenish(
845        *loader_id,
846        Arc::new(ProgramCacheEntry::new_builtin(0, 0, builtin_function)),
847    );
848    invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
849    pre_adjustments(&mut invoke_context);
850    let result = invoke_context.process_instruction(
851        instruction_data,
852        &instruction_accounts,
853        &program_indices,
854        &mut 0,
855        &mut ExecuteTimings::default(),
856    );
857    assert_eq!(result, expected_result);
858    post_adjustments(&mut invoke_context);
859    let mut transaction_accounts = transaction_context.deconstruct_without_keys().unwrap();
860    if pop_epoch_schedule_account {
861        transaction_accounts.pop();
862    }
863    transaction_accounts.pop();
864    transaction_accounts
865}
866
867#[cfg(test)]
868mod tests {
869    use rialo_s_account::WritableAccount;
870    use rialo_s_compute_budget::compute_budget_limits;
871    use rialo_s_instruction::Instruction;
872    use rialo_s_rent::Rent;
873    use serde::{Deserialize, Serialize};
874
875    use super::*;
876
877    #[derive(Debug, Serialize, Deserialize)]
878    enum MockInstruction {
879        NoopSuccess,
880        NoopFail,
881        ModifyOwned,
882        ModifyNotOwned,
883        ModifyReadonly,
884        UnbalancedPush,
885        UnbalancedPop,
886        ConsumeComputeUnits {
887            compute_units_to_consume: u64,
888            desired_result: Result<(), InstructionError>,
889        },
890        Resize {
891            new_len: u64,
892        },
893    }
894
895    const MOCK_BUILTIN_COMPUTE_UNIT_COST: u64 = 1;
896
897    declare_process_instruction!(
898        MockBuiltin,
899        MOCK_BUILTIN_COMPUTE_UNIT_COST,
900        |invoke_context| {
901            let transaction_context = &invoke_context.transaction_context;
902            let instruction_context = transaction_context.get_current_instruction_context()?;
903            let instruction_data = instruction_context.get_instruction_data();
904            let program_id = instruction_context.get_last_program_key(transaction_context)?;
905            let instruction_accounts = (0..4)
906                .map(|instruction_account_index| InstructionAccount {
907                    index_in_transaction: instruction_account_index,
908                    index_in_caller: instruction_account_index,
909                    index_in_callee: instruction_account_index,
910                    is_signer: false,
911                    is_writable: false,
912                })
913                .collect::<Vec<_>>();
914            assert_eq!(
915                program_id,
916                instruction_context
917                    .try_borrow_instruction_account(transaction_context, 0)?
918                    .get_owner()
919            );
920            assert_ne!(
921                instruction_context
922                    .try_borrow_instruction_account(transaction_context, 1)?
923                    .get_owner(),
924                instruction_context
925                    .try_borrow_instruction_account(transaction_context, 0)?
926                    .get_key()
927            );
928
929            if let Ok(instruction) = bincode::deserialize(instruction_data) {
930                match instruction {
931                    MockInstruction::NoopSuccess => (),
932                    MockInstruction::NoopFail => return Err(InstructionError::GenericError),
933                    MockInstruction::ModifyOwned => instruction_context
934                        .try_borrow_instruction_account(transaction_context, 0)?
935                        .set_data_from_slice(&[1])?,
936                    MockInstruction::ModifyNotOwned => instruction_context
937                        .try_borrow_instruction_account(transaction_context, 1)?
938                        .set_data_from_slice(&[1])?,
939                    MockInstruction::ModifyReadonly => instruction_context
940                        .try_borrow_instruction_account(transaction_context, 2)?
941                        .set_data_from_slice(&[1])?,
942                    MockInstruction::UnbalancedPush => {
943                        instruction_context
944                            .try_borrow_instruction_account(transaction_context, 0)?
945                            .checked_add_lamports(1)?;
946                        let program_id = *transaction_context.get_key_of_account_at_index(3)?;
947                        let metas = vec![
948                            AccountMeta::new_readonly(
949                                *transaction_context.get_key_of_account_at_index(0)?,
950                                false,
951                            ),
952                            AccountMeta::new_readonly(
953                                *transaction_context.get_key_of_account_at_index(1)?,
954                                false,
955                            ),
956                        ];
957                        let inner_instruction = Instruction::new_with_bincode(
958                            program_id,
959                            &MockInstruction::NoopSuccess,
960                            metas,
961                        );
962                        invoke_context
963                            .transaction_context
964                            .get_next_instruction_context()
965                            .unwrap()
966                            .configure(&[3], &instruction_accounts, &[]);
967                        let result = invoke_context.push();
968                        assert_eq!(result, Err(InstructionError::UnbalancedInstruction));
969                        result?;
970                        invoke_context
971                            .native_invoke(inner_instruction.into(), &[])
972                            .and(invoke_context.pop())?;
973                    }
974                    MockInstruction::UnbalancedPop => instruction_context
975                        .try_borrow_instruction_account(transaction_context, 0)?
976                        .checked_add_lamports(1)?,
977                    MockInstruction::ConsumeComputeUnits {
978                        compute_units_to_consume,
979                        desired_result,
980                    } => {
981                        invoke_context
982                            .consume_checked(compute_units_to_consume)
983                            .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
984                        return desired_result;
985                    }
986                    MockInstruction::Resize { new_len } => instruction_context
987                        .try_borrow_instruction_account(transaction_context, 0)?
988                        .set_data(vec![0; new_len as usize])?,
989                }
990            } else {
991                return Err(InstructionError::InvalidInstructionData);
992            }
993            Ok(())
994        }
995    );
996
997    #[test]
998    fn test_instruction_stack_height() {
999        let one_more_than_max_depth = ComputeBudget::default()
1000            .max_instruction_stack_depth
1001            .saturating_add(1);
1002        let mut invoke_stack = vec![];
1003        let mut transaction_accounts = vec![];
1004        let mut instruction_accounts = vec![];
1005        for index in 0..one_more_than_max_depth {
1006            invoke_stack.push(rialo_s_pubkey::new_rand());
1007            transaction_accounts.push((
1008                rialo_s_pubkey::new_rand(),
1009                AccountSharedData::new(index as u64, 1, invoke_stack.get(index).unwrap()),
1010            ));
1011            instruction_accounts.push(InstructionAccount {
1012                index_in_transaction: index as IndexOfAccount,
1013                index_in_caller: index as IndexOfAccount,
1014                index_in_callee: instruction_accounts.len() as IndexOfAccount,
1015                is_signer: false,
1016                is_writable: true,
1017            });
1018        }
1019        for (index, program_id) in invoke_stack.iter().enumerate() {
1020            transaction_accounts.push((
1021                *program_id,
1022                AccountSharedData::new(1, 1, &rialo_s_pubkey::Pubkey::default()),
1023            ));
1024            instruction_accounts.push(InstructionAccount {
1025                index_in_transaction: index as IndexOfAccount,
1026                index_in_caller: index as IndexOfAccount,
1027                index_in_callee: index as IndexOfAccount,
1028                is_signer: false,
1029                is_writable: false,
1030            });
1031        }
1032        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1033
1034        // Check call depth increases and has a limit
1035        let mut depth_reached = 0;
1036        for _ in 0..invoke_stack.len() {
1037            invoke_context
1038                .transaction_context
1039                .get_next_instruction_context()
1040                .unwrap()
1041                .configure(
1042                    &[one_more_than_max_depth.saturating_add(depth_reached) as IndexOfAccount],
1043                    &instruction_accounts,
1044                    &[],
1045                );
1046            if Err(InstructionError::CallDepth) == invoke_context.push() {
1047                break;
1048            }
1049            depth_reached = depth_reached.saturating_add(1);
1050        }
1051        assert_ne!(depth_reached, 0);
1052        assert!(depth_reached < one_more_than_max_depth);
1053    }
1054
1055    #[test]
1056    fn test_max_instruction_trace_length() {
1057        const MAX_INSTRUCTIONS: usize = 8;
1058        let mut transaction_context =
1059            TransactionContext::new(Vec::new(), Rent::default(), 1, MAX_INSTRUCTIONS);
1060        for _ in 0..MAX_INSTRUCTIONS {
1061            transaction_context.push().unwrap();
1062            transaction_context.pop().unwrap();
1063        }
1064        assert_eq!(
1065            transaction_context.push(),
1066            Err(InstructionError::MaxInstructionTraceLengthExceeded)
1067        );
1068    }
1069
1070    #[test]
1071    fn test_process_instruction() {
1072        let callee_program_id = rialo_s_pubkey::new_rand();
1073        let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1074        let not_owned_account = AccountSharedData::new(84, 1, &rialo_s_pubkey::new_rand());
1075        let readonly_account = AccountSharedData::new(168, 1, &rialo_s_pubkey::new_rand());
1076        let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1077        let mut program_account = AccountSharedData::new(1, 1, &native_loader::id());
1078        program_account.set_executable(true);
1079        let transaction_accounts = vec![
1080            (rialo_s_pubkey::new_rand(), owned_account),
1081            (rialo_s_pubkey::new_rand(), not_owned_account),
1082            (rialo_s_pubkey::new_rand(), readonly_account),
1083            (callee_program_id, program_account),
1084            (rialo_s_pubkey::new_rand(), loader_account),
1085        ];
1086        let metas = vec![
1087            AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1088            AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1089            AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1090        ];
1091        let instruction_accounts = (0..4)
1092            .map(|instruction_account_index| InstructionAccount {
1093                index_in_transaction: instruction_account_index,
1094                index_in_caller: instruction_account_index,
1095                index_in_callee: instruction_account_index,
1096                is_signer: false,
1097                is_writable: instruction_account_index < 2,
1098            })
1099            .collect::<Vec<_>>();
1100        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1101        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1102        program_cache_for_tx_batch.replenish(
1103            callee_program_id,
1104            Arc::new(ProgramCacheEntry::new_builtin(0, 1, MockBuiltin::vm)),
1105        );
1106        invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1107
1108        // Account modification tests
1109        let cases = vec![
1110            (MockInstruction::NoopSuccess, Ok(())),
1111            (
1112                MockInstruction::NoopFail,
1113                Err(InstructionError::GenericError),
1114            ),
1115            (MockInstruction::ModifyOwned, Ok(())),
1116            (
1117                MockInstruction::ModifyNotOwned,
1118                Err(InstructionError::ExternalAccountDataModified),
1119            ),
1120            (
1121                MockInstruction::ModifyReadonly,
1122                Err(InstructionError::ReadonlyDataModified),
1123            ),
1124            (
1125                MockInstruction::UnbalancedPush,
1126                Err(InstructionError::UnbalancedInstruction),
1127            ),
1128            (
1129                MockInstruction::UnbalancedPop,
1130                Err(InstructionError::UnbalancedInstruction),
1131            ),
1132        ];
1133        for case in cases {
1134            invoke_context
1135                .transaction_context
1136                .get_next_instruction_context()
1137                .unwrap()
1138                .configure(&[4], &instruction_accounts, &[]);
1139            invoke_context.push().unwrap();
1140            let inner_instruction =
1141                Instruction::new_with_bincode(callee_program_id, &case.0, metas.clone());
1142            let result = invoke_context
1143                .native_invoke(inner_instruction.into(), &[])
1144                .and(invoke_context.pop());
1145            assert_eq!(result, case.1);
1146        }
1147
1148        // Compute unit consumption tests
1149        let compute_units_to_consume = 10;
1150        let expected_results = vec![Ok(()), Err(InstructionError::GenericError)];
1151        for expected_result in expected_results {
1152            invoke_context
1153                .transaction_context
1154                .get_next_instruction_context()
1155                .unwrap()
1156                .configure(&[4], &instruction_accounts, &[]);
1157            invoke_context.push().unwrap();
1158            let inner_instruction = Instruction::new_with_bincode(
1159                callee_program_id,
1160                &MockInstruction::ConsumeComputeUnits {
1161                    compute_units_to_consume,
1162                    desired_result: expected_result.clone(),
1163                },
1164                metas.clone(),
1165            );
1166            let inner_instruction = StableInstruction::from(inner_instruction);
1167            let (inner_instruction_accounts, program_indices) = invoke_context
1168                .prepare_instruction(&inner_instruction, &[])
1169                .unwrap();
1170
1171            let mut compute_units_consumed = 0;
1172            let result = invoke_context.process_instruction(
1173                &inner_instruction.data,
1174                &inner_instruction_accounts,
1175                &program_indices,
1176                &mut compute_units_consumed,
1177                &mut ExecuteTimings::default(),
1178            );
1179
1180            // Because the instruction had compute cost > 0, then regardless of the execution result,
1181            // the number of compute units consumed should be a non-default which is something greater
1182            // than zero.
1183            assert!(compute_units_consumed > 0);
1184            assert_eq!(
1185                compute_units_consumed,
1186                compute_units_to_consume.saturating_add(MOCK_BUILTIN_COMPUTE_UNIT_COST),
1187            );
1188            assert_eq!(result, expected_result);
1189
1190            invoke_context.pop().unwrap();
1191        }
1192    }
1193
1194    #[test]
1195    fn test_invoke_context_compute_budget() {
1196        let transaction_accounts = vec![(rialo_s_pubkey::new_rand(), AccountSharedData::default())];
1197
1198        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1199        invoke_context.compute_budget = ComputeBudget::new(
1200            compute_budget_limits::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64,
1201        );
1202
1203        invoke_context
1204            .transaction_context
1205            .get_next_instruction_context()
1206            .unwrap()
1207            .configure(&[0], &[], &[]);
1208        invoke_context.push().unwrap();
1209        assert_eq!(
1210            *invoke_context.get_compute_budget(),
1211            ComputeBudget::new(
1212                compute_budget_limits::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64
1213            )
1214        );
1215        invoke_context.pop().unwrap();
1216    }
1217
1218    #[test]
1219    fn test_process_instruction_accounts_resize_delta() {
1220        let program_key = Pubkey::new_unique();
1221        let user_account_data_len = 123u64;
1222        let user_account =
1223            AccountSharedData::new(100, user_account_data_len as usize, &program_key);
1224        let dummy_account = AccountSharedData::new(10, 0, &program_key);
1225        let mut program_account = AccountSharedData::new(500, 500, &native_loader::id());
1226        program_account.set_executable(true);
1227        let transaction_accounts = vec![
1228            (Pubkey::new_unique(), user_account),
1229            (Pubkey::new_unique(), dummy_account),
1230            (program_key, program_account),
1231        ];
1232        let instruction_accounts = [
1233            InstructionAccount {
1234                index_in_transaction: 0,
1235                index_in_caller: 0,
1236                index_in_callee: 0,
1237                is_signer: false,
1238                is_writable: true,
1239            },
1240            InstructionAccount {
1241                index_in_transaction: 1,
1242                index_in_caller: 1,
1243                index_in_callee: 1,
1244                is_signer: false,
1245                is_writable: false,
1246            },
1247        ];
1248        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1249        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1250        program_cache_for_tx_batch.replenish(
1251            program_key,
1252            Arc::new(ProgramCacheEntry::new_builtin(0, 0, MockBuiltin::vm)),
1253        );
1254        invoke_context.program_cache_for_tx_batch = &mut program_cache_for_tx_batch;
1255
1256        // Test: Resize the account to *the same size*, so not consuming any additional size; this must succeed
1257        {
1258            let resize_delta: i64 = 0;
1259            let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
1260            let instruction_data =
1261                bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1262
1263            let result = invoke_context.process_instruction(
1264                &instruction_data,
1265                &instruction_accounts,
1266                &[2],
1267                &mut 0,
1268                &mut ExecuteTimings::default(),
1269            );
1270
1271            assert!(result.is_ok());
1272            assert_eq!(
1273                invoke_context
1274                    .transaction_context
1275                    .accounts_resize_delta()
1276                    .unwrap(),
1277                resize_delta
1278            );
1279        }
1280
1281        // Test: Resize the account larger; this must succeed
1282        {
1283            let resize_delta: i64 = 1;
1284            let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
1285            let instruction_data =
1286                bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1287
1288            let result = invoke_context.process_instruction(
1289                &instruction_data,
1290                &instruction_accounts,
1291                &[2],
1292                &mut 0,
1293                &mut ExecuteTimings::default(),
1294            );
1295
1296            assert!(result.is_ok());
1297            assert_eq!(
1298                invoke_context
1299                    .transaction_context
1300                    .accounts_resize_delta()
1301                    .unwrap(),
1302                resize_delta
1303            );
1304        }
1305
1306        // Test: Resize the account smaller; this must succeed
1307        {
1308            let resize_delta: i64 = -1;
1309            let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
1310            let instruction_data =
1311                bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1312
1313            let result = invoke_context.process_instruction(
1314                &instruction_data,
1315                &instruction_accounts,
1316                &[2],
1317                &mut 0,
1318                &mut ExecuteTimings::default(),
1319            );
1320
1321            assert!(result.is_ok());
1322            assert_eq!(
1323                invoke_context
1324                    .transaction_context
1325                    .accounts_resize_delta()
1326                    .unwrap(),
1327                resize_delta
1328            );
1329        }
1330    }
1331}