solana_program_runtime/
invoke_context.rs

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