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