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 is_previous_epoch_adopted(&self) -> bool {
1099 self.environment_config
1100 .stakes_handle
1101 .is_previous_epoch_adopted()
1102 }
1103
1104 #[cfg(feature = "testing")]
1113 pub fn signal_handover(&self, ts: u64) {
1114 self.environment_config.stakes_handle.signal_handover(ts);
1115 }
1116
1117 pub fn get_check_aligned(&self) -> bool {
1119 self.transaction_context
1120 .get_current_instruction_context()
1121 .and_then(|instruction_context| {
1122 let program_account =
1123 instruction_context.try_borrow_last_program_account(self.transaction_context);
1124 debug_assert!(program_account.is_ok());
1125 program_account
1126 })
1127 .map(|program_account| *program_account.get_owner() != bpf_loader_deprecated::id())
1128 .unwrap_or(true)
1129 }
1130
1131 pub fn set_syscall_context(
1133 &mut self,
1134 syscall_context: SyscallContext,
1135 ) -> Result<(), InstructionError> {
1136 *self
1137 .syscall_context
1138 .last_mut()
1139 .ok_or(InstructionError::CallDepth)? = Some(syscall_context);
1140 Ok(())
1141 }
1142
1143 pub fn get_syscall_context(&self) -> Result<&SyscallContext, InstructionError> {
1145 self.syscall_context
1146 .last()
1147 .and_then(std::option::Option::as_ref)
1148 .ok_or(InstructionError::CallDepth)
1149 }
1150
1151 pub fn get_syscall_context_mut(&mut self) -> Result<&mut SyscallContext, InstructionError> {
1153 self.syscall_context
1154 .last_mut()
1155 .and_then(|syscall_context| syscall_context.as_mut())
1156 .ok_or(InstructionError::CallDepth)
1157 }
1158
1159 pub fn get_traces(&self) -> &Vec<Vec<[u64; 12]>> {
1161 &self.traces
1162 }
1163}
1164
1165#[macro_export]
1166macro_rules! with_mock_invoke_context {
1167 (
1168 $invoke_context:ident,
1169 $transaction_context:ident,
1170 $entry:expr,
1171 $transaction_accounts:expr $(,)?
1172 ) => {
1173 use rialo_s_compute_budget::compute_budget::ComputeBudget;
1174 use rialo_s_feature_set::FeatureSet;
1175 use rialo_s_log_collector::LogCollector;
1176 use rialo_s_type_overrides::sync::Arc;
1177 use $crate::{
1178 __private::{Hash, ReadableAccount, Rent, TransactionContext},
1179 invoke_context::{EnvironmentConfig, InvokeContext},
1180 loaded_programs::{ProgramCacheEntry, ProgramCacheForTx, ProgramCacheForTxBatch},
1181 sysvar_cache::SysvarCache,
1182 };
1183 let compute_budget = ComputeBudget::default();
1184 let mut $transaction_context = TransactionContext::new(
1185 $transaction_accounts,
1186 Rent::default(),
1187 compute_budget.max_instruction_stack_depth,
1188 compute_budget.max_instruction_trace_length,
1189 );
1190 let mut sysvar_cache = SysvarCache::default();
1191 sysvar_cache.fill_missing_entries(|pubkey, callback| {
1192 for index in 0..$transaction_context.get_number_of_accounts() {
1193 if $transaction_context
1194 .get_key_of_account_at_index(index)
1195 .unwrap()
1196 == pubkey
1197 {
1198 callback(
1199 $transaction_context
1200 .get_account_at_index(index)
1201 .unwrap()
1202 .borrow()
1203 .data(),
1204 );
1205 }
1206 }
1207 });
1208 let mock_stakes_handle = $crate::invoke_context::StakesHandle::default();
1212 let environment_config = EnvironmentConfig::new(
1213 Hash::default(),
1214 0,
1215 &|_| 0,
1216 Arc::new(FeatureSet::all_enabled()),
1217 &sysvar_cache,
1218 0,
1219 mock_stakes_handle,
1220 );
1221 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1222 if let Some((loader_id, builtin)) = $entry {
1223 program_cache_for_tx_batch.replenish(
1224 loader_id,
1225 Arc::new(ProgramCacheEntry::new_builtin(0, 0, builtin)),
1226 );
1227 }
1228 let mut program_cache_for_tx = ProgramCacheForTx::from_cache(&program_cache_for_tx_batch);
1229 let mut $invoke_context = InvokeContext::new(
1230 &mut $transaction_context,
1231 &mut program_cache_for_tx,
1232 environment_config,
1233 Some(LogCollector::new_ref()),
1234 compute_budget,
1235 );
1236 };
1237 (
1238 $invoke_context:ident,
1239 $transaction_context:ident,
1240 $transaction_accounts:expr $(,)?
1241 ) => {
1242 with_mock_invoke_context!(
1243 $invoke_context,
1244 $transaction_context,
1245 None,
1246 $transaction_accounts
1247 )
1248 };
1249}
1250
1251pub fn mock_process_instruction<
1252 T: Into<StoredAccount>,
1253 F: FnMut(&mut InvokeContext<'_, '_>),
1254 G: FnMut(&mut InvokeContext<'_, '_>),
1255>(
1256 loader_id: &Pubkey,
1257 mut program_indices: Vec<IndexOfAccount>,
1258 instruction_data: &[u8],
1259 transaction_accounts: Vec<(Pubkey, T)>,
1260 instruction_account_metas: Vec<AccountMeta>,
1261 expected_result: Result<(), InstructionError>,
1262 builtin_function: BuiltinFunctionWithContext,
1263 mut pre_adjustments: F,
1264 mut post_adjustments: G,
1265) -> Vec<StoredAccount> {
1266 let mut transaction_accounts: Vec<TransactionAccount> = transaction_accounts
1268 .into_iter()
1269 .map(|(key, account)| (key, account.into()))
1270 .collect();
1271 let mut instruction_accounts: Vec<InstructionAccount> =
1272 Vec::with_capacity(instruction_account_metas.len());
1273 for (instruction_account_index, account_meta) in instruction_account_metas.iter().enumerate() {
1274 let index_in_transaction = transaction_accounts
1275 .iter()
1276 .position(|(key, _account)| *key == account_meta.pubkey)
1277 .unwrap_or(transaction_accounts.len())
1278 as IndexOfAccount;
1279 let index_in_callee = instruction_accounts
1280 .get(0..instruction_account_index)
1281 .unwrap()
1282 .iter()
1283 .position(|instruction_account| {
1284 instruction_account.index_in_transaction == index_in_transaction
1285 })
1286 .unwrap_or(instruction_account_index) as IndexOfAccount;
1287 instruction_accounts.push(InstructionAccount {
1288 index_in_transaction,
1289 index_in_caller: index_in_transaction,
1290 index_in_callee,
1291 is_signer: account_meta.is_signer,
1292 is_writable: account_meta.is_writable,
1293 });
1294 }
1295 if program_indices.is_empty() {
1296 program_indices.insert(0, transaction_accounts.len() as IndexOfAccount);
1297 let processor_account =
1298 StoredAccount::Data(AccountSharedData::new(0, 0, &native_loader::id()));
1299 transaction_accounts.push((*loader_id, processor_account));
1300 }
1301 let pop_epoch_schedule_account = if !transaction_accounts
1302 .iter()
1303 .any(|(key, _)| *key == sysvar::epoch_schedule::id())
1304 {
1305 transaction_accounts.push((
1306 sysvar::epoch_schedule::id(),
1307 create_account_shared_data_for_test(&EpochSchedule::default()),
1308 ));
1309 true
1310 } else {
1311 false
1312 };
1313 with_mock_invoke_context!(
1314 invoke_context,
1315 transaction_context,
1316 Some((*loader_id, builtin_function)),
1317 transaction_accounts
1318 );
1319 pre_adjustments(&mut invoke_context);
1320 let result = invoke_context.process_instruction(
1321 instruction_data,
1322 &instruction_accounts,
1323 &program_indices,
1324 &mut 0,
1325 &mut ExecuteTimings::default(),
1326 );
1327 assert_eq!(result, expected_result);
1328 post_adjustments(&mut invoke_context);
1329 drop(invoke_context);
1330 let mut transaction_accounts = transaction_context.deconstruct_without_keys().unwrap();
1331 if pop_epoch_schedule_account {
1332 transaction_accounts.pop();
1333 }
1334 transaction_accounts.pop();
1335 transaction_accounts
1336}
1337
1338#[cfg(test)]
1339mod tests {
1340 use rialo_s_compute_budget::compute_budget_limits;
1341 use rialo_s_instruction::Instruction;
1342 use rialo_s_rent::Rent;
1343 use serde::{Deserialize, Serialize};
1344
1345 use super::*;
1346
1347 #[derive(Debug, Serialize, Deserialize)]
1348 enum MockInstruction {
1349 NoopSuccess,
1350 NoopFail,
1351 ModifyOwned,
1352 ModifyNotOwned,
1353 ModifyReadonly,
1354 UnbalancedPush,
1355 UnbalancedPop,
1356 ConsumeComputeUnits {
1357 compute_units_to_consume: u64,
1358 desired_result: Result<(), InstructionError>,
1359 },
1360 Resize {
1361 new_len: u64,
1362 },
1363 }
1364
1365 const MOCK_BUILTIN_COMPUTE_UNIT_COST: u64 = 1;
1366
1367 declare_process_instruction!(
1368 MockBuiltin,
1369 MOCK_BUILTIN_COMPUTE_UNIT_COST,
1370 |invoke_context| {
1371 let transaction_context = &invoke_context.transaction_context;
1372 let instruction_context = transaction_context.get_current_instruction_context()?;
1373 let instruction_data = instruction_context.get_instruction_data();
1374 let program_id = instruction_context.get_last_program_key(transaction_context)?;
1375 let instruction_accounts = (0..4)
1376 .map(|instruction_account_index| InstructionAccount {
1377 index_in_transaction: instruction_account_index,
1378 index_in_caller: instruction_account_index,
1379 index_in_callee: instruction_account_index,
1380 is_signer: false,
1381 is_writable: false,
1382 })
1383 .collect::<Vec<_>>();
1384 assert_eq!(
1385 program_id,
1386 instruction_context
1387 .try_borrow_instruction_account(transaction_context, 0)?
1388 .get_owner()
1389 );
1390 assert_ne!(
1391 instruction_context
1392 .try_borrow_instruction_account(transaction_context, 1)?
1393 .get_owner(),
1394 instruction_context
1395 .try_borrow_instruction_account(transaction_context, 0)?
1396 .get_key()
1397 );
1398
1399 if let Ok(instruction) = bincode::deserialize(instruction_data) {
1400 match instruction {
1401 MockInstruction::NoopSuccess => (),
1402 MockInstruction::NoopFail => return Err(InstructionError::GenericError),
1403 MockInstruction::ModifyOwned => instruction_context
1404 .try_borrow_instruction_account(transaction_context, 0)?
1405 .set_data_from_slice(&[1])?,
1406 MockInstruction::ModifyNotOwned => instruction_context
1407 .try_borrow_instruction_account(transaction_context, 1)?
1408 .set_data_from_slice(&[1])?,
1409 MockInstruction::ModifyReadonly => instruction_context
1410 .try_borrow_instruction_account(transaction_context, 2)?
1411 .set_data_from_slice(&[1])?,
1412 MockInstruction::UnbalancedPush => {
1413 instruction_context
1414 .try_borrow_instruction_account(transaction_context, 0)?
1415 .checked_add_kelvins(1)?;
1416 let program_id = *transaction_context.get_key_of_account_at_index(3)?;
1417 let metas = vec![
1418 AccountMeta::new_readonly(
1419 *transaction_context.get_key_of_account_at_index(0)?,
1420 false,
1421 ),
1422 AccountMeta::new_readonly(
1423 *transaction_context.get_key_of_account_at_index(1)?,
1424 false,
1425 ),
1426 ];
1427 let inner_instruction = Instruction::new_with_bincode(
1428 program_id,
1429 &MockInstruction::NoopSuccess,
1430 metas,
1431 );
1432 invoke_context
1433 .transaction_context
1434 .get_next_instruction_context()
1435 .unwrap()
1436 .configure(&[3], &instruction_accounts, &[]);
1437 let result = invoke_context.push();
1438 assert_eq!(result, Err(InstructionError::UnbalancedInstruction));
1439 result?;
1440 invoke_context
1441 .native_invoke(inner_instruction.into(), &[])
1442 .and(invoke_context.pop())?;
1443 }
1444 MockInstruction::UnbalancedPop => instruction_context
1445 .try_borrow_instruction_account(transaction_context, 0)?
1446 .checked_add_kelvins(1)?,
1447 MockInstruction::ConsumeComputeUnits {
1448 compute_units_to_consume,
1449 desired_result,
1450 } => {
1451 invoke_context
1452 .consume_checked(compute_units_to_consume)
1453 .map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
1454 return desired_result;
1455 }
1456 MockInstruction::Resize { new_len } => instruction_context
1457 .try_borrow_instruction_account(transaction_context, 0)?
1458 .set_data(vec![0; new_len as usize])?,
1459 }
1460 } else {
1461 return Err(InstructionError::InvalidInstructionData);
1462 }
1463 Ok(())
1464 }
1465 );
1466
1467 #[test]
1468 fn test_instruction_stack_height() {
1469 let one_more_than_max_depth = ComputeBudget::default()
1470 .max_instruction_stack_depth
1471 .saturating_add(1);
1472 let mut invoke_stack = vec![];
1473 let mut transaction_accounts = vec![];
1474 let mut instruction_accounts = vec![];
1475 for index in 0..one_more_than_max_depth {
1476 invoke_stack.push(rialo_s_pubkey::new_rand());
1477 transaction_accounts.push((
1478 rialo_s_pubkey::new_rand(),
1479 AccountSharedData::new(index as u64, 1, invoke_stack.get(index).unwrap()),
1480 ));
1481 instruction_accounts.push(InstructionAccount {
1482 index_in_transaction: index as IndexOfAccount,
1483 index_in_caller: index as IndexOfAccount,
1484 index_in_callee: instruction_accounts.len() as IndexOfAccount,
1485 is_signer: false,
1486 is_writable: true,
1487 });
1488 }
1489 for (index, program_id) in invoke_stack.iter().enumerate() {
1490 transaction_accounts.push((
1491 *program_id,
1492 AccountSharedData::new(1, 1, &rialo_s_pubkey::Pubkey::default()),
1493 ));
1494 instruction_accounts.push(InstructionAccount {
1495 index_in_transaction: index as IndexOfAccount,
1496 index_in_caller: index as IndexOfAccount,
1497 index_in_callee: index as IndexOfAccount,
1498 is_signer: false,
1499 is_writable: false,
1500 });
1501 }
1502 let transaction_accounts: Vec<(Pubkey, StoredAccount)> = transaction_accounts
1503 .into_iter()
1504 .map(|(k, v)| (k, v.into()))
1505 .collect();
1506 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1507
1508 let mut depth_reached = 0;
1510 for _ in 0..invoke_stack.len() {
1511 invoke_context
1512 .transaction_context
1513 .get_next_instruction_context()
1514 .unwrap()
1515 .configure(
1516 &[one_more_than_max_depth.saturating_add(depth_reached) as IndexOfAccount],
1517 &instruction_accounts,
1518 &[],
1519 );
1520 if Err(InstructionError::CallDepth) == invoke_context.push() {
1521 break;
1522 }
1523 depth_reached = depth_reached.saturating_add(1);
1524 }
1525 assert_ne!(depth_reached, 0);
1526 assert!(depth_reached < one_more_than_max_depth);
1527 }
1528
1529 #[test]
1530 fn test_max_instruction_trace_length() {
1531 const MAX_INSTRUCTIONS: usize = 8;
1532 let mut transaction_context = TransactionContext::new::<StoredAccount>(
1533 Vec::new(),
1534 Rent::default(),
1535 1,
1536 MAX_INSTRUCTIONS,
1537 );
1538 for _ in 0..MAX_INSTRUCTIONS {
1539 transaction_context.push().unwrap();
1540 transaction_context.pop().unwrap();
1541 }
1542 assert_eq!(
1543 transaction_context.push(),
1544 Err(InstructionError::MaxInstructionTraceLengthExceeded)
1545 );
1546 }
1547
1548 #[test]
1549 fn test_process_instruction() {
1550 let callee_program_id = rialo_s_pubkey::new_rand();
1551 let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
1552 let not_owned_account = AccountSharedData::new(84, 1, &rialo_s_pubkey::new_rand());
1553 let readonly_account = AccountSharedData::new(168, 1, &rialo_s_pubkey::new_rand());
1554 let loader_account = AccountSharedData::new(0, 1, &native_loader::id());
1555 let program_account = AccountSharedData::new(1, 1, &native_loader::id());
1556 let transaction_accounts = vec![
1557 (rialo_s_pubkey::new_rand(), owned_account),
1558 (rialo_s_pubkey::new_rand(), not_owned_account),
1559 (rialo_s_pubkey::new_rand(), readonly_account),
1560 (callee_program_id, program_account),
1561 (rialo_s_pubkey::new_rand(), loader_account),
1562 ];
1563 let metas = vec![
1564 AccountMeta::new(transaction_accounts.first().unwrap().0, false),
1565 AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
1566 AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
1567 ];
1568 let instruction_accounts = (0..4)
1569 .map(|instruction_account_index| InstructionAccount {
1570 index_in_transaction: instruction_account_index,
1571 index_in_caller: instruction_account_index,
1572 index_in_callee: instruction_account_index,
1573 is_signer: false,
1574 is_writable: instruction_account_index < 2,
1575 })
1576 .collect::<Vec<_>>();
1577 let transaction_accounts: Vec<(Pubkey, StoredAccount)> = transaction_accounts
1578 .into_iter()
1579 .map(|(k, v)| (k, v.into()))
1580 .collect();
1581 with_mock_invoke_context!(
1582 invoke_context,
1583 transaction_context,
1584 Some((callee_program_id, MockBuiltin::vm)),
1585 transaction_accounts
1586 );
1587
1588 let cases = vec![
1590 (MockInstruction::NoopSuccess, Ok(())),
1591 (
1592 MockInstruction::NoopFail,
1593 Err(InstructionError::GenericError),
1594 ),
1595 (MockInstruction::ModifyOwned, Ok(())),
1596 (
1597 MockInstruction::ModifyNotOwned,
1598 Err(InstructionError::ExternalAccountDataModified),
1599 ),
1600 (
1601 MockInstruction::ModifyReadonly,
1602 Err(InstructionError::ReadonlyDataModified),
1603 ),
1604 (
1605 MockInstruction::UnbalancedPush,
1606 Err(InstructionError::UnbalancedInstruction),
1607 ),
1608 (
1609 MockInstruction::UnbalancedPop,
1610 Err(InstructionError::UnbalancedInstruction),
1611 ),
1612 ];
1613 for case in cases {
1614 invoke_context
1615 .transaction_context
1616 .get_next_instruction_context()
1617 .unwrap()
1618 .configure(&[4], &instruction_accounts, &[]);
1619 invoke_context.push().unwrap();
1620 let inner_instruction =
1621 Instruction::new_with_bincode(callee_program_id, &case.0, metas.clone());
1622 let result = invoke_context
1623 .native_invoke(inner_instruction.into(), &[])
1624 .and(invoke_context.pop());
1625 assert_eq!(result, case.1);
1626 }
1627
1628 let compute_units_to_consume = 10;
1630 let expected_results = vec![Ok(()), Err(InstructionError::GenericError)];
1631 for expected_result in expected_results {
1632 invoke_context
1633 .transaction_context
1634 .get_next_instruction_context()
1635 .unwrap()
1636 .configure(&[4], &instruction_accounts, &[]);
1637 invoke_context.push().unwrap();
1638 let inner_instruction = Instruction::new_with_bincode(
1639 callee_program_id,
1640 &MockInstruction::ConsumeComputeUnits {
1641 compute_units_to_consume,
1642 desired_result: expected_result.clone(),
1643 },
1644 metas.clone(),
1645 );
1646 let inner_instruction = StableInstruction::from(inner_instruction);
1647 let (inner_instruction_accounts, program_indices) = invoke_context
1648 .prepare_instruction(&inner_instruction, &[])
1649 .unwrap();
1650
1651 let mut compute_units_consumed = 0;
1652 let result = invoke_context.process_instruction(
1653 &inner_instruction.data,
1654 &inner_instruction_accounts,
1655 &program_indices,
1656 &mut compute_units_consumed,
1657 &mut ExecuteTimings::default(),
1658 );
1659
1660 assert!(compute_units_consumed > 0);
1664 assert_eq!(
1665 compute_units_consumed,
1666 compute_units_to_consume.saturating_add(MOCK_BUILTIN_COMPUTE_UNIT_COST),
1667 );
1668 assert_eq!(result, expected_result);
1669
1670 invoke_context.pop().unwrap();
1671 }
1672 }
1673
1674 #[test]
1675 fn test_invoke_context_compute_budget() {
1676 let transaction_accounts = vec![(rialo_s_pubkey::new_rand(), AccountSharedData::default())];
1677 let transaction_accounts: Vec<(Pubkey, StoredAccount)> = transaction_accounts
1678 .into_iter()
1679 .map(|(k, v)| (k, v.into()))
1680 .collect();
1681
1682 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1683 invoke_context.compute_budget = ComputeBudget::new(
1684 compute_budget_limits::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64,
1685 );
1686
1687 invoke_context
1688 .transaction_context
1689 .get_next_instruction_context()
1690 .unwrap()
1691 .configure(&[0], &[], &[]);
1692 invoke_context.push().unwrap();
1693 assert_eq!(
1694 *invoke_context.get_compute_budget(),
1695 ComputeBudget::new(
1696 compute_budget_limits::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64
1697 )
1698 );
1699 invoke_context.pop().unwrap();
1700 }
1701
1702 #[test]
1703 fn test_process_instruction_accounts_resize_delta() {
1704 let program_key = Pubkey::new_unique();
1705 let user_account_data_len = 123u64;
1706 let user_account =
1707 AccountSharedData::new(100, user_account_data_len as usize, &program_key);
1708 let dummy_account = AccountSharedData::new(10, 0, &program_key);
1709 let program_account = AccountSharedData::new(500, 500, &native_loader::id());
1710 let transaction_accounts = vec![
1711 (Pubkey::new_unique(), user_account),
1712 (Pubkey::new_unique(), dummy_account),
1713 (program_key, program_account),
1714 ];
1715 let instruction_accounts = [
1716 InstructionAccount {
1717 index_in_transaction: 0,
1718 index_in_caller: 0,
1719 index_in_callee: 0,
1720 is_signer: false,
1721 is_writable: true,
1722 },
1723 InstructionAccount {
1724 index_in_transaction: 1,
1725 index_in_caller: 1,
1726 index_in_callee: 1,
1727 is_signer: false,
1728 is_writable: false,
1729 },
1730 ];
1731 let transaction_accounts: Vec<(Pubkey, StoredAccount)> = transaction_accounts
1732 .into_iter()
1733 .map(|(k, v)| (k, v.into()))
1734 .collect();
1735 with_mock_invoke_context!(
1736 invoke_context,
1737 transaction_context,
1738 Some((program_key, MockBuiltin::vm)),
1739 transaction_accounts
1740 );
1741
1742 {
1744 let resize_delta: i64 = 0;
1745 let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
1746 let instruction_data =
1747 bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1748
1749 let result = invoke_context.process_instruction(
1750 &instruction_data,
1751 &instruction_accounts,
1752 &[2],
1753 &mut 0,
1754 &mut ExecuteTimings::default(),
1755 );
1756
1757 assert!(result.is_ok());
1758 assert_eq!(
1759 invoke_context
1760 .transaction_context
1761 .accounts_resize_delta()
1762 .unwrap(),
1763 resize_delta
1764 );
1765 }
1766
1767 {
1769 let resize_delta: i64 = 1;
1770 let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
1771 let instruction_data =
1772 bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1773
1774 let result = invoke_context.process_instruction(
1775 &instruction_data,
1776 &instruction_accounts,
1777 &[2],
1778 &mut 0,
1779 &mut ExecuteTimings::default(),
1780 );
1781
1782 assert!(result.is_ok());
1783 assert_eq!(
1784 invoke_context
1785 .transaction_context
1786 .accounts_resize_delta()
1787 .unwrap(),
1788 resize_delta
1789 );
1790 }
1791
1792 {
1794 let resize_delta: i64 = -1;
1795 let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
1796 let instruction_data =
1797 bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
1798
1799 let result = invoke_context.process_instruction(
1800 &instruction_data,
1801 &instruction_accounts,
1802 &[2],
1803 &mut 0,
1804 &mut ExecuteTimings::default(),
1805 );
1806
1807 assert!(result.is_ok());
1808 assert_eq!(
1809 invoke_context
1810 .transaction_context
1811 .accounts_resize_delta()
1812 .unwrap(),
1813 resize_delta
1814 );
1815 }
1816 }
1817}