miraland_program_runtime/
invoke_context.rs

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