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