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