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