solana_program_runtime/
invoke_context.rs

1use {
2    crate::{
3        accounts_data_meter::AccountsDataMeter,
4        compute_budget::ComputeBudget,
5        executor_cache::TransactionExecutorCache,
6        ic_logger_msg, ic_msg,
7        log_collector::LogCollector,
8        pre_account::PreAccount,
9        stable_log,
10        sysvar_cache::SysvarCache,
11        timings::{ExecuteDetailsTimings, ExecuteTimings},
12    },
13    solana_measure::measure::Measure,
14    solana_rbpf::vm::ContextObject,
15    solana_sdk::{
16        account::{AccountSharedData, ReadableAccount},
17        bpf_loader_upgradeable::{self, UpgradeableLoaderState},
18        feature_set::{enable_early_verification_of_account_modifications, FeatureSet},
19        hash::Hash,
20        instruction::{AccountMeta, InstructionError},
21        native_loader,
22        pubkey::Pubkey,
23        rent::Rent,
24        saturating_add_assign,
25        stable_layout::stable_instruction::StableInstruction,
26        transaction_context::{
27            IndexOfAccount, InstructionAccount, TransactionAccount, TransactionContext,
28        },
29    },
30    std::{
31        alloc::Layout,
32        borrow::Cow,
33        cell::RefCell,
34        fmt::{self, Debug},
35        rc::Rc,
36        sync::Arc,
37    },
38};
39
40pub type ProcessInstructionWithContext =
41    fn(IndexOfAccount, &mut InvokeContext) -> Result<(), InstructionError>;
42
43#[derive(Clone)]
44pub struct BuiltinProgram {
45    pub program_id: Pubkey,
46    pub process_instruction: ProcessInstructionWithContext,
47}
48
49impl std::fmt::Debug for BuiltinProgram {
50    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
51        // These are just type aliases for work around of Debug-ing above pointers
52        type ErasedProcessInstructionWithContext =
53            fn(IndexOfAccount, &'static mut InvokeContext<'static>) -> Result<(), InstructionError>;
54
55        // rustc doesn't compile due to bug without this work around
56        // https://github.com/rust-lang/rust/issues/50280
57        // https://users.rust-lang.org/t/display-function-pointer/17073/2
58        let erased_instruction: ErasedProcessInstructionWithContext = self.process_instruction;
59        write!(f, "{}: {:p}", self.program_id, erased_instruction)
60    }
61}
62
63impl<'a> ContextObject for InvokeContext<'a> {
64    fn trace(&mut self, state: [u64; 12]) {
65        self.trace_log_stack
66            .last_mut()
67            .expect("Inconsistent trace log stack")
68            .trace_log
69            .push(state);
70    }
71
72    fn consume(&mut self, amount: u64) {
73        self.log_consumed_bpf_units(amount);
74        // 1 to 1 instruction to compute unit mapping
75        // ignore overflow, Ebpf will bail if exceeded
76        let mut compute_meter = self.compute_meter.borrow_mut();
77        *compute_meter = compute_meter.saturating_sub(amount);
78    }
79
80    fn get_remaining(&self) -> u64 {
81        *self.compute_meter.borrow()
82    }
83}
84
85/// Based loosely on the unstable std::alloc::Alloc trait
86pub trait Alloc {
87    fn alloc(&mut self, layout: Layout) -> Result<u64, AllocErr>;
88    fn dealloc(&mut self, addr: u64, layout: Layout);
89}
90
91#[derive(Clone, PartialEq, Eq, Debug)]
92pub struct AllocErr;
93
94impl fmt::Display for AllocErr {
95    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
96        f.write_str("Error: Memory allocation failed")
97    }
98}
99
100struct SyscallContext {
101    check_aligned: bool,
102    check_size: bool,
103    orig_account_lengths: Vec<usize>,
104    allocator: Rc<RefCell<dyn Alloc>>,
105}
106
107#[derive(Default)]
108pub struct TraceLogStackFrame {
109    pub trace_log: Vec<[u64; 12]>,
110    pub consumed_bpf_units: RefCell<Vec<(usize, u64)>>,
111}
112
113pub struct InvokeContext<'a> {
114    pub transaction_context: &'a mut TransactionContext,
115    rent: Rent,
116    pre_accounts: Vec<PreAccount>,
117    builtin_programs: &'a [BuiltinProgram],
118    pub sysvar_cache: Cow<'a, SysvarCache>,
119    pub trace_log_stack: Vec<TraceLogStackFrame>,
120    log_collector: Option<Rc<RefCell<LogCollector>>>,
121    compute_budget: ComputeBudget,
122    current_compute_budget: ComputeBudget,
123    compute_meter: RefCell<u64>,
124    accounts_data_meter: AccountsDataMeter,
125    pub tx_executor_cache: Rc<RefCell<TransactionExecutorCache>>,
126    pub feature_set: Arc<FeatureSet>,
127    pub timings: ExecuteDetailsTimings,
128    pub blockhash: Hash,
129    pub lamports_per_signature: u64,
130    syscall_context: Vec<Option<SyscallContext>>,
131    pub enable_instruction_tracing: bool,
132}
133
134impl<'a> InvokeContext<'a> {
135    #[allow(clippy::too_many_arguments)]
136    pub fn new(
137        transaction_context: &'a mut TransactionContext,
138        rent: Rent,
139        builtin_programs: &'a [BuiltinProgram],
140        sysvar_cache: Cow<'a, SysvarCache>,
141        log_collector: Option<Rc<RefCell<LogCollector>>>,
142        compute_budget: ComputeBudget,
143        tx_executor_cache: Rc<RefCell<TransactionExecutorCache>>,
144        feature_set: Arc<FeatureSet>,
145        blockhash: Hash,
146        lamports_per_signature: u64,
147        prev_accounts_data_len: u64,
148    ) -> Self {
149        Self {
150            transaction_context,
151            rent,
152            pre_accounts: Vec::new(),
153            builtin_programs,
154            sysvar_cache,
155            trace_log_stack: vec![TraceLogStackFrame::default()],
156            log_collector,
157            current_compute_budget: compute_budget,
158            compute_budget,
159            compute_meter: RefCell::new(compute_budget.compute_unit_limit),
160            accounts_data_meter: AccountsDataMeter::new(prev_accounts_data_len),
161            tx_executor_cache,
162            feature_set,
163            timings: ExecuteDetailsTimings::default(),
164            blockhash,
165            lamports_per_signature,
166            syscall_context: Vec::new(),
167            enable_instruction_tracing: false,
168        }
169    }
170
171    pub fn new_mock(
172        transaction_context: &'a mut TransactionContext,
173        builtin_programs: &'a [BuiltinProgram],
174    ) -> Self {
175        let mut sysvar_cache = SysvarCache::default();
176        sysvar_cache.fill_missing_entries(|pubkey, callback| {
177            for index in 0..transaction_context.get_number_of_accounts() {
178                if transaction_context
179                    .get_key_of_account_at_index(index)
180                    .unwrap()
181                    == pubkey
182                {
183                    callback(
184                        transaction_context
185                            .get_account_at_index(index)
186                            .unwrap()
187                            .borrow()
188                            .data(),
189                    );
190                }
191            }
192        });
193        Self::new(
194            transaction_context,
195            Rent::default(),
196            builtin_programs,
197            Cow::Owned(sysvar_cache),
198            Some(LogCollector::new_ref()),
199            ComputeBudget::default(),
200            Rc::new(RefCell::new(TransactionExecutorCache::default())),
201            Arc::new(FeatureSet::all_enabled()),
202            Hash::default(),
203            0,
204            0,
205        )
206    }
207
208    /// Push a stack frame onto the invocation stack
209    pub fn push(&mut self) -> Result<(), InstructionError> {
210        let instruction_context = self
211            .transaction_context
212            .get_instruction_context_at_index_in_trace(
213                self.transaction_context.get_instruction_trace_length(),
214            )?;
215        let program_id = instruction_context
216            .get_last_program_key(self.transaction_context)
217            .map_err(|_| InstructionError::UnsupportedProgramId)?;
218        if self
219            .transaction_context
220            .get_instruction_context_stack_height()
221            == 0
222        {
223            self.current_compute_budget = self.compute_budget;
224
225            if !self
226                .feature_set
227                .is_active(&enable_early_verification_of_account_modifications::id())
228            {
229                self.pre_accounts = Vec::with_capacity(
230                    instruction_context.get_number_of_instruction_accounts() as usize,
231                );
232                for instruction_account_index in
233                    0..instruction_context.get_number_of_instruction_accounts()
234                {
235                    if instruction_context
236                        .is_instruction_account_duplicate(instruction_account_index)?
237                        .is_some()
238                    {
239                        continue; // Skip duplicate account
240                    }
241                    let index_in_transaction = instruction_context
242                        .get_index_of_instruction_account_in_transaction(
243                            instruction_account_index,
244                        )?;
245                    if index_in_transaction >= self.transaction_context.get_number_of_accounts() {
246                        return Err(InstructionError::MissingAccount);
247                    }
248                    let account = self
249                        .transaction_context
250                        .get_account_at_index(index_in_transaction)?
251                        .borrow()
252                        .clone();
253                    self.pre_accounts.push(PreAccount::new(
254                        self.transaction_context
255                            .get_key_of_account_at_index(index_in_transaction)?,
256                        account,
257                    ));
258                }
259            }
260        } else {
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.trace_log_stack.push(TraceLogStackFrame::default());
289        self.syscall_context.push(None);
290        self.transaction_context.push()
291    }
292
293    /// Pop a stack frame from the invocation stack
294    pub fn pop(&mut self) -> Result<(), InstructionError> {
295        self.trace_log_stack.pop();
296        self.syscall_context.pop();
297        self.transaction_context.pop()
298    }
299
300    /// Current height of the invocation stack, top level instructions are height
301    /// `solana_sdk::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    /// Verify the results of an instruction
308    ///
309    /// Note: `instruction_accounts` must be the same as passed to `InvokeContext::push()`,
310    /// so that they match the order of `pre_accounts`.
311    fn verify(
312        &mut self,
313        instruction_accounts: &[InstructionAccount],
314        program_indices: &[IndexOfAccount],
315    ) -> Result<(), InstructionError> {
316        let instruction_context = self
317            .transaction_context
318            .get_current_instruction_context()
319            .map_err(|_| InstructionError::CallDepth)?;
320        let program_id = instruction_context
321            .get_last_program_key(self.transaction_context)
322            .map_err(|_| InstructionError::CallDepth)?;
323
324        // Verify all executable accounts have zero outstanding refs
325        for account_index in program_indices.iter() {
326            self.transaction_context
327                .get_account_at_index(*account_index)?
328                .try_borrow_mut()
329                .map_err(|_| InstructionError::AccountBorrowOutstanding)?;
330        }
331
332        // Verify the per-account instruction results
333        let (mut pre_sum, mut post_sum) = (0_u128, 0_u128);
334        let mut pre_account_index = 0;
335        for (instruction_account_index, instruction_account) in
336            instruction_accounts.iter().enumerate()
337        {
338            if instruction_account_index as IndexOfAccount != instruction_account.index_in_callee {
339                continue; // Skip duplicate account
340            }
341            {
342                // Verify account has no outstanding references
343                let _ = self
344                    .transaction_context
345                    .get_account_at_index(instruction_account.index_in_transaction)?
346                    .try_borrow_mut()
347                    .map_err(|_| InstructionError::AccountBorrowOutstanding)?;
348            }
349            let pre_account = &self
350                .pre_accounts
351                .get(pre_account_index)
352                .ok_or(InstructionError::NotEnoughAccountKeys)?;
353            pre_account_index = pre_account_index.saturating_add(1);
354            let account = self
355                .transaction_context
356                .get_account_at_index(instruction_account.index_in_transaction)?
357                .borrow();
358            pre_account
359                .verify(
360                    program_id,
361                    instruction_account.is_writable,
362                    &self.rent,
363                    &account,
364                    &mut self.timings,
365                    true,
366                )
367                .map_err(|err| {
368                    ic_logger_msg!(
369                        self.log_collector,
370                        "failed to verify account {}: {}",
371                        pre_account.key(),
372                        err
373                    );
374                    err
375                })?;
376            pre_sum = pre_sum
377                .checked_add(u128::from(pre_account.lamports()))
378                .ok_or(InstructionError::UnbalancedInstruction)?;
379            post_sum = post_sum
380                .checked_add(u128::from(account.lamports()))
381                .ok_or(InstructionError::UnbalancedInstruction)?;
382
383            let pre_data_len = pre_account.data().len() as i64;
384            let post_data_len = account.data().len() as i64;
385            let data_len_delta = post_data_len.saturating_sub(pre_data_len);
386            self.accounts_data_meter
387                .adjust_delta_unchecked(data_len_delta);
388        }
389
390        // Verify that the total sum of all the lamports did not change
391        if pre_sum != post_sum {
392            return Err(InstructionError::UnbalancedInstruction);
393        }
394        Ok(())
395    }
396
397    /// Verify and update PreAccount state based on program execution
398    ///
399    /// Note: `instruction_accounts` must be the same as passed to `InvokeContext::push()`,
400    /// so that they match the order of `pre_accounts`.
401    fn verify_and_update(
402        &mut self,
403        instruction_accounts: &[InstructionAccount],
404        before_instruction_context_push: bool,
405    ) -> Result<(), InstructionError> {
406        let transaction_context = &self.transaction_context;
407        let instruction_context = transaction_context.get_current_instruction_context()?;
408        let program_id = instruction_context
409            .get_last_program_key(transaction_context)
410            .map_err(|_| InstructionError::CallDepth)?;
411
412        // Verify the per-account instruction results
413        let (mut pre_sum, mut post_sum) = (0_u128, 0_u128);
414        for (instruction_account_index, instruction_account) in
415            instruction_accounts.iter().enumerate()
416        {
417            if instruction_account_index as IndexOfAccount != instruction_account.index_in_callee {
418                continue; // Skip duplicate account
419            }
420            if instruction_account.index_in_transaction
421                < transaction_context.get_number_of_accounts()
422            {
423                let key = transaction_context
424                    .get_key_of_account_at_index(instruction_account.index_in_transaction)?;
425                let account = transaction_context
426                    .get_account_at_index(instruction_account.index_in_transaction)?;
427                let is_writable = if before_instruction_context_push {
428                    instruction_context
429                        .is_instruction_account_writable(instruction_account.index_in_caller)?
430                } else {
431                    instruction_account.is_writable
432                };
433                // Find the matching PreAccount
434                for pre_account in self.pre_accounts.iter_mut() {
435                    if key == pre_account.key() {
436                        {
437                            // Verify account has no outstanding references
438                            let _ = account
439                                .try_borrow_mut()
440                                .map_err(|_| InstructionError::AccountBorrowOutstanding)?;
441                        }
442                        let account = account.borrow();
443                        pre_account
444                            .verify(
445                                program_id,
446                                is_writable,
447                                &self.rent,
448                                &account,
449                                &mut self.timings,
450                                false,
451                            )
452                            .map_err(|err| {
453                                ic_logger_msg!(
454                                    self.log_collector,
455                                    "failed to verify account {}: {}",
456                                    key,
457                                    err
458                                );
459                                err
460                            })?;
461                        pre_sum = pre_sum
462                            .checked_add(u128::from(pre_account.lamports()))
463                            .ok_or(InstructionError::UnbalancedInstruction)?;
464                        post_sum = post_sum
465                            .checked_add(u128::from(account.lamports()))
466                            .ok_or(InstructionError::UnbalancedInstruction)?;
467                        if is_writable && !pre_account.executable() {
468                            pre_account.update(account.clone());
469                        }
470
471                        let pre_data_len = pre_account.data().len() as i64;
472                        let post_data_len = account.data().len() as i64;
473                        let data_len_delta = post_data_len.saturating_sub(pre_data_len);
474                        self.accounts_data_meter
475                            .adjust_delta_unchecked(data_len_delta);
476
477                        break;
478                    }
479                }
480            }
481        }
482
483        // Verify that the total sum of all the lamports did not change
484        if pre_sum != post_sum {
485            return Err(InstructionError::UnbalancedInstruction);
486        }
487        Ok(())
488    }
489
490    /// Entrypoint for a cross-program invocation from a builtin program
491    pub fn native_invoke(
492        &mut self,
493        instruction: StableInstruction,
494        signers: &[Pubkey],
495    ) -> Result<(), InstructionError> {
496        let (instruction_accounts, program_indices) =
497            self.prepare_instruction(&instruction, signers)?;
498        let mut compute_units_consumed = 0;
499        self.process_instruction(
500            &instruction.data,
501            &instruction_accounts,
502            &program_indices,
503            &mut compute_units_consumed,
504            &mut ExecuteTimings::default(),
505        )?;
506        Ok(())
507    }
508
509    /// Helper to prepare for process_instruction()
510    #[allow(clippy::type_complexity)]
511    pub fn prepare_instruction(
512        &mut self,
513        instruction: &StableInstruction,
514        signers: &[Pubkey],
515    ) -> Result<(Vec<InstructionAccount>, Vec<IndexOfAccount>), InstructionError> {
516        // Finds the index of each account in the instruction by its pubkey.
517        // Then normalizes / unifies the privileges of duplicate accounts.
518        // Note: This is an O(n^2) algorithm,
519        // but performed on a very small slice and requires no heap allocations.
520        let instruction_context = self.transaction_context.get_current_instruction_context()?;
521        let mut deduplicated_instruction_accounts: Vec<InstructionAccount> = Vec::new();
522        let mut duplicate_indicies = Vec::with_capacity(instruction.accounts.len());
523        for (instruction_account_index, account_meta) in instruction.accounts.iter().enumerate() {
524            let index_in_transaction = self
525                .transaction_context
526                .find_index_of_account(&account_meta.pubkey)
527                .ok_or_else(|| {
528                    ic_msg!(
529                        self,
530                        "Instruction references an unknown account {}",
531                        account_meta.pubkey,
532                    );
533                    InstructionError::MissingAccount
534                })?;
535            if let Some(duplicate_index) =
536                deduplicated_instruction_accounts
537                    .iter()
538                    .position(|instruction_account| {
539                        instruction_account.index_in_transaction == index_in_transaction
540                    })
541            {
542                duplicate_indicies.push(duplicate_index);
543                let instruction_account = deduplicated_instruction_accounts
544                    .get_mut(duplicate_index)
545                    .ok_or(InstructionError::NotEnoughAccountKeys)?;
546                instruction_account.is_signer |= account_meta.is_signer;
547                instruction_account.is_writable |= account_meta.is_writable;
548            } else {
549                let index_in_caller = instruction_context
550                    .find_index_of_instruction_account(
551                        self.transaction_context,
552                        &account_meta.pubkey,
553                    )
554                    .ok_or_else(|| {
555                        ic_msg!(
556                            self,
557                            "Instruction references an unknown account {}",
558                            account_meta.pubkey,
559                        );
560                        InstructionError::MissingAccount
561                    })?;
562                duplicate_indicies.push(deduplicated_instruction_accounts.len());
563                deduplicated_instruction_accounts.push(InstructionAccount {
564                    index_in_transaction,
565                    index_in_caller,
566                    index_in_callee: instruction_account_index as IndexOfAccount,
567                    is_signer: account_meta.is_signer,
568                    is_writable: account_meta.is_writable,
569                });
570            }
571        }
572        for instruction_account in deduplicated_instruction_accounts.iter() {
573            let borrowed_account = instruction_context.try_borrow_instruction_account(
574                self.transaction_context,
575                instruction_account.index_in_caller,
576            )?;
577
578            // Readonly in caller cannot become writable in callee
579            if instruction_account.is_writable && !borrowed_account.is_writable() {
580                ic_msg!(
581                    self,
582                    "{}'s writable privilege escalated",
583                    borrowed_account.get_key(),
584                );
585                return Err(InstructionError::PrivilegeEscalation);
586            }
587
588            // To be signed in the callee,
589            // it must be either signed in the caller or by the program
590            if instruction_account.is_signer
591                && !(borrowed_account.is_signer() || signers.contains(borrowed_account.get_key()))
592            {
593                ic_msg!(
594                    self,
595                    "{}'s signer privilege escalated",
596                    borrowed_account.get_key()
597                );
598                return Err(InstructionError::PrivilegeEscalation);
599            }
600        }
601        let instruction_accounts = duplicate_indicies
602            .into_iter()
603            .map(|duplicate_index| {
604                Ok(deduplicated_instruction_accounts
605                    .get(duplicate_index)
606                    .ok_or(InstructionError::NotEnoughAccountKeys)?
607                    .clone())
608            })
609            .collect::<Result<Vec<InstructionAccount>, InstructionError>>()?;
610
611        // Find and validate executables / program accounts
612        let callee_program_id = instruction.program_id;
613        let program_account_index = instruction_context
614            .find_index_of_instruction_account(self.transaction_context, &callee_program_id)
615            .ok_or_else(|| {
616                ic_msg!(self, "Unknown program {}", callee_program_id);
617                InstructionError::MissingAccount
618            })?;
619        let borrowed_program_account = instruction_context
620            .try_borrow_instruction_account(self.transaction_context, program_account_index)?;
621        if !borrowed_program_account.is_executable() {
622            ic_msg!(self, "Account {} is not executable", callee_program_id);
623            return Err(InstructionError::AccountNotExecutable);
624        }
625        let mut program_indices = vec![];
626        if borrowed_program_account.get_owner() == &bpf_loader_upgradeable::id() {
627            if let UpgradeableLoaderState::Program {
628                programdata_address,
629            } = borrowed_program_account.get_state()?
630            {
631                if let Some(programdata_account_index) = self
632                    .transaction_context
633                    .find_index_of_program_account(&programdata_address)
634                {
635                    program_indices.push(programdata_account_index);
636                } else {
637                    ic_msg!(
638                        self,
639                        "Unknown upgradeable programdata account {}",
640                        programdata_address,
641                    );
642                    return Err(InstructionError::MissingAccount);
643                }
644            } else {
645                ic_msg!(
646                    self,
647                    "Invalid upgradeable program account {}",
648                    callee_program_id,
649                );
650                return Err(InstructionError::MissingAccount);
651            }
652        }
653        program_indices.push(borrowed_program_account.get_index_in_transaction());
654
655        Ok((instruction_accounts, program_indices))
656    }
657
658    /// Processes an instruction and returns how many compute units were used
659    pub fn process_instruction(
660        &mut self,
661        instruction_data: &[u8],
662        instruction_accounts: &[InstructionAccount],
663        program_indices: &[IndexOfAccount],
664        compute_units_consumed: &mut u64,
665        timings: &mut ExecuteTimings,
666    ) -> Result<(), InstructionError> {
667        *compute_units_consumed = 0;
668
669        let nesting_level = self
670            .transaction_context
671            .get_instruction_context_stack_height();
672        let is_top_level_instruction = nesting_level == 0;
673        if !is_top_level_instruction
674            && !self
675                .feature_set
676                .is_active(&enable_early_verification_of_account_modifications::id())
677        {
678            // Verify the calling program hasn't misbehaved
679            let mut verify_caller_time = Measure::start("verify_caller_time");
680            let verify_caller_result = self.verify_and_update(instruction_accounts, true);
681            verify_caller_time.stop();
682            saturating_add_assign!(
683                timings
684                    .execute_accessories
685                    .process_instructions
686                    .verify_caller_us,
687                verify_caller_time.as_us()
688            );
689            verify_caller_result?;
690        }
691
692        self.transaction_context
693            .get_next_instruction_context()?
694            .configure(program_indices, instruction_accounts, instruction_data);
695        self.push()?;
696        self.process_executable_chain(compute_units_consumed, timings)
697            .and_then(|_| {
698                if self
699                    .feature_set
700                    .is_active(&enable_early_verification_of_account_modifications::id())
701                {
702                    Ok(())
703                } else {
704                    // Verify the called program has not misbehaved
705                    let mut verify_callee_time = Measure::start("verify_callee_time");
706                    let result = if is_top_level_instruction {
707                        self.verify(instruction_accounts, program_indices)
708                    } else {
709                        self.verify_and_update(instruction_accounts, false)
710                    };
711                    verify_callee_time.stop();
712                    saturating_add_assign!(
713                        timings
714                            .execute_accessories
715                            .process_instructions
716                            .verify_callee_us,
717                        verify_callee_time.as_us()
718                    );
719                    result
720                }
721            })
722            // MUST pop if and only if `push` succeeded, independent of `result`.
723            // Thus, the `.and()` instead of an `.and_then()`.
724            .and(self.pop())
725    }
726
727    /// Calls the instruction's program entrypoint method
728    fn process_executable_chain(
729        &mut self,
730        compute_units_consumed: &mut u64,
731        timings: &mut ExecuteTimings,
732    ) -> Result<(), InstructionError> {
733        let instruction_context = self.transaction_context.get_current_instruction_context()?;
734        let mut process_executable_chain_time = Measure::start("process_executable_chain_time");
735
736        let (first_instruction_account, builtin_id) = {
737            let borrowed_root_account = instruction_context
738                .try_borrow_program_account(self.transaction_context, 0)
739                .map_err(|_| InstructionError::UnsupportedProgramId)?;
740            let owner_id = borrowed_root_account.get_owner();
741            if native_loader::check_id(owner_id) {
742                (1, *borrowed_root_account.get_key())
743            } else {
744                (0, *owner_id)
745            }
746        };
747
748        for entry in self.builtin_programs {
749            if entry.program_id == builtin_id {
750                let program_id =
751                    *instruction_context.get_last_program_key(self.transaction_context)?;
752                self.transaction_context
753                    .set_return_data(program_id, Vec::new())?;
754
755                let pre_remaining_units = self.get_remaining();
756                let result = if builtin_id == program_id {
757                    let logger = self.get_log_collector();
758                    stable_log::program_invoke(&logger, &program_id, self.get_stack_height());
759                    (entry.process_instruction)(first_instruction_account, self)
760                        .map(|()| {
761                            stable_log::program_success(&logger, &program_id);
762                        })
763                        .map_err(|err| {
764                            stable_log::program_failure(&logger, &program_id, &err);
765                            err
766                        })
767                } else {
768                    (entry.process_instruction)(first_instruction_account, self)
769                };
770                let post_remaining_units = self.get_remaining();
771                *compute_units_consumed = pre_remaining_units.saturating_sub(post_remaining_units);
772
773                process_executable_chain_time.stop();
774                saturating_add_assign!(
775                    timings
776                        .execute_accessories
777                        .process_instructions
778                        .process_executable_chain_us,
779                    process_executable_chain_time.as_us()
780                );
781                return result;
782            }
783        }
784
785        Err(InstructionError::UnsupportedProgramId)
786    }
787
788    /// Get this invocation's LogCollector
789    pub fn get_log_collector(&self) -> Option<Rc<RefCell<LogCollector>>> {
790        self.log_collector.clone()
791    }
792
793    /// Consume compute units
794    pub fn consume_checked(&self, amount: u64) -> Result<(), InstructionError> {
795        self.log_consumed_bpf_units(amount);
796        let mut compute_meter = self.compute_meter.borrow_mut();
797        let exceeded = *compute_meter < amount;
798        *compute_meter = compute_meter.saturating_sub(amount);
799        if exceeded {
800            return Err(InstructionError::ComputationalBudgetExceeded);
801        }
802        Ok(())
803    }
804
805    /// Set compute units
806    ///
807    /// Only use for tests and benchmarks
808    pub fn mock_set_remaining(&self, remaining: u64) {
809        *self.compute_meter.borrow_mut() = remaining;
810    }
811
812    /// Get this invocation's AccountsDataMeter
813    pub fn get_accounts_data_meter(&self) -> &AccountsDataMeter {
814        &self.accounts_data_meter
815    }
816
817    /// Get this invocation's compute budget
818    pub fn get_compute_budget(&self) -> &ComputeBudget {
819        &self.current_compute_budget
820    }
821
822    /// Get cached sysvars
823    pub fn get_sysvar_cache(&self) -> &SysvarCache {
824        &self.sysvar_cache
825    }
826
827    // Set this instruction syscall context
828    pub fn set_syscall_context(
829        &mut self,
830        check_aligned: bool,
831        check_size: bool,
832        orig_account_lengths: Vec<usize>,
833        allocator: Rc<RefCell<dyn Alloc>>,
834    ) -> Result<(), InstructionError> {
835        *self
836            .syscall_context
837            .last_mut()
838            .ok_or(InstructionError::CallDepth)? = Some(SyscallContext {
839            check_aligned,
840            check_size,
841            orig_account_lengths,
842            allocator,
843        });
844        Ok(())
845    }
846
847    // Should alignment be enforced during user pointer translation
848    pub fn get_check_aligned(&self) -> bool {
849        self.syscall_context
850            .last()
851            .and_then(|context| context.as_ref())
852            .map(|context| context.check_aligned)
853            .unwrap_or(true)
854    }
855
856    // Set should type size be checked during user pointer translation
857    pub fn get_check_size(&self) -> bool {
858        self.syscall_context
859            .last()
860            .and_then(|context| context.as_ref())
861            .map(|context| context.check_size)
862            .unwrap_or(true)
863    }
864
865    /// Get the original account lengths
866    pub fn get_orig_account_lengths(&self) -> Result<&[usize], InstructionError> {
867        self.syscall_context
868            .last()
869            .and_then(|context| context.as_ref())
870            .map(|context| context.orig_account_lengths.as_slice())
871            .ok_or(InstructionError::CallDepth)
872    }
873
874    // Get this instruction's memory allocator
875    pub fn get_allocator(&self) -> Result<Rc<RefCell<dyn Alloc>>, InstructionError> {
876        self.syscall_context
877            .last()
878            .and_then(|context| context.as_ref())
879            .map(|context| context.allocator.clone())
880            .ok_or(InstructionError::CallDepth)
881    }
882
883    fn log_consumed_bpf_units(&self, amount: u64) {
884        if self.enable_instruction_tracing && amount != 0 {
885            let trace_log_stack_frame = self
886                .trace_log_stack
887                .last()
888                .expect("Inconsistent trace log stack");
889
890            trace_log_stack_frame.consumed_bpf_units.borrow_mut().push((
891                trace_log_stack_frame.trace_log.len().saturating_sub(1),
892                amount,
893            ));
894        }
895    }
896}
897
898pub struct MockInvokeContextPreparation {
899    pub transaction_accounts: Vec<TransactionAccount>,
900    pub instruction_accounts: Vec<InstructionAccount>,
901}
902
903pub fn prepare_mock_invoke_context(
904    transaction_accounts: Vec<TransactionAccount>,
905    instruction_account_metas: Vec<AccountMeta>,
906    _program_indices: &[IndexOfAccount],
907) -> MockInvokeContextPreparation {
908    let mut instruction_accounts: Vec<InstructionAccount> =
909        Vec::with_capacity(instruction_account_metas.len());
910    for (instruction_account_index, account_meta) in instruction_account_metas.iter().enumerate() {
911        let index_in_transaction = transaction_accounts
912            .iter()
913            .position(|(key, _account)| *key == account_meta.pubkey)
914            .unwrap_or(transaction_accounts.len())
915            as IndexOfAccount;
916        let index_in_callee = instruction_accounts
917            .get(0..instruction_account_index)
918            .unwrap()
919            .iter()
920            .position(|instruction_account| {
921                instruction_account.index_in_transaction == index_in_transaction
922            })
923            .unwrap_or(instruction_account_index) as IndexOfAccount;
924        instruction_accounts.push(InstructionAccount {
925            index_in_transaction,
926            index_in_caller: index_in_transaction,
927            index_in_callee,
928            is_signer: account_meta.is_signer,
929            is_writable: account_meta.is_writable,
930        });
931    }
932    MockInvokeContextPreparation {
933        transaction_accounts,
934        instruction_accounts,
935    }
936}
937
938pub fn with_mock_invoke_context<R, F: FnMut(&mut InvokeContext) -> R>(
939    loader_id: Pubkey,
940    account_size: usize,
941    is_writable: bool,
942    mut callback: F,
943) -> R {
944    let program_indices = vec![0, 1];
945    let program_key = Pubkey::new_unique();
946    let transaction_accounts = vec![
947        (
948            loader_id,
949            AccountSharedData::new(0, 0, &native_loader::id()),
950        ),
951        (program_key, AccountSharedData::new(1, 0, &loader_id)),
952        (
953            Pubkey::new_unique(),
954            AccountSharedData::new(2, account_size, &program_key),
955        ),
956    ];
957    let instruction_accounts = vec![AccountMeta {
958        pubkey: transaction_accounts.get(2).unwrap().0,
959        is_signer: false,
960        is_writable,
961    }];
962    let preparation =
963        prepare_mock_invoke_context(transaction_accounts, instruction_accounts, &program_indices);
964    let compute_budget = ComputeBudget::default();
965    let mut transaction_context = TransactionContext::new(
966        preparation.transaction_accounts,
967        Some(Rent::default()),
968        compute_budget.max_invoke_stack_height,
969        compute_budget.max_instruction_trace_length,
970    );
971    transaction_context.enable_cap_accounts_data_allocations_per_transaction();
972    let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
973    invoke_context
974        .transaction_context
975        .get_next_instruction_context()
976        .unwrap()
977        .configure(&program_indices, &preparation.instruction_accounts, &[]);
978    invoke_context.push().unwrap();
979    callback(&mut invoke_context)
980}
981
982pub fn mock_process_instruction(
983    loader_id: &Pubkey,
984    mut program_indices: Vec<IndexOfAccount>,
985    instruction_data: &[u8],
986    transaction_accounts: Vec<TransactionAccount>,
987    instruction_accounts: Vec<AccountMeta>,
988    sysvar_cache_override: Option<&SysvarCache>,
989    feature_set_override: Option<Arc<FeatureSet>>,
990    expected_result: Result<(), InstructionError>,
991    process_instruction: ProcessInstructionWithContext,
992) -> Vec<AccountSharedData> {
993    program_indices.insert(0, transaction_accounts.len() as IndexOfAccount);
994    let mut preparation =
995        prepare_mock_invoke_context(transaction_accounts, instruction_accounts, &program_indices);
996    let processor_account = AccountSharedData::new(0, 0, &native_loader::id());
997    preparation
998        .transaction_accounts
999        .push((*loader_id, processor_account));
1000    let compute_budget = ComputeBudget::default();
1001    let mut transaction_context = TransactionContext::new(
1002        preparation.transaction_accounts,
1003        Some(Rent::default()),
1004        compute_budget.max_invoke_stack_height,
1005        compute_budget.max_instruction_trace_length,
1006    );
1007    transaction_context.enable_cap_accounts_data_allocations_per_transaction();
1008    let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
1009    if let Some(sysvar_cache) = sysvar_cache_override {
1010        invoke_context.sysvar_cache = Cow::Borrowed(sysvar_cache);
1011    }
1012    if let Some(feature_set) = feature_set_override {
1013        invoke_context.feature_set = feature_set;
1014    }
1015    invoke_context
1016        .transaction_context
1017        .get_next_instruction_context()
1018        .unwrap()
1019        .configure(
1020            &program_indices,
1021            &preparation.instruction_accounts,
1022            instruction_data,
1023        );
1024    let result = invoke_context
1025        .push()
1026        .and_then(|_| process_instruction(1, &mut invoke_context));
1027    let pop_result = invoke_context.pop();
1028    assert_eq!(result.and(pop_result), expected_result);
1029    let mut transaction_accounts = transaction_context.deconstruct_without_keys().unwrap();
1030    transaction_accounts.pop();
1031    transaction_accounts
1032}
1033
1034#[cfg(test)]
1035mod tests {
1036    use {
1037        super::*,
1038        crate::compute_budget,
1039        serde::{Deserialize, Serialize},
1040        solana_sdk::{account::WritableAccount, instruction::Instruction},
1041    };
1042
1043    #[derive(Debug, Serialize, Deserialize)]
1044    enum MockInstruction {
1045        NoopSuccess,
1046        NoopFail,
1047        ModifyOwned,
1048        ModifyNotOwned,
1049        ModifyReadonly,
1050        UnbalancedPush,
1051        UnbalancedPop,
1052        ConsumeComputeUnits {
1053            compute_units_to_consume: u64,
1054            desired_result: Result<(), InstructionError>,
1055        },
1056        Resize {
1057            new_len: u64,
1058        },
1059    }
1060
1061    #[test]
1062    fn test_program_entry_debug() {
1063        #[allow(clippy::unnecessary_wraps)]
1064        fn mock_process_instruction(
1065            _first_instruction_account: IndexOfAccount,
1066            _invoke_context: &mut InvokeContext,
1067        ) -> Result<(), InstructionError> {
1068            Ok(())
1069        }
1070        #[allow(clippy::unnecessary_wraps)]
1071        fn mock_ix_processor(
1072            _first_instruction_account: IndexOfAccount,
1073            _invoke_context: &mut InvokeContext,
1074        ) -> Result<(), InstructionError> {
1075            Ok(())
1076        }
1077        let builtin_programs = &[
1078            BuiltinProgram {
1079                program_id: solana_sdk::pubkey::new_rand(),
1080                process_instruction: mock_process_instruction,
1081            },
1082            BuiltinProgram {
1083                program_id: solana_sdk::pubkey::new_rand(),
1084                process_instruction: mock_ix_processor,
1085            },
1086        ];
1087        assert!(!format!("{builtin_programs:?}").is_empty());
1088    }
1089
1090    #[allow(clippy::integer_arithmetic)]
1091    fn mock_process_instruction(
1092        _first_instruction_account: IndexOfAccount,
1093        invoke_context: &mut InvokeContext,
1094    ) -> Result<(), InstructionError> {
1095        let transaction_context = &invoke_context.transaction_context;
1096        let instruction_context = transaction_context.get_current_instruction_context()?;
1097        let instruction_data = instruction_context.get_instruction_data();
1098        let program_id = instruction_context.get_last_program_key(transaction_context)?;
1099        let instruction_accounts = (0..4)
1100            .map(|instruction_account_index| InstructionAccount {
1101                index_in_transaction: instruction_account_index,
1102                index_in_caller: instruction_account_index,
1103                index_in_callee: instruction_account_index,
1104                is_signer: false,
1105                is_writable: false,
1106            })
1107            .collect::<Vec<_>>();
1108        assert_eq!(
1109            program_id,
1110            instruction_context
1111                .try_borrow_instruction_account(transaction_context, 0)?
1112                .get_owner()
1113        );
1114        assert_ne!(
1115            instruction_context
1116                .try_borrow_instruction_account(transaction_context, 1)?
1117                .get_owner(),
1118            instruction_context
1119                .try_borrow_instruction_account(transaction_context, 0)?
1120                .get_key()
1121        );
1122
1123        if let Ok(instruction) = bincode::deserialize(instruction_data) {
1124            match instruction {
1125                MockInstruction::NoopSuccess => (),
1126                MockInstruction::NoopFail => return Err(InstructionError::GenericError),
1127                MockInstruction::ModifyOwned => instruction_context
1128                    .try_borrow_instruction_account(transaction_context, 0)?
1129                    .set_data_from_slice(&[1])?,
1130                MockInstruction::ModifyNotOwned => instruction_context
1131                    .try_borrow_instruction_account(transaction_context, 1)?
1132                    .set_data_from_slice(&[1])?,
1133                MockInstruction::ModifyReadonly => instruction_context
1134                    .try_borrow_instruction_account(transaction_context, 2)?
1135                    .set_data_from_slice(&[1])?,
1136                MockInstruction::UnbalancedPush => {
1137                    instruction_context
1138                        .try_borrow_instruction_account(transaction_context, 0)?
1139                        .checked_add_lamports(1)?;
1140                    let program_id = *transaction_context.get_key_of_account_at_index(3)?;
1141                    let metas = vec![
1142                        AccountMeta::new_readonly(
1143                            *transaction_context.get_key_of_account_at_index(0)?,
1144                            false,
1145                        ),
1146                        AccountMeta::new_readonly(
1147                            *transaction_context.get_key_of_account_at_index(1)?,
1148                            false,
1149                        ),
1150                    ];
1151                    let inner_instruction = Instruction::new_with_bincode(
1152                        program_id,
1153                        &MockInstruction::NoopSuccess,
1154                        metas,
1155                    );
1156                    invoke_context
1157                        .transaction_context
1158                        .get_next_instruction_context()
1159                        .unwrap()
1160                        .configure(&[3], &instruction_accounts, &[]);
1161                    let result = invoke_context.push();
1162                    assert_eq!(result, Err(InstructionError::UnbalancedInstruction));
1163                    result?;
1164                    invoke_context
1165                        .native_invoke(inner_instruction.into(), &[])
1166                        .and(invoke_context.pop())?;
1167                }
1168                MockInstruction::UnbalancedPop => instruction_context
1169                    .try_borrow_instruction_account(transaction_context, 0)?
1170                    .checked_add_lamports(1)?,
1171                MockInstruction::ConsumeComputeUnits {
1172                    compute_units_to_consume,
1173                    desired_result,
1174                } => {
1175                    invoke_context.consume_checked(compute_units_to_consume)?;
1176                    return desired_result;
1177                }
1178                MockInstruction::Resize { new_len } => instruction_context
1179                    .try_borrow_instruction_account(transaction_context, 0)?
1180                    .set_data(vec![0; new_len as usize])?,
1181            }
1182        } else {
1183            return Err(InstructionError::InvalidInstructionData);
1184        }
1185        Ok(())
1186    }
1187
1188    #[test]
1189    fn test_instruction_stack_height() {
1190        const MAX_DEPTH: usize = 10;
1191        let mut invoke_stack = vec![];
1192        let mut accounts = vec![];
1193        let mut instruction_accounts = vec![];
1194        for index in 0..MAX_DEPTH {
1195            invoke_stack.push(solana_sdk::pubkey::new_rand());
1196            accounts.push((
1197                solana_sdk::pubkey::new_rand(),
1198                AccountSharedData::new(index as u64, 1, invoke_stack.get(index).unwrap()),
1199            ));
1200            instruction_accounts.push(InstructionAccount {
1201                index_in_transaction: index as IndexOfAccount,
1202                index_in_caller: index as IndexOfAccount,
1203                index_in_callee: instruction_accounts.len() as IndexOfAccount,
1204                is_signer: false,
1205                is_writable: true,
1206            });
1207        }
1208        for (index, program_id) in invoke_stack.iter().enumerate() {
1209            accounts.push((
1210                *program_id,
1211                AccountSharedData::new(1, 1, &solana_sdk::pubkey::Pubkey::default()),
1212            ));
1213            instruction_accounts.push(InstructionAccount {
1214                index_in_transaction: index as IndexOfAccount,
1215                index_in_caller: index as IndexOfAccount,
1216                index_in_callee: index as IndexOfAccount,
1217                is_signer: false,
1218                is_writable: false,
1219            });
1220        }
1221        let mut transaction_context = TransactionContext::new(
1222            accounts,
1223            Some(Rent::default()),
1224            ComputeBudget::default().max_invoke_stack_height,
1225            MAX_DEPTH,
1226        );
1227        let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
1228
1229        // Check call depth increases and has a limit
1230        let mut depth_reached = 0;
1231        for _ in 0..invoke_stack.len() {
1232            invoke_context
1233                .transaction_context
1234                .get_next_instruction_context()
1235                .unwrap()
1236                .configure(
1237                    &[(MAX_DEPTH + depth_reached) as IndexOfAccount],
1238                    &instruction_accounts,
1239                    &[],
1240                );
1241            if Err(InstructionError::CallDepth) == invoke_context.push() {
1242                break;
1243            }
1244            depth_reached += 1;
1245        }
1246        assert_ne!(depth_reached, 0);
1247        assert!(depth_reached < MAX_DEPTH);
1248    }
1249
1250    #[test]
1251    fn test_max_instruction_trace_length() {
1252        const MAX_INSTRUCTIONS: usize = 8;
1253        let mut transaction_context =
1254            TransactionContext::new(Vec::new(), Some(Rent::default()), 1, MAX_INSTRUCTIONS);
1255        for _ in 0..MAX_INSTRUCTIONS {
1256            transaction_context.push().unwrap();
1257            transaction_context.pop().unwrap();
1258        }
1259        assert_eq!(
1260            transaction_context.push(),
1261            Err(InstructionError::MaxInstructionTraceLengthExceeded)
1262        );
1263    }
1264
1265    #[test]
1266    fn test_process_instruction() {
1267        let callee_program_id = solana_sdk::pubkey::new_rand();
1268        let builtin_programs = &[BuiltinProgram {
1269            program_id: callee_program_id,
1270            process_instruction: mock_process_instruction,
1271        }];
1272
1273        let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1274        let not_owned_account = AccountSharedData::new(84, 1, &solana_sdk::pubkey::new_rand());
1275        let readonly_account = AccountSharedData::new(168, 1, &solana_sdk::pubkey::new_rand());
1276        let loader_account = AccountSharedData::new(0, 0, &native_loader::id());
1277        let mut program_account = AccountSharedData::new(1, 0, &native_loader::id());
1278        program_account.set_executable(true);
1279        let accounts = vec![
1280            (solana_sdk::pubkey::new_rand(), owned_account),
1281            (solana_sdk::pubkey::new_rand(), not_owned_account),
1282            (solana_sdk::pubkey::new_rand(), readonly_account),
1283            (callee_program_id, program_account),
1284            (solana_sdk::pubkey::new_rand(), loader_account),
1285        ];
1286        let metas = vec![
1287            AccountMeta::new(accounts.get(0).unwrap().0, false),
1288            AccountMeta::new(accounts.get(1).unwrap().0, false),
1289            AccountMeta::new_readonly(accounts.get(2).unwrap().0, false),
1290        ];
1291        let instruction_accounts = (0..4)
1292            .map(|instruction_account_index| InstructionAccount {
1293                index_in_transaction: instruction_account_index,
1294                index_in_caller: instruction_account_index,
1295                index_in_callee: instruction_account_index,
1296                is_signer: false,
1297                is_writable: instruction_account_index < 2,
1298            })
1299            .collect::<Vec<_>>();
1300        let mut transaction_context =
1301            TransactionContext::new(accounts, Some(Rent::default()), 2, 18);
1302        let mut invoke_context =
1303            InvokeContext::new_mock(&mut transaction_context, builtin_programs);
1304
1305        // Account modification tests
1306        let cases = vec![
1307            (MockInstruction::NoopSuccess, Ok(())),
1308            (
1309                MockInstruction::NoopFail,
1310                Err(InstructionError::GenericError),
1311            ),
1312            (MockInstruction::ModifyOwned, Ok(())),
1313            (
1314                MockInstruction::ModifyNotOwned,
1315                Err(InstructionError::ExternalAccountDataModified),
1316            ),
1317            (
1318                MockInstruction::ModifyReadonly,
1319                Err(InstructionError::ReadonlyDataModified),
1320            ),
1321            (
1322                MockInstruction::UnbalancedPush,
1323                Err(InstructionError::UnbalancedInstruction),
1324            ),
1325            (
1326                MockInstruction::UnbalancedPop,
1327                Err(InstructionError::UnbalancedInstruction),
1328            ),
1329        ];
1330        for case in cases {
1331            invoke_context
1332                .transaction_context
1333                .get_next_instruction_context()
1334                .unwrap()
1335                .configure(&[4], &instruction_accounts, &[]);
1336            invoke_context.push().unwrap();
1337            let inner_instruction =
1338                Instruction::new_with_bincode(callee_program_id, &case.0, metas.clone());
1339            let result = invoke_context
1340                .native_invoke(inner_instruction.into(), &[])
1341                .and(invoke_context.pop());
1342            assert_eq!(result, case.1);
1343        }
1344
1345        // Compute unit consumption tests
1346        let compute_units_to_consume = 10;
1347        let expected_results = vec![Ok(()), Err(InstructionError::GenericError)];
1348        for expected_result in expected_results {
1349            invoke_context
1350                .transaction_context
1351                .get_next_instruction_context()
1352                .unwrap()
1353                .configure(&[4], &instruction_accounts, &[]);
1354            invoke_context.push().unwrap();
1355            let inner_instruction = Instruction::new_with_bincode(
1356                callee_program_id,
1357                &MockInstruction::ConsumeComputeUnits {
1358                    compute_units_to_consume,
1359                    desired_result: expected_result.clone(),
1360                },
1361                metas.clone(),
1362            );
1363            let inner_instruction = StableInstruction::from(inner_instruction);
1364            let (inner_instruction_accounts, program_indices) = invoke_context
1365                .prepare_instruction(&inner_instruction, &[])
1366                .unwrap();
1367
1368            let mut compute_units_consumed = 0;
1369            let result = invoke_context.process_instruction(
1370                &inner_instruction.data,
1371                &inner_instruction_accounts,
1372                &program_indices,
1373                &mut compute_units_consumed,
1374                &mut ExecuteTimings::default(),
1375            );
1376
1377            // Because the instruction had compute cost > 0, then regardless of the execution result,
1378            // the number of compute units consumed should be a non-default which is something greater
1379            // than zero.
1380            assert!(compute_units_consumed > 0);
1381            assert_eq!(compute_units_consumed, compute_units_to_consume);
1382            assert_eq!(result, expected_result);
1383
1384            invoke_context.pop().unwrap();
1385        }
1386    }
1387
1388    #[test]
1389    fn test_invoke_context_compute_budget() {
1390        let accounts = vec![(solana_sdk::pubkey::new_rand(), AccountSharedData::default())];
1391
1392        let mut transaction_context =
1393            TransactionContext::new(accounts, Some(Rent::default()), 1, 1);
1394        let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
1395        invoke_context.compute_budget =
1396            ComputeBudget::new(compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64);
1397
1398        invoke_context
1399            .transaction_context
1400            .get_next_instruction_context()
1401            .unwrap()
1402            .configure(&[0], &[], &[]);
1403        invoke_context.push().unwrap();
1404        assert_eq!(
1405            *invoke_context.get_compute_budget(),
1406            ComputeBudget::new(compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64)
1407        );
1408        invoke_context.pop().unwrap();
1409    }
1410
1411    #[test]
1412    fn test_process_instruction_accounts_resize_delta() {
1413        let program_key = Pubkey::new_unique();
1414        let user_account_data_len = 123u64;
1415        let user_account =
1416            AccountSharedData::new(100, user_account_data_len as usize, &program_key);
1417        let dummy_account = AccountSharedData::new(10, 0, &program_key);
1418        let mut program_account = AccountSharedData::new(500, 500, &native_loader::id());
1419        program_account.set_executable(true);
1420        let accounts = vec![
1421            (Pubkey::new_unique(), user_account),
1422            (Pubkey::new_unique(), dummy_account),
1423            (program_key, program_account),
1424        ];
1425
1426        let builtin_programs = [BuiltinProgram {
1427            program_id: program_key,
1428            process_instruction: mock_process_instruction,
1429        }];
1430
1431        let mut transaction_context =
1432            TransactionContext::new(accounts, Some(Rent::default()), 1, 3);
1433        let mut invoke_context =
1434            InvokeContext::new_mock(&mut transaction_context, &builtin_programs);
1435
1436        let instruction_accounts = [
1437            InstructionAccount {
1438                index_in_transaction: 0,
1439                index_in_caller: 0,
1440                index_in_callee: 0,
1441                is_signer: false,
1442                is_writable: true,
1443            },
1444            InstructionAccount {
1445                index_in_transaction: 1,
1446                index_in_caller: 1,
1447                index_in_callee: 1,
1448                is_signer: false,
1449                is_writable: false,
1450            },
1451        ];
1452
1453        // Test: Resize the account to *the same size*, so not consuming any additional size; this must succeed
1454        {
1455            let resize_delta: i64 = 0;
1456            let new_len = (user_account_data_len as i64 + resize_delta) as u64;
1457            let instruction_data =
1458                bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1459
1460            let result = invoke_context.process_instruction(
1461                &instruction_data,
1462                &instruction_accounts,
1463                &[2],
1464                &mut 0,
1465                &mut ExecuteTimings::default(),
1466            );
1467
1468            assert!(result.is_ok());
1469            assert_eq!(
1470                invoke_context
1471                    .transaction_context
1472                    .accounts_resize_delta()
1473                    .unwrap(),
1474                resize_delta
1475            );
1476        }
1477
1478        // Test: Resize the account larger; this must succeed
1479        {
1480            let resize_delta: i64 = 1;
1481            let new_len = (user_account_data_len as i64 + resize_delta) as u64;
1482            let instruction_data =
1483                bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1484
1485            let result = invoke_context.process_instruction(
1486                &instruction_data,
1487                &instruction_accounts,
1488                &[2],
1489                &mut 0,
1490                &mut ExecuteTimings::default(),
1491            );
1492
1493            assert!(result.is_ok());
1494            assert_eq!(
1495                invoke_context
1496                    .transaction_context
1497                    .accounts_resize_delta()
1498                    .unwrap(),
1499                resize_delta
1500            );
1501        }
1502
1503        // Test: Resize the account smaller; this must succeed
1504        {
1505            let resize_delta: i64 = -1;
1506            let new_len = (user_account_data_len as i64 + resize_delta) as u64;
1507            let instruction_data =
1508                bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1509
1510            let result = invoke_context.process_instruction(
1511                &instruction_data,
1512                &instruction_accounts,
1513                &[2],
1514                &mut 0,
1515                &mut ExecuteTimings::default(),
1516            );
1517
1518            assert!(result.is_ok());
1519            assert_eq!(
1520                invoke_context
1521                    .transaction_context
1522                    .accounts_resize_delta()
1523                    .unwrap(),
1524                resize_delta
1525            );
1526        }
1527    }
1528}