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