1use std::{
6 alloc::Layout,
7 cell::RefCell,
8 collections::HashSet,
9 fmt::{self, Debug},
10 rc::Rc,
11};
12
13use rialo_hash::Hash;
14use rialo_s_account::{create_account_shared_data_for_test, AccountSharedData, StoredAccount};
15use rialo_s_clock::Slot;
16use rialo_s_compute_budget::compute_budget::ComputeBudget;
17use rialo_s_epoch_schedule::EpochSchedule;
18use rialo_s_feature_set::{
19 lift_cpi_caller_restriction, move_precompile_verification_to_svm,
20 remove_accounts_executable_flag_checks, FeatureSet,
21};
22use rialo_s_instruction::{error::InstructionError, AccountMeta};
23use rialo_s_log_collector::{ic_msg, LogCollector};
24use rialo_s_measure::measure::Measure;
25use rialo_s_precompiles::Precompile;
26use rialo_s_pubkey::Pubkey;
27use rialo_s_sdk_ids::{bpf_loader_deprecated, native_loader, sysvar};
28use rialo_s_stable_layout::stable_instruction::StableInstruction;
29use rialo_s_timings::{ExecuteDetailsTimings, ExecuteTimings};
30use rialo_s_transaction_context::{
31 IndexOfAccount, InstructionAccount, TransactionAccount, TransactionContext,
32};
33use rialo_s_type_overrides::sync::{atomic::Ordering, Arc};
34pub use rialo_stake_cache_interface::{StakeCacheData, StakesHandle, ValidatorAccount};
35use solana_sbpf::{
36 ebpf::MM_HEAP_START,
37 error::{EbpfError, ProgramResult},
38 memory_region::MemoryMapping,
39 program::{BuiltinFunction, SBPFVersion},
40 vm::{Config, ContextObject, EbpfVm},
41};
42
43use crate::{
44 loaded_programs::{ProgramCacheEntryType, ProgramCacheForTx, ProgramRuntimeEnvironments},
45 stable_log,
46 sysvar_cache::SysvarCache,
47};
48
49pub type BuiltinFunctionWithContext = BuiltinFunction<InvokeContext<'static, 'static>>;
50
51#[macro_export]
53macro_rules! declare_process_instruction {
54 ($process_instruction:ident, $cu_to_consume:expr, |$invoke_context:ident| $inner:tt) => {
55 $crate::solana_sbpf::declare_builtin_function!(
56 $process_instruction,
57 fn rust(
58 invoke_context: &mut $crate::invoke_context::InvokeContext<'_, '_>,
59 _arg0: u64,
60 _arg1: u64,
61 _arg2: u64,
62 _arg3: u64,
63 _arg4: u64,
64 _memory_mapping: &mut $crate::solana_sbpf::memory_region::MemoryMapping<'_>,
65 ) -> std::result::Result<u64, Box<dyn std::error::Error>> {
66 fn process_instruction_inner(
67 $invoke_context: &mut $crate::invoke_context::InvokeContext<'_, '_>,
68 ) -> std::result::Result<(), $crate::__private::InstructionError>
69 $inner
70
71 let consumption_result = if $cu_to_consume > 0
72 {
73 invoke_context.consume_checked($cu_to_consume)
74 } else {
75 Ok(())
76 };
77 consumption_result
78 .and_then(|_| {
79 process_instruction_inner(invoke_context)
80 .map(|_| 0)
81 .map_err(|err| Box::new(err) as Box<dyn std::error::Error>)
82 })
83 .into()
84 }
85 );
86 };
87}
88
89pub enum AccountInsertError {
91 TooManyAccounts,
93
94 AccountAlreadyLoaded,
96}
97
98#[allow(clippy::result_unit_err)]
102pub trait RuntimeAccountLoader {
103 fn load_account(&self, pubkey: &Pubkey) -> Result<Option<StoredAccount>, ()>;
109}
110
111impl ContextObject for InvokeContext<'_, '_> {
112 fn trace(&mut self, state: [u64; 12]) {
113 self.syscall_context
114 .last_mut()
115 .unwrap()
116 .as_mut()
117 .unwrap()
118 .trace_log
119 .push(state);
120 }
121
122 fn consume(&mut self, amount: u64) {
123 let mut compute_meter = self.compute_meter.borrow_mut();
126 *compute_meter = compute_meter.saturating_sub(amount);
127 }
128
129 fn get_remaining(&self) -> u64 {
130 *self.compute_meter.borrow()
131 }
132}
133
134#[derive(Clone, PartialEq, Eq, Debug)]
135pub struct AllocErr;
136impl fmt::Display for AllocErr {
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 f.write_str("Error: Memory allocation failed")
139 }
140}
141
142pub struct BpfAllocator {
143 len: u64,
144 pos: u64,
145}
146
147impl BpfAllocator {
148 pub fn new(len: u64) -> Self {
149 Self { len, pos: 0 }
150 }
151
152 pub fn alloc(&mut self, layout: Layout) -> Result<u64, AllocErr> {
153 let bytes_to_align = (self.pos as *const u8).align_offset(layout.align()) as u64;
154 if self
155 .pos
156 .saturating_add(bytes_to_align)
157 .saturating_add(layout.size() as u64)
158 <= self.len
159 {
160 self.pos = self.pos.saturating_add(bytes_to_align);
161 let addr = MM_HEAP_START.saturating_add(self.pos);
162 self.pos = self.pos.saturating_add(layout.size() as u64);
163 Ok(addr)
164 } else {
165 Err(AllocErr)
166 }
167 }
168}
169
170pub struct EnvironmentConfig<'a> {
171 pub blockhash: Hash,
172 pub blockhash_kelvins_per_signature: u64,
173 get_epoch_vote_account_stake_callback: &'a dyn Fn(&'a Pubkey) -> u64,
174 pub feature_set: Arc<FeatureSet>,
175 sysvar_cache: &'a SysvarCache,
176 pub random_seed: u64,
177 stakes_handle: StakesHandle,
186}
187impl<'a> EnvironmentConfig<'a> {
188 #[allow(clippy::too_many_arguments)]
189 pub fn new(
190 blockhash: Hash,
191 blockhash_kelvins_per_signature: u64,
192 get_epoch_vote_account_stake_callback: &'a dyn Fn(&'a Pubkey) -> u64,
193 feature_set: Arc<FeatureSet>,
194 sysvar_cache: &'a SysvarCache,
195 random_seed: u64,
196 stakes_handle: StakesHandle,
197 ) -> Self {
198 Self {
199 blockhash,
200 blockhash_kelvins_per_signature,
201 get_epoch_vote_account_stake_callback,
202 feature_set,
203 sysvar_cache,
204 random_seed,
205 stakes_handle,
206 }
207 }
208}
209
210pub struct SyscallContext {
211 pub allocator: BpfAllocator,
212 pub accounts_metadata: Vec<SerializedAccountMetadata>,
213 pub trace_log: Vec<[u64; 12]>,
214}
215
216#[derive(Debug, Clone)]
217pub struct SerializedAccountMetadata {
218 pub original_data_len: usize,
219 pub vm_data_addr: u64,
220 pub vm_key_addr: u64,
221 pub vm_kelvins_addr: u64,
222 pub vm_owner_addr: u64,
223}
224
225pub struct InvokeContext<'a, 'b> {
227 pub transaction_context: &'a mut TransactionContext,
229 pub program_cache_for_tx_batch: &'a mut ProgramCacheForTx<'b>,
231 pub environment_config: EnvironmentConfig<'a>,
233 compute_budget: ComputeBudget,
235 compute_meter: RefCell<u64>,
238 log_collector: Option<Rc<RefCell<LogCollector>>>,
239 pub execute_time: Option<Measure>,
241 pub timings: ExecuteDetailsTimings,
242 pub syscall_context: Vec<Option<SyscallContext>>,
243 traces: Vec<Vec<[u64; 12]>>,
244 pub account_loader: Option<Box<dyn RuntimeAccountLoader + 'a>>,
245 loaded_accounts: HashSet<Pubkey>,
250 num_account_loads: usize,
252}
253
254impl<'a, 'b> InvokeContext<'a, 'b> {
255 #[allow(clippy::too_many_arguments)]
256 pub fn new(
257 transaction_context: &'a mut TransactionContext,
258 program_cache_for_tx_batch: &'a mut ProgramCacheForTx<'b>,
259 environment_config: EnvironmentConfig<'a>,
260 log_collector: Option<Rc<RefCell<LogCollector>>>,
261 compute_budget: ComputeBudget,
262 ) -> Self {
263 let loaded_accounts = transaction_context.account_keys().copied().collect();
264
265 Self {
266 transaction_context,
267 program_cache_for_tx_batch,
268 environment_config,
269 log_collector,
270 compute_budget,
271 compute_meter: RefCell::new(compute_budget.compute_unit_limit),
272 execute_time: None,
273 timings: ExecuteDetailsTimings::default(),
274 syscall_context: Vec::new(),
275 traces: Vec::new(),
276 account_loader: None,
277 loaded_accounts,
278 num_account_loads: 0usize,
279 }
280 }
281
282 pub fn new_with_account_loader(
283 transaction_context: &'a mut TransactionContext,
284 program_cache_for_tx_batch: &'a mut ProgramCacheForTx<'b>,
285 environment_config: EnvironmentConfig<'a>,
286 log_collector: Option<Rc<RefCell<LogCollector>>>,
287 compute_budget: ComputeBudget,
288 account_loader: Box<dyn RuntimeAccountLoader + 'a>,
289 num_account_locks: usize,
290 num_writable_accounts: usize,
291 ) -> Self {
292 let loaded_accounts = transaction_context.account_keys().copied().collect();
293
294 Self {
295 transaction_context,
296 program_cache_for_tx_batch,
297 environment_config,
298 log_collector,
299 compute_budget,
300 compute_meter: RefCell::new(compute_budget.compute_unit_limit),
301 execute_time: None,
302 timings: ExecuteDetailsTimings::default(),
303 syscall_context: Vec::new(),
304 traces: Vec::new(),
305 account_loader: Some(account_loader),
306 loaded_accounts,
307 num_account_loads: num_account_locks.saturating_sub(num_writable_accounts),
308 }
309 }
310
311 pub fn add_loaded_account(&mut self, pubkey: Pubkey) -> Result<(), AccountInsertError> {
317 match self.num_account_loads.checked_sub(1) {
318 Some(value) => self.num_account_loads = value,
319 None => return Err(AccountInsertError::TooManyAccounts),
320 }
321
322 if self.loaded_accounts.insert(pubkey) {
323 return Ok(());
324 }
325
326 Err(AccountInsertError::AccountAlreadyLoaded)
327 }
328
329 pub fn get_environments_for_slot(
330 &self,
331 effective_slot: Slot,
332 ) -> Result<&ProgramRuntimeEnvironments, InstructionError> {
333 let epoch_schedule = self.environment_config.sysvar_cache.get_epoch_schedule()?;
334 let epoch = epoch_schedule.get_epoch(effective_slot);
335 Ok(self
336 .program_cache_for_tx_batch
337 .get_environments_for_epoch(epoch))
338 }
339
340 pub fn push(&mut self) -> Result<(), InstructionError> {
342 let instruction_context = self
343 .transaction_context
344 .get_instruction_context_at_index_in_trace(
345 self.transaction_context.get_instruction_trace_length(),
346 )?;
347 let program_id = instruction_context
348 .get_last_program_key(self.transaction_context)
349 .map_err(|_| InstructionError::UnsupportedProgramId)?;
350 if self
351 .transaction_context
352 .get_instruction_context_stack_height()
353 != 0
354 {
355 let contains = (0..self
356 .transaction_context
357 .get_instruction_context_stack_height())
358 .any(|level| {
359 self.transaction_context
360 .get_instruction_context_at_nesting_level(level)
361 .and_then(|instruction_context| {
362 instruction_context
363 .try_borrow_last_program_account(self.transaction_context)
364 })
365 .map(|program_account| program_account.get_key() == program_id)
366 .unwrap_or(false)
367 });
368 let is_last = self
369 .transaction_context
370 .get_current_instruction_context()
371 .and_then(|instruction_context| {
372 instruction_context.try_borrow_last_program_account(self.transaction_context)
373 })
374 .map(|program_account| program_account.get_key() == program_id)
375 .unwrap_or(false);
376 if contains && !is_last {
377 return Err(InstructionError::ReentrancyNotAllowed);
379 }
380 }
381
382 self.syscall_context.push(None);
383 self.transaction_context.push()
384 }
385
386 fn pop(&mut self) -> Result<(), InstructionError> {
388 if let Some(Some(syscall_context)) = self.syscall_context.pop() {
389 self.traces.push(syscall_context.trace_log);
390 }
391 self.transaction_context.pop()
392 }
393
394 pub fn get_stack_height(&self) -> usize {
397 self.transaction_context
398 .get_instruction_context_stack_height()
399 }
400
401 pub fn native_invoke(
403 &mut self,
404 instruction: StableInstruction,
405 signers: &[Pubkey],
406 ) -> Result<(), InstructionError> {
407 let (instruction_accounts, program_indices) =
408 self.prepare_instruction(&instruction, signers)?;
409 let mut compute_units_consumed = 0;
410 self.process_instruction(
411 &instruction.data,
412 &instruction_accounts,
413 &program_indices,
414 &mut compute_units_consumed,
415 &mut ExecuteTimings::default(),
416 )?;
417 Ok(())
418 }
419
420 #[allow(clippy::type_complexity)]
422 pub fn prepare_instruction(
423 &mut self,
424 instruction: &StableInstruction,
425 signers: &[Pubkey],
426 ) -> Result<(Vec<InstructionAccount>, Vec<IndexOfAccount>), InstructionError> {
427 self.prepare_instruction_inner(instruction.program_id, &instruction.accounts, signers)
428 }
429
430 pub fn prepare_cpi_instruction(
432 &mut self,
433 program_id: Pubkey,
434 account_metas: &[AccountMeta],
435 signers: &[Pubkey],
436 ) -> Result<(Vec<InstructionAccount>, Vec<IndexOfAccount>), InstructionError> {
437 self.prepare_instruction_inner(program_id, account_metas, signers)
438 }
439
440 pub fn prepare_instruction_inner(
441 &mut self,
442 callee_program_id: Pubkey,
443 account_metas: &[AccountMeta],
444 signers: &[Pubkey],
445 ) -> Result<(Vec<InstructionAccount>, Vec<IndexOfAccount>), InstructionError> {
446 let instruction_context = self.transaction_context.get_current_instruction_context()?;
451 let mut deduplicated_instruction_accounts: Vec<InstructionAccount> = Vec::new();
452 let mut duplicate_indicies = Vec::with_capacity(account_metas.len());
453 for (instruction_account_index, account_meta) in account_metas.iter().enumerate() {
454 let index_in_transaction = self
455 .transaction_context
456 .find_index_of_account(&account_meta.pubkey)
457 .ok_or_else(|| {
458 ic_msg!(
459 self,
460 "Instruction references an unknown account {}",
461 account_meta.pubkey,
462 );
463 InstructionError::MissingAccount
464 })?;
465 if let Some(duplicate_index) =
466 deduplicated_instruction_accounts
467 .iter()
468 .position(|instruction_account| {
469 instruction_account.index_in_transaction == index_in_transaction
470 })
471 {
472 duplicate_indicies.push(duplicate_index);
473 let instruction_account = deduplicated_instruction_accounts
474 .get_mut(duplicate_index)
475 .ok_or(InstructionError::NotEnoughAccountKeys)?;
476 instruction_account.is_signer |= account_meta.is_signer;
477 instruction_account.is_writable |= account_meta.is_writable;
478 } else {
479 let index_in_caller = instruction_context
480 .find_index_of_instruction_account(
481 self.transaction_context,
482 &account_meta.pubkey,
483 )
484 .ok_or_else(|| {
485 ic_msg!(
486 self,
487 "Instruction references an unknown account {}",
488 account_meta.pubkey,
489 );
490 InstructionError::MissingAccount
491 })?;
492 duplicate_indicies.push(deduplicated_instruction_accounts.len());
493 deduplicated_instruction_accounts.push(InstructionAccount {
494 index_in_transaction,
495 index_in_caller,
496 index_in_callee: instruction_account_index as IndexOfAccount,
497 is_signer: account_meta.is_signer,
498 is_writable: account_meta.is_writable,
499 });
500 }
501 }
502 for instruction_account in deduplicated_instruction_accounts.iter() {
503 let borrowed_account = instruction_context.try_borrow_instruction_account(
504 self.transaction_context,
505 instruction_account.index_in_caller,
506 )?;
507
508 if instruction_account.is_writable && !borrowed_account.is_writable() {
510 ic_msg!(
511 self,
512 "{}'s writable privilege escalated",
513 borrowed_account.get_key(),
514 );
515 return Err(InstructionError::PrivilegeEscalation);
516 }
517
518 if instruction_account.is_signer
521 && !(borrowed_account.is_signer() || signers.contains(borrowed_account.get_key()))
522 {
523 ic_msg!(
524 self,
525 "{}'s signer privilege escalated",
526 borrowed_account.get_key()
527 );
528 return Err(InstructionError::PrivilegeEscalation);
529 }
530 }
531 let instruction_accounts = duplicate_indicies
532 .into_iter()
533 .map(|duplicate_index| {
534 deduplicated_instruction_accounts
535 .get(duplicate_index)
536 .cloned()
537 .ok_or(InstructionError::NotEnoughAccountKeys)
538 })
539 .collect::<Result<Vec<InstructionAccount>, InstructionError>>()?;
540
541 let program_account_index = if self
543 .get_feature_set()
544 .is_active(&lift_cpi_caller_restriction::id())
545 {
546 match (
547 self.transaction_context
548 .find_index_of_program_account(&callee_program_id),
549 &self.account_loader,
550 ) {
551 (Some(index), _) => index,
552 (None, Some(account_loader)) => {
553 match account_loader.load_account(&callee_program_id) {
554 Ok(Some(account)) => {
555 self.transaction_context
556 .add_account((callee_program_id, account.clone()));
557
558 assert!(self.loaded_accounts.insert(callee_program_id));
560
561 self.transaction_context
563 .find_index_of_program_account(&callee_program_id)
564 .expect("program to exist")
565 }
566 Ok(None) => return Err(InstructionError::MissingAccount),
567 Err(()) => return Err(InstructionError::GenericError),
568 }
569 }
570 (None, None) => {
571 ic_msg!(self, "Unknown program {}", callee_program_id);
572 return Err(InstructionError::MissingAccount);
573 }
574 }
575 } else {
576 let program_account_index = instruction_context
577 .find_index_of_instruction_account(self.transaction_context, &callee_program_id)
578 .ok_or_else(|| {
579 ic_msg!(self, "Unknown program {}", callee_program_id);
580 InstructionError::MissingAccount
581 })?;
582 let borrowed_program_account = instruction_context
583 .try_borrow_instruction_account(self.transaction_context, program_account_index)?;
584 #[allow(deprecated)]
585 if !self
586 .get_feature_set()
587 .is_active(&remove_accounts_executable_flag_checks::id())
588 && !borrowed_program_account.is_executable()
589 {
590 ic_msg!(self, "Account {} is not executable", callee_program_id);
591 return Err(InstructionError::AccountNotExecutable);
592 }
593 borrowed_program_account.get_index_in_transaction()
594 };
595
596 Ok((instruction_accounts, vec![program_account_index]))
597 }
598
599 pub fn process_instruction(
601 &mut self,
602 instruction_data: &[u8],
603 instruction_accounts: &[InstructionAccount],
604 program_indices: &[IndexOfAccount],
605 compute_units_consumed: &mut u64,
606 timings: &mut ExecuteTimings,
607 ) -> Result<(), InstructionError> {
608 *compute_units_consumed = 0;
609 self.transaction_context
610 .get_next_instruction_context()?
611 .configure(program_indices, instruction_accounts, instruction_data);
612 self.push()?;
613 self.process_executable_chain(compute_units_consumed, timings)
614 .and(self.pop())
617 }
618
619 pub fn process_precompile<'ix_data>(
621 &mut self,
622 precompile: &Precompile,
623 instruction_data: &[u8],
624 instruction_accounts: &[InstructionAccount],
625 program_indices: &[IndexOfAccount],
626 message_instruction_datas_iter: impl Iterator<Item = &'ix_data [u8]>,
627 ) -> Result<(), InstructionError> {
628 self.transaction_context
629 .get_next_instruction_context()?
630 .configure(program_indices, instruction_accounts, instruction_data);
631 self.push()?;
632
633 let feature_set = self.get_feature_set();
634 let move_precompile_verification_to_svm =
635 feature_set.is_active(&move_precompile_verification_to_svm::id());
636 if move_precompile_verification_to_svm {
637 let instruction_datas: Vec<_> = message_instruction_datas_iter.collect();
638 precompile
639 .verify(instruction_data, &instruction_datas, feature_set)
640 .map_err(InstructionError::from)
641 .and(self.pop())
642 } else {
643 self.pop()
644 }
645 }
646
647 fn process_executable_chain(
649 &mut self,
650 compute_units_consumed: &mut u64,
651 timings: &mut ExecuteTimings,
652 ) -> Result<(), InstructionError> {
653 let instruction_context = self.transaction_context.get_current_instruction_context()?;
654 let process_executable_chain_time = Measure::start("process_executable_chain_time");
655
656 let builtin_id = {
657 debug_assert!(instruction_context.get_number_of_program_accounts() <= 1);
658 let borrowed_root_account = instruction_context
659 .try_borrow_program_account(self.transaction_context, 0)
660 .map_err(|_| InstructionError::UnsupportedProgramId)?;
661 let owner_id = borrowed_root_account.get_owner();
662 if native_loader::check_id(owner_id) {
663 *borrowed_root_account.get_key()
664 } else {
665 *owner_id
666 }
667 };
668
669 const ENTRYPOINT_KEY: u32 = 0x71E3CF81;
671 let entry = self
672 .program_cache_for_tx_batch
673 .find(&builtin_id)
674 .ok_or(InstructionError::UnsupportedProgramId)?;
675 let function = match &entry.program {
676 ProgramCacheEntryType::Builtin(program) => program
677 .get_function_registry()
678 .lookup_by_key(ENTRYPOINT_KEY)
679 .map(|(_name, function)| function),
680 _ => None,
681 }
682 .ok_or(InstructionError::UnsupportedProgramId)?;
683 entry.ix_usage_counter.fetch_add(1, Ordering::Relaxed);
684
685 let program_id = *instruction_context.get_last_program_key(self.transaction_context)?;
686 self.transaction_context
687 .set_return_data(program_id, Vec::new())?;
688 let logger = self.get_log_collector();
689 stable_log::program_invoke(&logger, &program_id, self.get_stack_height());
690 let pre_remaining_units = self.get_remaining();
691 let mock_config = Config::default();
695 let empty_memory_mapping =
696 MemoryMapping::new(Vec::new(), &mock_config, SBPFVersion::V0).unwrap();
697 let mut vm = EbpfVm::new(
698 self.program_cache_for_tx_batch
699 .environments
700 .program_runtime_v2
701 .clone(),
702 SBPFVersion::V0,
703 unsafe {
705 std::mem::transmute::<&mut InvokeContext<'_, '_>, &mut InvokeContext<'_, '_>>(self)
706 },
707 empty_memory_mapping,
708 0,
709 );
710 vm.invoke_function(function);
711 let result = match vm.program_result {
712 ProgramResult::Ok(_) => {
713 stable_log::program_success(&logger, &program_id);
714 Ok(())
715 }
716 ProgramResult::Err(ref err) => {
717 if let EbpfError::SyscallError(syscall_error) = err {
718 if let Some(instruction_err) = syscall_error.downcast_ref::<InstructionError>()
719 {
720 stable_log::program_failure(&logger, &program_id, instruction_err);
721 Err(instruction_err.clone())
722 } else {
723 stable_log::program_failure(&logger, &program_id, syscall_error);
724 Err(InstructionError::ProgramFailedToComplete)
725 }
726 } else {
727 stable_log::program_failure(&logger, &program_id, err);
728 Err(InstructionError::ProgramFailedToComplete)
729 }
730 }
731 };
732 let post_remaining_units = self.get_remaining();
733 *compute_units_consumed = pre_remaining_units.saturating_sub(post_remaining_units);
734
735 if builtin_id == program_id && result.is_ok() && *compute_units_consumed == 0 {
736 return Err(InstructionError::BuiltinProgramsMustConsumeComputeUnits);
737 }
738
739 timings
740 .execute_accessories
741 .process_instructions
742 .process_executable_chain_us += process_executable_chain_time.end_as_us();
743 result
744 }
745
746 pub fn get_log_collector(&self) -> Option<Rc<RefCell<LogCollector>>> {
748 self.log_collector.clone()
749 }
750
751 pub fn consume_checked(&self, amount: u64) -> Result<(), Box<dyn std::error::Error>> {
753 let mut compute_meter = self.compute_meter.borrow_mut();
754 let exceeded = *compute_meter < amount;
755 *compute_meter = compute_meter.saturating_sub(amount);
756 if exceeded {
757 return Err(Box::new(InstructionError::ComputationalBudgetExceeded));
758 }
759 Ok(())
760 }
761
762 pub fn mock_set_remaining(&self, remaining: u64) {
766 *self.compute_meter.borrow_mut() = remaining;
767 }
768
769 pub fn get_compute_budget(&self) -> &ComputeBudget {
771 &self.compute_budget
772 }
773
774 pub fn get_feature_set(&self) -> &FeatureSet {
776 &self.environment_config.feature_set
777 }
778
779 pub fn mock_set_feature_set(&mut self, feature_set: Arc<FeatureSet>) {
783 self.environment_config.feature_set = feature_set;
784 }
785
786 pub fn get_sysvar_cache(&self) -> &SysvarCache {
788 self.environment_config.sysvar_cache
789 }
790
791 pub fn get_epoch_vote_account_stake(&self, pubkey: &'a Pubkey) -> u64 {
793 (self
794 .environment_config
795 .get_epoch_vote_account_stake_callback)(pubkey)
796 }
797
798 pub fn get_current_epoch(&self) -> u64 {
808 self.environment_config
809 .stakes_handle
810 .pending_epoch()
811 .saturating_sub(1)
812 }
813
814 pub fn get_next_epoch(&self) -> u64 {
821 self.environment_config.stakes_handle.pending_epoch()
822 }
823
824 pub fn get_last_freeze_timestamp(&self) -> u64 {
839 self.environment_config
840 .stakes_handle
841 .last_frozen_timestamp()
842 .unwrap_or(0)
843 }
844
845 pub fn mock_set_last_freeze_timestamp(&mut self, timestamp: u64) {
856 self.environment_config.stakes_handle.set_pending_epoch(1);
858
859 let history_entry = StakeCacheData {
861 epoch: 0,
862 timestamp,
863 ..Default::default()
864 };
865 self.environment_config
866 .stakes_handle
867 .push_frozen(history_entry);
868 }
869
870 pub fn mock_insert_stake_account(
877 &mut self,
878 pubkey: rialo_s_pubkey::Pubkey,
879 account: rialo_stake_cache_interface::StakeAccount,
880 ) {
881 self.environment_config
882 .stakes_handle
883 .insert_stake_account(pubkey, account);
884 }
885
886 pub fn freeze_stakes(&self) {
899 self.environment_config.stakes_handle.freeze_stakes();
900 }
901
902 pub fn signal_freeze_stakes(&self) {
909 self.environment_config
910 .stakes_handle
911 .set_epoch_stakes_frozen();
912 }
913
914 pub fn get_all_validator_accounts_from_last_frozen(&self) -> Vec<(Pubkey, ValidatorAccount)> {
923 self.environment_config
924 .stakes_handle
925 .get_all_validator_accounts_from_last_frozen()
926 }
927
928 pub fn frozen_stake_history_len(&self) -> usize {
933 self.environment_config.stakes_handle.frozen_len()
934 }
935
936 pub fn request_epoch_rewards_init(&self, epoch: u64, total_rewards: u64) {
946 self.environment_config
947 .stakes_handle
948 .request_epoch_rewards_init(epoch, total_rewards);
949 }
950
951 pub fn front_frozen_epoch(&self) -> Option<u64> {
958 self.environment_config.stakes_handle.front_frozen_epoch()
959 }
960
961 pub fn is_validator_referenced(
984 &self,
985 validator: &Pubkey,
986 validator_info: &rialo_stake_cache_interface::ValidatorInfo,
987 last_freeze_timestamp: u64,
988 current_timestamp: u64,
989 ) -> bool {
990 self.environment_config
991 .stakes_handle
992 .is_validator_referenced(
993 validator,
994 validator_info,
995 last_freeze_timestamp,
996 current_timestamp,
997 )
998 }
999
1000 pub fn has_locked_stakers(
1015 &self,
1016 validator: &Pubkey,
1017 lockup_period: u64,
1018 current_timestamp: u64,
1019 ) -> bool {
1020 self.environment_config.stakes_handle.has_locked_stakers(
1021 validator,
1022 lockup_period,
1023 current_timestamp,
1024 )
1025 }
1026
1027 pub fn is_validator_referenced_excluding_self_bond(
1033 &self,
1034 validator_pubkey: &Pubkey,
1035 validator_info: &rialo_stake_cache_interface::ValidatorInfo,
1036 last_freeze_timestamp: u64,
1037 current_timestamp: u64,
1038 ) -> bool {
1039 self.environment_config
1040 .stakes_handle
1041 .is_validator_referenced_excluding_self_bond(
1042 validator_pubkey,
1043 validator_info,
1044 last_freeze_timestamp,
1045 current_timestamp,
1046 )
1047 }
1048
1049 pub fn get_stake_account_from_pending(
1054 &self,
1055 pubkey: &Pubkey,
1056 ) -> Option<rialo_stake_cache_interface::StakeAccount> {
1057 self.environment_config
1058 .stakes_handle
1059 .get_stake_account_from_pending(pubkey)
1060 }
1061
1062 pub fn is_epoch_rewards_init_pending(&self) -> bool {
1067 self.environment_config
1068 .stakes_handle
1069 .is_epoch_rewards_init_pending()
1070 }
1071
1072 pub fn completed_frozen_epochs(&self) -> Vec<u64> {
1078 self.environment_config
1079 .stakes_handle
1080 .completed_frozen_epochs()
1081 }
1082
1083 pub fn epoch_rewards_exists(&self, epoch: u64) -> bool {
1089 self.environment_config
1090 .stakes_handle
1091 .epoch_rewards_exists(epoch)
1092 }
1093
1094 pub fn get_check_aligned(&self) -> bool {
1096 self.transaction_context
1097 .get_current_instruction_context()
1098 .and_then(|instruction_context| {
1099 let program_account =
1100 instruction_context.try_borrow_last_program_account(self.transaction_context);
1101 debug_assert!(program_account.is_ok());
1102 program_account
1103 })
1104 .map(|program_account| *program_account.get_owner() != bpf_loader_deprecated::id())
1105 .unwrap_or(true)
1106 }
1107
1108 pub fn set_syscall_context(
1110 &mut self,
1111 syscall_context: SyscallContext,
1112 ) -> Result<(), InstructionError> {
1113 *self
1114 .syscall_context
1115 .last_mut()
1116 .ok_or(InstructionError::CallDepth)? = Some(syscall_context);
1117 Ok(())
1118 }
1119
1120 pub fn get_syscall_context(&self) -> Result<&SyscallContext, InstructionError> {
1122 self.syscall_context
1123 .last()
1124 .and_then(std::option::Option::as_ref)
1125 .ok_or(InstructionError::CallDepth)
1126 }
1127
1128 pub fn get_syscall_context_mut(&mut self) -> Result<&mut SyscallContext, InstructionError> {
1130 self.syscall_context
1131 .last_mut()
1132 .and_then(|syscall_context| syscall_context.as_mut())
1133 .ok_or(InstructionError::CallDepth)
1134 }
1135
1136 pub fn get_traces(&self) -> &Vec<Vec<[u64; 12]>> {
1138 &self.traces
1139 }
1140}
1141
1142#[macro_export]
1143macro_rules! with_mock_invoke_context {
1144 (
1145 $invoke_context:ident,
1146 $transaction_context:ident,
1147 $entry:expr,
1148 $transaction_accounts:expr $(,)?
1149 ) => {
1150 use rialo_s_compute_budget::compute_budget::ComputeBudget;
1151 use rialo_s_feature_set::FeatureSet;
1152 use rialo_s_log_collector::LogCollector;
1153 use rialo_s_type_overrides::sync::Arc;
1154 use $crate::{
1155 __private::{Hash, ReadableAccount, Rent, TransactionContext},
1156 invoke_context::{EnvironmentConfig, InvokeContext},
1157 loaded_programs::{ProgramCacheEntry, ProgramCacheForTx, ProgramCacheForTxBatch},
1158 sysvar_cache::SysvarCache,
1159 };
1160 let compute_budget = ComputeBudget::default();
1161 let mut $transaction_context = TransactionContext::new(
1162 $transaction_accounts,
1163 Rent::default(),
1164 compute_budget.max_instruction_stack_depth,
1165 compute_budget.max_instruction_trace_length,
1166 );
1167 let mut sysvar_cache = SysvarCache::default();
1168 sysvar_cache.fill_missing_entries(|pubkey, callback| {
1169 for index in 0..$transaction_context.get_number_of_accounts() {
1170 if $transaction_context
1171 .get_key_of_account_at_index(index)
1172 .unwrap()
1173 == pubkey
1174 {
1175 callback(
1176 $transaction_context
1177 .get_account_at_index(index)
1178 .unwrap()
1179 .borrow()
1180 .data(),
1181 );
1182 }
1183 }
1184 });
1185 let mock_stakes_handle = $crate::invoke_context::StakesHandle::default();
1189 let environment_config = EnvironmentConfig::new(
1190 Hash::default(),
1191 0,
1192 &|_| 0,
1193 Arc::new(FeatureSet::all_enabled()),
1194 &sysvar_cache,
1195 0,
1196 mock_stakes_handle,
1197 );
1198 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1199 if let Some((loader_id, builtin)) = $entry {
1200 program_cache_for_tx_batch.replenish(
1201 loader_id,
1202 Arc::new(ProgramCacheEntry::new_builtin(0, 0, builtin)),
1203 );
1204 }
1205 let mut program_cache_for_tx = ProgramCacheForTx::from_cache(&program_cache_for_tx_batch);
1206 let mut $invoke_context = InvokeContext::new(
1207 &mut $transaction_context,
1208 &mut program_cache_for_tx,
1209 environment_config,
1210 Some(LogCollector::new_ref()),
1211 compute_budget,
1212 );
1213 };
1214 (
1215 $invoke_context:ident,
1216 $transaction_context:ident,
1217 $transaction_accounts:expr $(,)?
1218 ) => {
1219 with_mock_invoke_context!(
1220 $invoke_context,
1221 $transaction_context,
1222 None,
1223 $transaction_accounts
1224 )
1225 };
1226}
1227
1228pub fn mock_process_instruction<
1229 T: Into<StoredAccount>,
1230 F: FnMut(&mut InvokeContext<'_, '_>),
1231 G: FnMut(&mut InvokeContext<'_, '_>),
1232>(
1233 loader_id: &Pubkey,
1234 mut program_indices: Vec<IndexOfAccount>,
1235 instruction_data: &[u8],
1236 transaction_accounts: Vec<(Pubkey, T)>,
1237 instruction_account_metas: Vec<AccountMeta>,
1238 expected_result: Result<(), InstructionError>,
1239 builtin_function: BuiltinFunctionWithContext,
1240 mut pre_adjustments: F,
1241 mut post_adjustments: G,
1242) -> Vec<StoredAccount> {
1243 let mut transaction_accounts: Vec<TransactionAccount> = transaction_accounts
1245 .into_iter()
1246 .map(|(key, account)| (key, account.into()))
1247 .collect();
1248 let mut instruction_accounts: Vec<InstructionAccount> =
1249 Vec::with_capacity(instruction_account_metas.len());
1250 for (instruction_account_index, account_meta) in instruction_account_metas.iter().enumerate() {
1251 let index_in_transaction = transaction_accounts
1252 .iter()
1253 .position(|(key, _account)| *key == account_meta.pubkey)
1254 .unwrap_or(transaction_accounts.len())
1255 as IndexOfAccount;
1256 let index_in_callee = instruction_accounts
1257 .get(0..instruction_account_index)
1258 .unwrap()
1259 .iter()
1260 .position(|instruction_account| {
1261 instruction_account.index_in_transaction == index_in_transaction
1262 })
1263 .unwrap_or(instruction_account_index) as IndexOfAccount;
1264 instruction_accounts.push(InstructionAccount {
1265 index_in_transaction,
1266 index_in_caller: index_in_transaction,
1267 index_in_callee,
1268 is_signer: account_meta.is_signer,
1269 is_writable: account_meta.is_writable,
1270 });
1271 }
1272 if program_indices.is_empty() {
1273 program_indices.insert(0, transaction_accounts.len() as IndexOfAccount);
1274 let processor_account =
1275 StoredAccount::Data(AccountSharedData::new(0, 0, &native_loader::id()));
1276 transaction_accounts.push((*loader_id, processor_account));
1277 }
1278 let pop_epoch_schedule_account = if !transaction_accounts
1279 .iter()
1280 .any(|(key, _)| *key == sysvar::epoch_schedule::id())
1281 {
1282 transaction_accounts.push((
1283 sysvar::epoch_schedule::id(),
1284 create_account_shared_data_for_test(&EpochSchedule::default()),
1285 ));
1286 true
1287 } else {
1288 false
1289 };
1290 with_mock_invoke_context!(
1291 invoke_context,
1292 transaction_context,
1293 Some((*loader_id, builtin_function)),
1294 transaction_accounts
1295 );
1296 pre_adjustments(&mut invoke_context);
1297 let result = invoke_context.process_instruction(
1298 instruction_data,
1299 &instruction_accounts,
1300 &program_indices,
1301 &mut 0,
1302 &mut ExecuteTimings::default(),
1303 );
1304 assert_eq!(result, expected_result);
1305 post_adjustments(&mut invoke_context);
1306 drop(invoke_context);
1307 let mut transaction_accounts = transaction_context.deconstruct_without_keys().unwrap();
1308 if pop_epoch_schedule_account {
1309 transaction_accounts.pop();
1310 }
1311 transaction_accounts.pop();
1312 transaction_accounts
1313}
1314
1315#[cfg(test)]
1316mod tests {
1317 use rialo_s_compute_budget::compute_budget_limits;
1318 use rialo_s_instruction::Instruction;
1319 use rialo_s_rent::Rent;
1320 use serde::{Deserialize, Serialize};
1321
1322 use super::*;
1323
1324 #[derive(Debug, Serialize, Deserialize)]
1325 enum MockInstruction {
1326 NoopSuccess,
1327 NoopFail,
1328 ModifyOwned,
1329 ModifyNotOwned,
1330 ModifyReadonly,
1331 UnbalancedPush,
1332 UnbalancedPop,
1333 ConsumeComputeUnits {
1334 compute_units_to_consume: u64,
1335 desired_result: Result<(), InstructionError>,
1336 },
1337 Resize {
1338 new_len: u64,
1339 },
1340 }
1341
1342 const MOCK_BUILTIN_COMPUTE_UNIT_COST: u64 = 1;
1343
1344 declare_process_instruction!(
1345 MockBuiltin,
1346 MOCK_BUILTIN_COMPUTE_UNIT_COST,
1347 |invoke_context| {
1348 let transaction_context = &invoke_context.transaction_context;
1349 let instruction_context = transaction_context.get_current_instruction_context()?;
1350 let instruction_data = instruction_context.get_instruction_data();
1351 let program_id = instruction_context.get_last_program_key(transaction_context)?;
1352 let instruction_accounts = (0..4)
1353 .map(|instruction_account_index| InstructionAccount {
1354 index_in_transaction: instruction_account_index,
1355 index_in_caller: instruction_account_index,
1356 index_in_callee: instruction_account_index,
1357 is_signer: false,
1358 is_writable: false,
1359 })
1360 .collect::<Vec<_>>();
1361 assert_eq!(
1362 program_id,
1363 instruction_context
1364 .try_borrow_instruction_account(transaction_context, 0)?
1365 .get_owner()
1366 );
1367 assert_ne!(
1368 instruction_context
1369 .try_borrow_instruction_account(transaction_context, 1)?
1370 .get_owner(),
1371 instruction_context
1372 .try_borrow_instruction_account(transaction_context, 0)?
1373 .get_key()
1374 );
1375
1376 if let Ok(instruction) = bincode::deserialize(instruction_data) {
1377 match instruction {
1378 MockInstruction::NoopSuccess => (),
1379 MockInstruction::NoopFail => return Err(InstructionError::GenericError),
1380 MockInstruction::ModifyOwned => instruction_context
1381 .try_borrow_instruction_account(transaction_context, 0)?
1382 .set_data_from_slice(&[1])?,
1383 MockInstruction::ModifyNotOwned => instruction_context
1384 .try_borrow_instruction_account(transaction_context, 1)?
1385 .set_data_from_slice(&[1])?,
1386 MockInstruction::ModifyReadonly => instruction_context
1387 .try_borrow_instruction_account(transaction_context, 2)?
1388 .set_data_from_slice(&[1])?,
1389 MockInstruction::UnbalancedPush => {
1390 instruction_context
1391 .try_borrow_instruction_account(transaction_context, 0)?
1392 .checked_add_kelvins(1)?;
1393 let program_id = *transaction_context.get_key_of_account_at_index(3)?;
1394 let metas = vec![
1395 AccountMeta::new_readonly(
1396 *transaction_context.get_key_of_account_at_index(0)?,
1397 false,
1398 ),
1399 AccountMeta::new_readonly(
1400 *transaction_context.get_key_of_account_at_index(1)?,
1401 false,
1402 ),
1403 ];
1404 let inner_instruction = Instruction::new_with_bincode(
1405 program_id,
1406 &MockInstruction::NoopSuccess,
1407 metas,
1408 );
1409 invoke_context
1410 .transaction_context
1411 .get_next_instruction_context()
1412 .unwrap()
1413 .configure(&[3], &instruction_accounts, &[]);
1414 let result = invoke_context.push();
1415 assert_eq!(result, Err(InstructionError::UnbalancedInstruction));
1416 result?;
1417 invoke_context
1418 .native_invoke(inner_instruction.into(), &[])
1419 .and(invoke_context.pop())?;
1420 }
1421 MockInstruction::UnbalancedPop => instruction_context
1422 .try_borrow_instruction_account(transaction_context, 0)?
1423 .checked_add_kelvins(1)?,
1424 MockInstruction::ConsumeComputeUnits {
1425 compute_units_to_consume,
1426 desired_result,
1427 } => {
1428 invoke_context
1429 .consume_checked(compute_units_to_consume)
1430 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
1431 return desired_result;
1432 }
1433 MockInstruction::Resize { new_len } => instruction_context
1434 .try_borrow_instruction_account(transaction_context, 0)?
1435 .set_data(vec![0; new_len as usize])?,
1436 }
1437 } else {
1438 return Err(InstructionError::InvalidInstructionData);
1439 }
1440 Ok(())
1441 }
1442 );
1443
1444 #[test]
1445 fn test_instruction_stack_height() {
1446 let one_more_than_max_depth = ComputeBudget::default()
1447 .max_instruction_stack_depth
1448 .saturating_add(1);
1449 let mut invoke_stack = vec![];
1450 let mut transaction_accounts = vec![];
1451 let mut instruction_accounts = vec![];
1452 for index in 0..one_more_than_max_depth {
1453 invoke_stack.push(rialo_s_pubkey::new_rand());
1454 transaction_accounts.push((
1455 rialo_s_pubkey::new_rand(),
1456 AccountSharedData::new(index as u64, 1, invoke_stack.get(index).unwrap()),
1457 ));
1458 instruction_accounts.push(InstructionAccount {
1459 index_in_transaction: index as IndexOfAccount,
1460 index_in_caller: index as IndexOfAccount,
1461 index_in_callee: instruction_accounts.len() as IndexOfAccount,
1462 is_signer: false,
1463 is_writable: true,
1464 });
1465 }
1466 for (index, program_id) in invoke_stack.iter().enumerate() {
1467 transaction_accounts.push((
1468 *program_id,
1469 AccountSharedData::new(1, 1, &rialo_s_pubkey::Pubkey::default()),
1470 ));
1471 instruction_accounts.push(InstructionAccount {
1472 index_in_transaction: index as IndexOfAccount,
1473 index_in_caller: index as IndexOfAccount,
1474 index_in_callee: index as IndexOfAccount,
1475 is_signer: false,
1476 is_writable: false,
1477 });
1478 }
1479 let transaction_accounts: Vec<(Pubkey, StoredAccount)> = transaction_accounts
1480 .into_iter()
1481 .map(|(k, v)| (k, v.into()))
1482 .collect();
1483 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1484
1485 let mut depth_reached = 0;
1487 for _ in 0..invoke_stack.len() {
1488 invoke_context
1489 .transaction_context
1490 .get_next_instruction_context()
1491 .unwrap()
1492 .configure(
1493 &[one_more_than_max_depth.saturating_add(depth_reached) as IndexOfAccount],
1494 &instruction_accounts,
1495 &[],
1496 );
1497 if Err(InstructionError::CallDepth) == invoke_context.push() {
1498 break;
1499 }
1500 depth_reached = depth_reached.saturating_add(1);
1501 }
1502 assert_ne!(depth_reached, 0);
1503 assert!(depth_reached < one_more_than_max_depth);
1504 }
1505
1506 #[test]
1507 fn test_max_instruction_trace_length() {
1508 const MAX_INSTRUCTIONS: usize = 8;
1509 let mut transaction_context = TransactionContext::new::<StoredAccount>(
1510 Vec::new(),
1511 Rent::default(),
1512 1,
1513 MAX_INSTRUCTIONS,
1514 );
1515 for _ in 0..MAX_INSTRUCTIONS {
1516 transaction_context.push().unwrap();
1517 transaction_context.pop().unwrap();
1518 }
1519 assert_eq!(
1520 transaction_context.push(),
1521 Err(InstructionError::MaxInstructionTraceLengthExceeded)
1522 );
1523 }
1524
1525 #[test]
1526 fn test_process_instruction() {
1527 let callee_program_id = rialo_s_pubkey::new_rand();
1528 let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1529 let not_owned_account = AccountSharedData::new(84, 1, &rialo_s_pubkey::new_rand());
1530 let readonly_account = AccountSharedData::new(168, 1, &rialo_s_pubkey::new_rand());
1531 let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1532 let program_account = AccountSharedData::new(1, 1, &native_loader::id());
1533 let transaction_accounts = vec![
1534 (rialo_s_pubkey::new_rand(), owned_account),
1535 (rialo_s_pubkey::new_rand(), not_owned_account),
1536 (rialo_s_pubkey::new_rand(), readonly_account),
1537 (callee_program_id, program_account),
1538 (rialo_s_pubkey::new_rand(), loader_account),
1539 ];
1540 let metas = vec![
1541 AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1542 AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1543 AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1544 ];
1545 let instruction_accounts = (0..4)
1546 .map(|instruction_account_index| InstructionAccount {
1547 index_in_transaction: instruction_account_index,
1548 index_in_caller: instruction_account_index,
1549 index_in_callee: instruction_account_index,
1550 is_signer: false,
1551 is_writable: instruction_account_index < 2,
1552 })
1553 .collect::<Vec<_>>();
1554 let transaction_accounts: Vec<(Pubkey, StoredAccount)> = transaction_accounts
1555 .into_iter()
1556 .map(|(k, v)| (k, v.into()))
1557 .collect();
1558 with_mock_invoke_context!(
1559 invoke_context,
1560 transaction_context,
1561 Some((callee_program_id, MockBuiltin::vm)),
1562 transaction_accounts
1563 );
1564
1565 let cases = vec![
1567 (MockInstruction::NoopSuccess, Ok(())),
1568 (
1569 MockInstruction::NoopFail,
1570 Err(InstructionError::GenericError),
1571 ),
1572 (MockInstruction::ModifyOwned, Ok(())),
1573 (
1574 MockInstruction::ModifyNotOwned,
1575 Err(InstructionError::ExternalAccountDataModified),
1576 ),
1577 (
1578 MockInstruction::ModifyReadonly,
1579 Err(InstructionError::ReadonlyDataModified),
1580 ),
1581 (
1582 MockInstruction::UnbalancedPush,
1583 Err(InstructionError::UnbalancedInstruction),
1584 ),
1585 (
1586 MockInstruction::UnbalancedPop,
1587 Err(InstructionError::UnbalancedInstruction),
1588 ),
1589 ];
1590 for case in cases {
1591 invoke_context
1592 .transaction_context
1593 .get_next_instruction_context()
1594 .unwrap()
1595 .configure(&[4], &instruction_accounts, &[]);
1596 invoke_context.push().unwrap();
1597 let inner_instruction =
1598 Instruction::new_with_bincode(callee_program_id, &case.0, metas.clone());
1599 let result = invoke_context
1600 .native_invoke(inner_instruction.into(), &[])
1601 .and(invoke_context.pop());
1602 assert_eq!(result, case.1);
1603 }
1604
1605 let compute_units_to_consume = 10;
1607 let expected_results = vec![Ok(()), Err(InstructionError::GenericError)];
1608 for expected_result in expected_results {
1609 invoke_context
1610 .transaction_context
1611 .get_next_instruction_context()
1612 .unwrap()
1613 .configure(&[4], &instruction_accounts, &[]);
1614 invoke_context.push().unwrap();
1615 let inner_instruction = Instruction::new_with_bincode(
1616 callee_program_id,
1617 &MockInstruction::ConsumeComputeUnits {
1618 compute_units_to_consume,
1619 desired_result: expected_result.clone(),
1620 },
1621 metas.clone(),
1622 );
1623 let inner_instruction = StableInstruction::from(inner_instruction);
1624 let (inner_instruction_accounts, program_indices) = invoke_context
1625 .prepare_instruction(&inner_instruction, &[])
1626 .unwrap();
1627
1628 let mut compute_units_consumed = 0;
1629 let result = invoke_context.process_instruction(
1630 &inner_instruction.data,
1631 &inner_instruction_accounts,
1632 &program_indices,
1633 &mut compute_units_consumed,
1634 &mut ExecuteTimings::default(),
1635 );
1636
1637 assert!(compute_units_consumed > 0);
1641 assert_eq!(
1642 compute_units_consumed,
1643 compute_units_to_consume.saturating_add(MOCK_BUILTIN_COMPUTE_UNIT_COST),
1644 );
1645 assert_eq!(result, expected_result);
1646
1647 invoke_context.pop().unwrap();
1648 }
1649 }
1650
1651 #[test]
1652 fn test_invoke_context_compute_budget() {
1653 let transaction_accounts = vec![(rialo_s_pubkey::new_rand(), AccountSharedData::default())];
1654 let transaction_accounts: Vec<(Pubkey, StoredAccount)> = transaction_accounts
1655 .into_iter()
1656 .map(|(k, v)| (k, v.into()))
1657 .collect();
1658
1659 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1660 invoke_context.compute_budget = ComputeBudget::new(
1661 compute_budget_limits::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64,
1662 );
1663
1664 invoke_context
1665 .transaction_context
1666 .get_next_instruction_context()
1667 .unwrap()
1668 .configure(&[0], &[], &[]);
1669 invoke_context.push().unwrap();
1670 assert_eq!(
1671 *invoke_context.get_compute_budget(),
1672 ComputeBudget::new(
1673 compute_budget_limits::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64
1674 )
1675 );
1676 invoke_context.pop().unwrap();
1677 }
1678
1679 #[test]
1680 fn test_process_instruction_accounts_resize_delta() {
1681 let program_key = Pubkey::new_unique();
1682 let user_account_data_len = 123u64;
1683 let user_account =
1684 AccountSharedData::new(100, user_account_data_len as usize, &program_key);
1685 let dummy_account = AccountSharedData::new(10, 0, &program_key);
1686 let program_account = AccountSharedData::new(500, 500, &native_loader::id());
1687 let transaction_accounts = vec![
1688 (Pubkey::new_unique(), user_account),
1689 (Pubkey::new_unique(), dummy_account),
1690 (program_key, program_account),
1691 ];
1692 let instruction_accounts = [
1693 InstructionAccount {
1694 index_in_transaction: 0,
1695 index_in_caller: 0,
1696 index_in_callee: 0,
1697 is_signer: false,
1698 is_writable: true,
1699 },
1700 InstructionAccount {
1701 index_in_transaction: 1,
1702 index_in_caller: 1,
1703 index_in_callee: 1,
1704 is_signer: false,
1705 is_writable: false,
1706 },
1707 ];
1708 let transaction_accounts: Vec<(Pubkey, StoredAccount)> = transaction_accounts
1709 .into_iter()
1710 .map(|(k, v)| (k, v.into()))
1711 .collect();
1712 with_mock_invoke_context!(
1713 invoke_context,
1714 transaction_context,
1715 Some((program_key, MockBuiltin::vm)),
1716 transaction_accounts
1717 );
1718
1719 {
1721 let resize_delta: i64 = 0;
1722 let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
1723 let instruction_data =
1724 bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1725
1726 let result = invoke_context.process_instruction(
1727 &instruction_data,
1728 &instruction_accounts,
1729 &[2],
1730 &mut 0,
1731 &mut ExecuteTimings::default(),
1732 );
1733
1734 assert!(result.is_ok());
1735 assert_eq!(
1736 invoke_context
1737 .transaction_context
1738 .accounts_resize_delta()
1739 .unwrap(),
1740 resize_delta
1741 );
1742 }
1743
1744 {
1746 let resize_delta: i64 = 1;
1747 let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
1748 let instruction_data =
1749 bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1750
1751 let result = invoke_context.process_instruction(
1752 &instruction_data,
1753 &instruction_accounts,
1754 &[2],
1755 &mut 0,
1756 &mut ExecuteTimings::default(),
1757 );
1758
1759 assert!(result.is_ok());
1760 assert_eq!(
1761 invoke_context
1762 .transaction_context
1763 .accounts_resize_delta()
1764 .unwrap(),
1765 resize_delta
1766 );
1767 }
1768
1769 {
1771 let resize_delta: i64 = -1;
1772 let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
1773 let instruction_data =
1774 bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1775
1776 let result = invoke_context.process_instruction(
1777 &instruction_data,
1778 &instruction_accounts,
1779 &[2],
1780 &mut 0,
1781 &mut ExecuteTimings::default(),
1782 );
1783
1784 assert!(result.is_ok());
1785 assert_eq!(
1786 invoke_context
1787 .transaction_context
1788 .accounts_resize_delta()
1789 .unwrap(),
1790 resize_delta
1791 );
1792 }
1793 }
1794}