1#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
2use {
3 crate::{
4 DropOnBailOut, IndexOfAccount, MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION,
5 MAX_ACCOUNT_DATA_LEN, MAX_ACCOUNTS_PER_TRANSACTION,
6 instruction::{InstructionContext, InstructionFrame},
7 transaction_accounts::{KeyedAccountSharedData, TransactionAccounts},
8 vm_addresses::{
9 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS, GUEST_INSTRUCTION_DATA_BASE_ADDRESS,
10 GUEST_REGION_SIZE, RETURN_DATA_SCRATCHPAD,
11 },
12 },
13 solana_account::{AccountSharedData, ReadableAccount, WritableAccount},
14 solana_instruction_error::InstructionError,
15 solana_instructions_sysvar as instructions,
16 solana_rent::Rent,
17 solana_sbpf::memory_region::{AccessType, AccessViolationHandler, MemoryRegion},
18 std::{borrow::Cow, cell::Cell, rc::Rc},
19};
20use {
21 crate::{instruction_accounts::InstructionAccount, vm_slice::VmSlice},
22 solana_pubkey::Pubkey,
23};
24
25pub type InstructionTrace<'ix_data> = (
27 Vec<InstructionFrame>,
28 Vec<Box<[InstructionAccount]>>,
29 Vec<Cow<'ix_data, [u8]>>,
30);
31
32#[repr(C)]
37#[derive(Debug)]
38struct TransactionFrame {
39 return_data_pubkey: Pubkey,
41 return_data_scratchpad: VmSlice<u8>,
42 pub cpi_data_scratchpad: VmSlice<u8>,
44 pub cpi_accounts_scratchpad: VmSlice<InstructionAccount>,
46 current_executing_instruction: u16,
48 total_number_of_instructions_in_trace: u16,
50 number_of_cpis_in_trace: u16,
52 number_of_transaction_accounts: u16,
54}
55
56#[derive(Debug)]
60#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
61pub struct TransactionContext<'ix_data> {
62 pub(crate) accounts: Rc<TransactionAccounts>,
63 instruction_stack_capacity: usize,
64 instruction_trace_capacity: usize,
65 instruction_stack: Vec<usize>,
66 instruction_trace: Vec<InstructionFrame>,
67 transaction_frame: TransactionFrame,
68 return_data_bytes: Vec<u8>,
69 next_top_level_instruction_index: usize,
70 #[cfg(not(target_os = "solana"))]
71 pub(crate) rent: Rent,
72 deduplication_maps: Vec<Box<[u8]>>,
76 instruction_accounts: Vec<Box<[InstructionAccount]>>,
78 instruction_data: Vec<Cow<'ix_data, [u8]>>,
81}
82
83#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
84impl<'ix_data> TransactionContext<'ix_data> {
85 pub fn new_with_feature_flags(
87 transaction_accounts: Vec<KeyedAccountSharedData>,
88 rent: Rent,
89 instruction_stack_capacity: usize,
90 instruction_trace_capacity: usize,
91 number_of_top_level_instructions: usize,
92 drop_on_bail_out: DropOnBailOut,
93 ) -> Self {
94 let transaction_frame = TransactionFrame {
95 return_data_pubkey: Pubkey::default(),
96 return_data_scratchpad: VmSlice::new(RETURN_DATA_SCRATCHPAD, 0),
97 cpi_data_scratchpad: VmSlice::new(
98 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(
99 GUEST_REGION_SIZE.saturating_mul(number_of_top_level_instructions as u64),
100 ),
101 0,
102 ),
103 cpi_accounts_scratchpad: VmSlice::new(
104 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS.saturating_add(
105 GUEST_REGION_SIZE.saturating_mul(number_of_top_level_instructions as u64),
106 ),
107 0,
108 ),
109 current_executing_instruction: 0,
110 total_number_of_instructions_in_trace: number_of_top_level_instructions as u16,
111 number_of_cpis_in_trace: 0,
112 number_of_transaction_accounts: transaction_accounts.len() as u16,
113 };
114
115 let mut instruction_trace =
117 Vec::with_capacity(instruction_trace_capacity.saturating_add(1));
118 instruction_trace.resize_with(
119 number_of_top_level_instructions.saturating_add(1),
120 InstructionFrame::default,
121 );
122
123 Self {
124 accounts: Rc::new(TransactionAccounts::new_with_feature_flags(
125 transaction_accounts,
126 drop_on_bail_out,
127 )),
128 instruction_stack_capacity,
129 instruction_trace_capacity,
130 instruction_stack: Vec::with_capacity(instruction_stack_capacity),
131 instruction_trace,
132 return_data_bytes: Vec::new(),
133 transaction_frame,
134 next_top_level_instruction_index: 0,
135 rent,
136 instruction_accounts: Vec::with_capacity(instruction_trace_capacity),
137 deduplication_maps: Vec::with_capacity(instruction_trace_capacity),
138 instruction_data: Vec::with_capacity(instruction_trace_capacity),
139 }
140 }
141
142 #[cfg(feature = "dev-context-only-utils")]
144 pub fn new(
145 transaction_accounts: Vec<KeyedAccountSharedData>,
146 rent: Rent,
147 instruction_stack_capacity: usize,
148 instruction_trace_capacity: usize,
149 number_of_top_level_instructions: usize,
150 ) -> Self {
151 Self::new_with_feature_flags(
152 transaction_accounts,
153 rent,
154 instruction_stack_capacity,
155 instruction_trace_capacity,
156 number_of_top_level_instructions,
157 DropOnBailOut::Disabled,
158 )
159 }
160
161 pub fn deconstruct_without_keys(self) -> Result<Vec<AccountSharedData>, InstructionError> {
163 if !self.instruction_stack.is_empty() {
164 return Err(InstructionError::CallDepth);
165 }
166
167 let accounts = Rc::try_unwrap(self.accounts)
168 .expect("transaction_context.accounts has unexpected outstanding refs")
169 .deconstruct_into_account_shared_data();
170
171 Ok(accounts)
172 }
173
174 pub fn accounts(&self) -> &Rc<TransactionAccounts> {
175 &self.accounts
176 }
177
178 pub fn get_number_of_accounts(&self) -> IndexOfAccount {
180 self.accounts.len() as IndexOfAccount
181 }
182
183 pub fn get_key_of_account_at_index(
185 &self,
186 index_in_transaction: IndexOfAccount,
187 ) -> Result<&Pubkey, InstructionError> {
188 self.accounts
189 .account_key(index_in_transaction)
190 .ok_or(InstructionError::MissingAccount)
191 }
192
193 pub fn find_index_of_account(&self, pubkey: &Pubkey) -> Option<IndexOfAccount> {
195 self.accounts
196 .account_keys_iter()
197 .position(|key| key == pubkey)
198 .map(|index| index as IndexOfAccount)
199 }
200
201 pub fn get_instruction_trace_capacity(&self) -> usize {
203 self.instruction_trace_capacity
204 }
205
206 pub fn get_instruction_trace_length(&self) -> usize {
210 self.instruction_trace.len().saturating_sub(1)
211 }
212
213 pub fn get_instruction_context_at_index_in_trace(
215 &self,
216 index_in_trace: usize,
217 ) -> Result<InstructionContext<'_, '_>, InstructionError> {
218 let instruction = self
219 .instruction_trace
220 .get(index_in_trace)
221 .ok_or(InstructionError::CallDepth)?;
222
223 let instruction_accounts = self
226 .instruction_accounts
227 .get(index_in_trace)
228 .map(|item| item.as_ref())
229 .unwrap_or_default();
230 let dedup_map = self
231 .deduplication_maps
232 .get(index_in_trace)
233 .map(|item| item.as_ref())
234 .unwrap_or_default();
235 let instruction_data = self
236 .instruction_data
237 .get(index_in_trace)
238 .map(|item| item.as_ref())
239 .unwrap_or_default();
240 Ok(InstructionContext {
241 transaction_context: self,
242 index_in_trace,
243 nesting_level: instruction.nesting_level as usize,
244 program_account_index_in_tx: instruction.program_account_index_in_tx as IndexOfAccount,
245 instruction_accounts,
246 dedup_map,
247 instruction_data,
248 index_of_caller_instruction: instruction.index_of_caller_instruction as usize,
249 })
250 }
251
252 pub fn get_instruction_context_at_nesting_level(
254 &self,
255 nesting_level: usize,
256 ) -> Result<InstructionContext<'_, '_>, InstructionError> {
257 let index_in_trace = *self
258 .instruction_stack
259 .get(nesting_level)
260 .ok_or(InstructionError::CallDepth)?;
261 let instruction_context = self.get_instruction_context_at_index_in_trace(index_in_trace)?;
262 debug_assert_eq!(instruction_context.nesting_level, nesting_level);
263 Ok(instruction_context)
264 }
265
266 pub fn get_instruction_stack_capacity(&self) -> usize {
268 self.instruction_stack_capacity
269 }
270
271 pub fn get_instruction_stack_height(&self) -> usize {
274 self.instruction_stack.len()
275 }
276
277 pub fn get_current_instruction_index(&self) -> Result<usize, InstructionError> {
279 self.instruction_stack
280 .last()
281 .copied()
282 .ok_or(InstructionError::CallDepth)
283 }
284
285 pub fn get_current_instruction_context(
287 &self,
288 ) -> Result<InstructionContext<'_, '_>, InstructionError> {
289 let index_in_trace = self.get_current_instruction_index()?;
290 self.get_instruction_context_at_index_in_trace(index_in_trace)
291 }
292
293 pub fn get_next_instruction_context(
297 &self,
298 ) -> Result<InstructionContext<'_, '_>, InstructionError> {
299 let index_in_trace = if self.instruction_stack.is_empty() {
300 self.next_top_level_instruction_index
301 } else {
302 self.instruction_trace
303 .len()
304 .checked_sub(1)
305 .ok_or(InstructionError::CallDepth)?
306 };
307 self.get_instruction_context_at_index_in_trace(index_in_trace)
308 }
309
310 pub fn configure_instruction_at_index(
312 &mut self,
313 instruction_index: usize,
314 program_index: IndexOfAccount,
315 instruction_accounts: Vec<InstructionAccount>,
316 deduplication_map: Vec<u8>,
317 instruction_data: Cow<'ix_data, [u8]>,
318 caller_index: Option<u16>,
319 ) -> Result<(), InstructionError> {
320 debug_assert_eq!(
321 deduplication_map.len(),
322 usize::from(self.get_number_of_accounts()).min(MAX_ACCOUNTS_PER_TRANSACTION)
323 );
324
325 let instruction = self
326 .instruction_trace
327 .get_mut(instruction_index)
328 .ok_or(InstructionError::MaxInstructionTraceLengthExceeded)?;
329
330 if let Some(caller_index) = caller_index {
332 self.transaction_frame.total_number_of_instructions_in_trace = self
333 .transaction_frame
334 .total_number_of_instructions_in_trace
335 .saturating_add(1);
336 instruction.index_of_caller_instruction = caller_index;
337 let next_data_ptr = self
338 .transaction_frame
339 .cpi_data_scratchpad
340 .ptr()
341 .saturating_add(GUEST_REGION_SIZE);
342 self.transaction_frame.cpi_data_scratchpad = VmSlice::new(next_data_ptr, 0);
343 let next_accounts_ptr = self
344 .transaction_frame
345 .cpi_accounts_scratchpad
346 .ptr()
347 .saturating_add(GUEST_REGION_SIZE);
348 self.transaction_frame.cpi_accounts_scratchpad = VmSlice::new(next_accounts_ptr, 0);
349 }
350
351 instruction.program_account_index_in_tx = program_index;
352 instruction.configure_vm_slices(
353 instruction_index as u64,
354 instruction_accounts.len(),
355 instruction_data.len() as u64,
356 );
357 self.deduplication_maps
358 .push(deduplication_map.into_boxed_slice());
359 self.instruction_accounts
360 .push(instruction_accounts.into_boxed_slice());
361 self.instruction_data.push(instruction_data);
362 Ok(())
363 }
364
365 fn deduplicate_accounts_for_tests(
367 &self,
368 instruction_accounts: &[InstructionAccount],
369 ) -> Vec<u8> {
370 let mut dedup_map = vec![
371 u8::MAX;
372 usize::from(self.get_number_of_accounts())
373 .min(MAX_ACCOUNTS_PER_TRANSACTION)
374 ];
375 for (idx, account) in instruction_accounts.iter().enumerate() {
376 let index_in_instruction = dedup_map
377 .get_mut(account.index_in_transaction as usize)
378 .unwrap();
379 if *index_in_instruction == u8::MAX {
380 *index_in_instruction = idx as u8;
381 }
382 }
383 dedup_map
384 }
385
386 pub fn configure_top_level_instruction_for_tests(
388 &mut self,
389 program_index: IndexOfAccount,
390 instruction_accounts: Vec<InstructionAccount>,
391 instruction_data: Vec<u8>,
392 ) -> Result<(), InstructionError> {
393 debug_assert!(instruction_accounts.len() <= u8::MAX as usize);
394 let dedup_map = self.deduplicate_accounts_for_tests(&instruction_accounts);
395
396 self.configure_instruction_at_index(
397 self.next_top_level_instruction_index,
398 program_index,
399 instruction_accounts,
400 dedup_map,
401 Cow::Owned(instruction_data),
402 None,
403 )?;
404 Ok(())
405 }
406
407 pub fn configure_next_cpi_for_tests(
409 &mut self,
410 program_index: IndexOfAccount,
411 instruction_accounts: Vec<InstructionAccount>,
412 instruction_data: Vec<u8>,
413 ) -> Result<(), InstructionError> {
414 debug_assert!(instruction_accounts.len() <= u8::MAX as usize);
415 let dedup_map = self.deduplicate_accounts_for_tests(&instruction_accounts);
416 let caller_index = self.get_current_instruction_index()?;
417 let cpi_index = self.get_instruction_trace_length();
418 self.configure_instruction_at_index(
419 cpi_index,
420 program_index,
421 instruction_accounts,
422 dedup_map,
423 Cow::Owned(instruction_data),
424 Some(caller_index as u16),
425 )?;
426 Ok(())
427 }
428
429 pub fn push(&mut self) -> Result<(), InstructionError> {
431 let nesting_level = self.get_instruction_stack_height();
432 if !self.instruction_stack.is_empty() && self.accounts.get_lamports_delta() != 0 {
433 return Err(InstructionError::UnbalancedInstruction);
434 }
435 {
436 let instruction = self
437 .instruction_trace
438 .last_mut()
439 .ok_or(InstructionError::CallDepth)?;
440 instruction.nesting_level = nesting_level as u16;
441 }
442
443 if self.number_of_called_instructions_in_trace() >= self.instruction_trace_capacity {
444 return Err(InstructionError::MaxInstructionTraceLengthExceeded);
445 }
446
447 let (index_in_trace, current_top_level_instruction) = if self.instruction_stack.is_empty() {
448 let index = self.next_top_level_instruction_index;
449 self.next_top_level_instruction_index =
450 self.next_top_level_instruction_index.saturating_add(1);
451 (index, index)
452 } else {
453 let index = self.get_instruction_trace_length();
454 self.transaction_frame.number_of_cpis_in_trace = self
455 .transaction_frame
456 .number_of_cpis_in_trace
457 .saturating_add(1);
458 self.instruction_trace.push(InstructionFrame::default());
459 (
460 index,
461 self.next_top_level_instruction_index.saturating_sub(1),
462 )
463 };
464
465 if nesting_level >= self.instruction_stack_capacity {
466 return Err(InstructionError::CallDepth);
467 }
468 self.transaction_frame.current_executing_instruction = index_in_trace as u16;
469 self.instruction_stack.push(index_in_trace);
470 if let Some(index_in_transaction) = self.find_index_of_account(&instructions::id()) {
471 let mut mut_account_ref = self.accounts.try_borrow_mut(index_in_transaction)?;
472 if mut_account_ref.owner() != &solana_sdk_ids::sysvar::id() {
473 return Err(InstructionError::InvalidAccountOwner);
474 }
475 instructions::store_current_index_checked(
476 mut_account_ref.data_as_mut_slice(),
477 current_top_level_instruction as u16,
478 )?;
479 }
480 Ok(())
481 }
482
483 pub fn pop(&mut self) -> Result<(), InstructionError> {
485 if self.instruction_stack.is_empty() {
486 return Err(InstructionError::CallDepth);
487 }
488 let detected_an_unbalanced_instruction =
490 self.get_current_instruction_context()
491 .and_then(|instruction_context| {
492 self.accounts
494 .try_borrow_mut(
495 instruction_context.get_index_of_program_account_in_transaction()?,
496 )
497 .map_err(|err| {
498 if err == InstructionError::AccountBorrowFailed {
499 InstructionError::AccountBorrowOutstanding
500 } else {
501 err
502 }
503 })?;
504 Ok(self.accounts.get_lamports_delta() != 0)
505 });
506 self.instruction_stack.pop();
508 if let Some(instr_idx) = self.instruction_stack.last() {
509 self.transaction_frame.current_executing_instruction = *instr_idx as u16;
510 }
511 if detected_an_unbalanced_instruction? {
512 Err(InstructionError::UnbalancedInstruction)
513 } else {
514 Ok(())
515 }
516 }
517
518 pub fn get_return_data(&self) -> (&Pubkey, &[u8]) {
520 (
521 &self.transaction_frame.return_data_pubkey,
522 &self.return_data_bytes,
523 )
524 }
525
526 pub fn set_return_data(
528 &mut self,
529 program_id: Pubkey,
530 data: Vec<u8>,
531 ) -> Result<(), InstructionError> {
532 self.transaction_frame.return_data_pubkey = program_id;
533 self.transaction_frame
534 .return_data_scratchpad
535 .set_len(data.len() as u64);
536 self.return_data_bytes = data;
537 Ok(())
538 }
539
540 pub fn access_violation_handler(
542 &self,
543 virtual_address_space_adjustments: bool,
544 account_data_direct_mapping: bool,
545 ) -> AccessViolationHandler {
546 let accounts = Rc::clone(&self.accounts);
547 Box::new(
548 move |region: &mut MemoryRegion,
549 address_space_reserved_for_account: u64,
550 access_type: AccessType,
551 vm_addr: u64,
552 len: u64| {
553 if access_type == AccessType::Load {
554 return;
555 }
556 let Some(index_in_transaction) = region.access_violation_handler_payload else {
557 return;
559 };
560 let region_vm_addr_start = region.vm_addr_range().start;
561 let requested_length = vm_addr
562 .saturating_add(len)
563 .saturating_sub(region_vm_addr_start)
564 as usize;
565 if requested_length > address_space_reserved_for_account as usize {
566 return;
568 }
569
570 let Ok(mut account) = accounts.try_borrow_mut(index_in_transaction) else {
574 debug_assert!(false);
575 return;
576 };
577 if accounts.touch(index_in_transaction).is_err() {
578 debug_assert!(false);
579 return;
580 }
581
582 let remaining_allowed_growth = MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION
583 .saturating_sub(accounts.resize_delta())
584 .max(0) as usize;
585
586 if requested_length > region.len() {
587 let old_len = account.data().len();
591 let new_len = (address_space_reserved_for_account as usize)
592 .min(MAX_ACCOUNT_DATA_LEN as usize)
593 .min(old_len.saturating_add(remaining_allowed_growth));
594 debug_assert!(accounts.can_data_be_resized(old_len, new_len).is_ok());
596 if accounts
597 .update_accounts_resize_delta(old_len, new_len)
598 .is_err()
599 {
600 return;
601 }
602
603 account.resize(new_len, 0);
604 let data_ptr = region.host_buffer().ptr() as *mut u8;
605 let new_buffer = std::ptr::slice_from_raw_parts_mut(data_ptr, new_len);
606 unsafe {
607 region.redirect(new_buffer);
636 }
637 }
638
639 if virtual_address_space_adjustments && account_data_direct_mapping {
641 unsafe {
642 region.redirect(account.raw_mut_data_slice());
644 }
645 }
646 },
647 )
648 }
649
650 pub fn take_instruction_trace(&mut self) -> InstructionTrace<'_> {
652 self.instruction_trace.pop();
655 (
656 std::mem::take(&mut self.instruction_trace),
657 std::mem::take(&mut self.instruction_accounts),
658 std::mem::take(&mut self.instruction_data),
659 )
660 }
661
662 pub fn number_of_called_instructions_in_trace(&self) -> usize {
668 self.next_top_level_instruction_index
669 .saturating_add(self.transaction_frame.number_of_cpis_in_trace as usize)
670 }
671
672 pub fn next_top_level_instruction_index(&self) -> usize {
674 self.next_top_level_instruction_index
675 }
676
677 pub fn number_of_cpis_in_trace(&self) -> usize {
679 self.transaction_frame.number_of_cpis_in_trace as usize
680 }
681}
682
683#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
685#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
686#[cfg_attr(feature = "wincode", derive(wincode::SchemaRead, wincode::SchemaWrite))]
687#[derive(Clone, Debug, Default, PartialEq, Eq)]
688pub struct TransactionReturnData {
689 pub program_id: Pubkey,
690 pub data: Vec<u8>,
691}
692
693#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
695pub struct ExecutionRecord {
696 pub accounts: Vec<KeyedAccountSharedData>,
697 pub return_data: TransactionReturnData,
698 pub touched_flags: Box<[bool]>,
700 pub accounts_resize_delta: i64,
701}
702
703#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
705impl From<TransactionContext<'_>> for ExecutionRecord {
706 fn from(context: TransactionContext) -> Self {
707 let (accounts, touched_flags, resize_delta) = Rc::try_unwrap(context.accounts)
708 .expect("transaction_context.accounts has unexpected outstanding refs")
709 .take();
710
711 let touched_flags: Box<[bool]> = Vec::from(touched_flags)
717 .into_iter()
718 .map(|flag| flag.into_inner())
719 .collect();
720
721 let return_data = TransactionReturnData {
722 program_id: context.transaction_frame.return_data_pubkey,
723 data: context.return_data_bytes,
724 };
725
726 Self {
727 accounts,
728 return_data,
729 touched_flags,
730 accounts_resize_delta: Cell::into_inner(resize_delta),
731 }
732 }
733}
734
735#[cfg(all(test, not(target_arch = "sbf"), not(target_arch = "bpf")))]
736mod tests {
737 use super::*;
738
739 #[test]
740 fn test_instructions_sysvar_store_index_checked() {
741 let build_transaction_context = |account: AccountSharedData| {
742 TransactionContext::new(
743 vec![
744 (Pubkey::new_unique(), AccountSharedData::default()),
745 (instructions::id(), account),
746 ],
747 Rent::default(),
748 2,
749 2,
750 1,
751 )
752 };
753
754 let correct_space = 2;
755 let rent_exempt_lamports = Rent::default().minimum_balance(correct_space);
756
757 let account =
759 AccountSharedData::new(rent_exempt_lamports, correct_space, &Pubkey::new_unique());
760 assert_eq!(
761 build_transaction_context(account).push(),
762 Err(InstructionError::InvalidAccountOwner),
763 );
764
765 let account =
767 AccountSharedData::new(rent_exempt_lamports, 0, &solana_sdk_ids::sysvar::id());
768 assert_eq!(
769 build_transaction_context(account).push(),
770 Err(InstructionError::AccountDataTooSmall),
771 );
772
773 let account = AccountSharedData::new(
775 rent_exempt_lamports,
776 correct_space,
777 &solana_sdk_ids::sysvar::id(),
778 );
779 assert_eq!(build_transaction_context(account).push(), Ok(()),);
780 }
781
782 #[test]
783 fn test_invalid_native_loader_index() {
784 let mut transaction_context = TransactionContext::new(
785 vec![(
786 Pubkey::new_unique(),
787 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
788 )],
789 Rent::default(),
790 20,
791 20,
792 1,
793 );
794
795 transaction_context
796 .configure_top_level_instruction_for_tests(
797 u16::MAX,
798 vec![InstructionAccount::new(0, false, false)],
799 vec![],
800 )
801 .unwrap();
802 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
803
804 let result = instruction_context.get_index_of_program_account_in_transaction();
805 assert_eq!(result, Err(InstructionError::MissingAccount));
806
807 let result = instruction_context.get_program_key();
808 assert_eq!(result, Err(InstructionError::MissingAccount));
809
810 let result = instruction_context.get_program_owner();
811 assert_eq!(result.err(), Some(InstructionError::MissingAccount));
812 }
813
814 #[test]
815 fn test_instruction_shared_items() {
816 let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 11];
817 let mut transaction_context =
818 TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 3);
819
820 let instruction_accounts_1 = vec![
821 InstructionAccount::new(0, false, true),
822 InstructionAccount::new(3, true, false),
823 ];
824 transaction_context
825 .configure_top_level_instruction_for_tests(
826 1,
827 instruction_accounts_1.clone(),
828 vec![1, 2, 3, 4],
829 )
830 .unwrap();
831 transaction_context.push().unwrap();
832
833 let instruction_accounts_2 = vec![
834 InstructionAccount::new(0, false, true),
835 InstructionAccount::new(3, true, false),
836 InstructionAccount::new(5, false, false),
837 ];
838 transaction_context
839 .configure_top_level_instruction_for_tests(
840 1,
841 instruction_accounts_2.clone(),
842 vec![5, 6, 7, 8, 9],
843 )
844 .unwrap();
845 transaction_context.push().unwrap();
846
847 let instruction_accounts_3 = vec![
848 InstructionAccount::new(0, false, true),
849 InstructionAccount::new(3, true, false),
850 InstructionAccount::new(5, false, false),
851 InstructionAccount::new(3, false, false),
852 InstructionAccount::new(10, false, false),
853 ];
854 transaction_context
855 .configure_top_level_instruction_for_tests(
856 1,
857 instruction_accounts_3.clone(),
858 vec![10, 11],
859 )
860 .unwrap();
861 transaction_context.push().unwrap();
862
863 let first_ix_context = transaction_context
864 .get_instruction_context_at_index_in_trace(0)
865 .unwrap();
866 assert_eq!(
867 instruction_accounts_1.as_slice(),
868 first_ix_context.instruction_accounts
869 );
870 assert_eq!(
871 *first_ix_context.instruction_data,
872 **transaction_context.instruction_data.first().unwrap()
873 );
874 for (idx_in_ix, acc) in instruction_accounts_1.iter().enumerate() {
875 assert_eq!(
876 *first_ix_context
877 .dedup_map
878 .get(acc.index_in_transaction as usize)
879 .unwrap(),
880 idx_in_ix as u8
881 );
882 }
883
884 let second_ix_context = transaction_context
885 .get_instruction_context_at_index_in_trace(1)
886 .unwrap();
887 assert_eq!(
888 instruction_accounts_2.as_slice(),
889 second_ix_context.instruction_accounts
890 );
891 assert_eq!(
892 *second_ix_context.instruction_data,
893 **transaction_context.instruction_data.get(1).unwrap()
894 );
895 for (idx_in_ix, acc) in instruction_accounts_2.iter().enumerate() {
896 assert_eq!(
897 *second_ix_context
898 .dedup_map
899 .get(acc.index_in_transaction as usize)
900 .unwrap(),
901 idx_in_ix as u8
902 );
903 }
904
905 let third_ix_context = transaction_context
906 .get_instruction_context_at_index_in_trace(2)
907 .unwrap();
908 assert_eq!(
909 instruction_accounts_3.as_slice(),
910 third_ix_context.instruction_accounts
911 );
912 assert_eq!(
913 *third_ix_context.instruction_data,
914 **transaction_context.instruction_data.get(2).unwrap()
915 );
916 for (idx_in_ix, acc) in instruction_accounts_3.iter().enumerate() {
917 if idx_in_ix == 3 {
918 assert_eq!(
919 *third_ix_context
920 .dedup_map
921 .get(acc.index_in_transaction as usize)
922 .unwrap(),
923 1
924 );
925 } else {
926 assert_eq!(
927 *third_ix_context
928 .dedup_map
929 .get(acc.index_in_transaction as usize)
930 .unwrap(),
931 idx_in_ix as u8
932 );
933 }
934 }
935 }
936
937 #[test]
938 fn test_number_of_instructions() {
939 let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
940 let mut transaction_context =
941 TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 2);
942 assert_eq!(
943 transaction_context
944 .transaction_frame
945 .number_of_cpis_in_trace,
946 0
947 );
948
949 transaction_context
951 .configure_instruction_at_index(
952 0,
953 0,
954 vec![InstructionAccount::new(1, false, false)],
955 vec![0; 3],
956 Vec::new().into(),
957 None,
958 )
959 .unwrap();
960
961 transaction_context
963 .configure_instruction_at_index(
964 1,
965 0,
966 vec![InstructionAccount::new(1, false, false)],
967 vec![0; 3],
968 Vec::new().into(),
969 None,
970 )
971 .unwrap();
972
973 transaction_context.push().unwrap();
975 assert_eq!(
976 transaction_context
977 .transaction_frame
978 .current_executing_instruction,
979 0
980 );
981 assert_eq!(
982 transaction_context.number_of_called_instructions_in_trace(),
983 1
984 );
985
986 assert_eq!(
987 transaction_context
988 .transaction_frame
989 .total_number_of_instructions_in_trace,
990 2
991 );
992
993 assert_eq!(
994 transaction_context
995 .transaction_frame
996 .number_of_cpis_in_trace,
997 0
998 );
999
1000 assert_eq!(
1001 transaction_context
1002 .transaction_frame
1003 .cpi_data_scratchpad
1004 .ptr(),
1005 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(2))
1006 );
1007 assert_eq!(
1008 transaction_context
1009 .transaction_frame
1010 .cpi_data_scratchpad
1011 .len(),
1012 0,
1013 );
1014 assert_eq!(
1015 transaction_context
1016 .transaction_frame
1017 .cpi_accounts_scratchpad
1018 .ptr(),
1019 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1020 .saturating_add(GUEST_REGION_SIZE.saturating_mul(2))
1021 );
1022 assert_eq!(
1023 transaction_context
1024 .transaction_frame
1025 .cpi_data_scratchpad
1026 .len(),
1027 0,
1028 );
1029
1030 assert_eq!(
1031 transaction_context.number_of_called_instructions_in_trace(),
1032 1
1033 );
1034
1035 transaction_context
1037 .configure_next_cpi_for_tests(
1038 0,
1039 vec![InstructionAccount::new(2, false, true)],
1040 Vec::new(),
1041 )
1042 .unwrap();
1043
1044 transaction_context.push().unwrap();
1045 assert_eq!(
1046 transaction_context
1047 .transaction_frame
1048 .current_executing_instruction,
1049 2
1050 );
1051
1052 assert_eq!(
1053 transaction_context
1054 .transaction_frame
1055 .total_number_of_instructions_in_trace,
1056 3
1057 );
1058 assert_eq!(
1059 transaction_context
1060 .transaction_frame
1061 .number_of_cpis_in_trace,
1062 1
1063 );
1064 assert_eq!(
1065 transaction_context.number_of_called_instructions_in_trace(),
1066 2
1067 );
1068
1069 assert_eq!(
1070 transaction_context
1071 .transaction_frame
1072 .cpi_data_scratchpad
1073 .ptr(),
1074 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(3))
1075 );
1076 assert_eq!(
1077 transaction_context
1078 .transaction_frame
1079 .cpi_accounts_scratchpad
1080 .ptr(),
1081 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1082 .saturating_add(GUEST_REGION_SIZE.saturating_mul(3))
1083 );
1084
1085 transaction_context
1087 .configure_next_cpi_for_tests(
1088 0,
1089 vec![InstructionAccount::new(2, false, true)],
1090 Vec::new(),
1091 )
1092 .unwrap();
1093
1094 transaction_context.push().unwrap();
1095 assert_eq!(
1096 transaction_context
1097 .transaction_frame
1098 .current_executing_instruction,
1099 3
1100 );
1101
1102 assert_eq!(
1103 transaction_context
1104 .transaction_frame
1105 .total_number_of_instructions_in_trace,
1106 4
1107 );
1108
1109 assert_eq!(
1110 transaction_context
1111 .transaction_frame
1112 .cpi_data_scratchpad
1113 .ptr(),
1114 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(4))
1115 );
1116 assert_eq!(
1117 transaction_context
1118 .transaction_frame
1119 .cpi_accounts_scratchpad
1120 .ptr(),
1121 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1122 .saturating_add(GUEST_REGION_SIZE.saturating_mul(4))
1123 );
1124
1125 assert_eq!(
1126 transaction_context
1127 .transaction_frame
1128 .number_of_cpis_in_trace,
1129 2
1130 );
1131
1132 assert_eq!(
1133 transaction_context.number_of_called_instructions_in_trace(),
1134 3
1135 );
1136 transaction_context.pop().unwrap();
1138 assert_eq!(
1139 transaction_context.number_of_called_instructions_in_trace(),
1140 3
1141 );
1142
1143 assert_eq!(
1144 transaction_context
1145 .transaction_frame
1146 .total_number_of_instructions_in_trace,
1147 4
1148 );
1149 assert_eq!(
1150 transaction_context
1151 .transaction_frame
1152 .number_of_cpis_in_trace,
1153 2,
1154 );
1155 assert_eq!(
1156 transaction_context
1157 .transaction_frame
1158 .current_executing_instruction,
1159 2
1160 );
1161
1162 transaction_context
1164 .configure_next_cpi_for_tests(
1165 0,
1166 vec![InstructionAccount::new(2, false, true)],
1167 Vec::new(),
1168 )
1169 .unwrap();
1170
1171 transaction_context.push().unwrap();
1172 assert_eq!(
1173 transaction_context
1174 .transaction_frame
1175 .current_executing_instruction,
1176 4
1177 );
1178
1179 assert_eq!(
1180 transaction_context
1181 .transaction_frame
1182 .total_number_of_instructions_in_trace,
1183 5
1184 );
1185
1186 assert_eq!(
1187 transaction_context
1188 .transaction_frame
1189 .cpi_data_scratchpad
1190 .ptr(),
1191 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1192 );
1193 assert_eq!(
1194 transaction_context
1195 .transaction_frame
1196 .cpi_accounts_scratchpad
1197 .ptr(),
1198 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1199 .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1200 );
1201
1202 assert_eq!(
1203 transaction_context
1204 .transaction_frame
1205 .number_of_cpis_in_trace,
1206 3
1207 );
1208 assert_eq!(
1209 transaction_context.number_of_called_instructions_in_trace(),
1210 4
1211 );
1212
1213 transaction_context.pop().unwrap();
1215
1216 assert_eq!(
1217 transaction_context
1218 .transaction_frame
1219 .current_executing_instruction,
1220 2
1221 );
1222
1223 assert_eq!(
1224 transaction_context
1225 .transaction_frame
1226 .total_number_of_instructions_in_trace,
1227 5
1228 );
1229
1230 assert_eq!(
1231 transaction_context
1232 .transaction_frame
1233 .cpi_data_scratchpad
1234 .ptr(),
1235 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1236 );
1237 assert_eq!(
1238 transaction_context
1239 .transaction_frame
1240 .cpi_accounts_scratchpad
1241 .ptr(),
1242 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1243 .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1244 );
1245
1246 assert_eq!(
1247 transaction_context
1248 .transaction_frame
1249 .number_of_cpis_in_trace,
1250 3
1251 );
1252
1253 transaction_context.pop().unwrap();
1255 assert_eq!(
1256 transaction_context.number_of_called_instructions_in_trace(),
1257 4
1258 );
1259
1260 assert_eq!(
1261 transaction_context
1262 .transaction_frame
1263 .current_executing_instruction,
1264 0
1265 );
1266
1267 assert_eq!(
1268 transaction_context
1269 .transaction_frame
1270 .total_number_of_instructions_in_trace,
1271 5
1272 );
1273
1274 assert_eq!(
1275 transaction_context
1276 .transaction_frame
1277 .cpi_data_scratchpad
1278 .ptr(),
1279 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1280 );
1281 assert_eq!(
1282 transaction_context
1283 .transaction_frame
1284 .cpi_accounts_scratchpad
1285 .ptr(),
1286 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1287 .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1288 );
1289
1290 assert_eq!(
1291 transaction_context
1292 .transaction_frame
1293 .number_of_cpis_in_trace,
1294 3,
1295 );
1296
1297 transaction_context.pop().unwrap();
1299 transaction_context.push().unwrap();
1300 assert_eq!(
1301 transaction_context
1302 .transaction_frame
1303 .current_executing_instruction,
1304 1,
1305 );
1306 assert_eq!(
1307 transaction_context
1308 .transaction_frame
1309 .number_of_cpis_in_trace,
1310 3
1311 );
1312
1313 transaction_context
1315 .configure_next_cpi_for_tests(
1316 0,
1317 vec![InstructionAccount::new(2, false, true)],
1318 Vec::new(),
1319 )
1320 .unwrap();
1321
1322 transaction_context.push().unwrap();
1323
1324 assert_eq!(
1325 transaction_context
1326 .transaction_frame
1327 .current_executing_instruction,
1328 5,
1329 );
1330
1331 assert_eq!(
1332 transaction_context
1333 .transaction_frame
1334 .total_number_of_instructions_in_trace,
1335 6
1336 );
1337
1338 assert_eq!(
1339 transaction_context
1340 .transaction_frame
1341 .cpi_data_scratchpad
1342 .ptr(),
1343 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(6))
1344 );
1345 assert_eq!(
1346 transaction_context
1347 .transaction_frame
1348 .cpi_accounts_scratchpad
1349 .ptr(),
1350 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1351 .saturating_add(GUEST_REGION_SIZE.saturating_mul(6))
1352 );
1353
1354 assert_eq!(
1355 transaction_context
1356 .transaction_frame
1357 .number_of_cpis_in_trace,
1358 4
1359 );
1360 assert_eq!(
1361 transaction_context.number_of_called_instructions_in_trace(),
1362 6
1363 );
1364
1365 transaction_context.pop().unwrap();
1367 assert_eq!(
1368 transaction_context
1369 .transaction_frame
1370 .number_of_cpis_in_trace,
1371 4
1372 );
1373 assert_eq!(
1374 transaction_context
1375 .transaction_frame
1376 .current_executing_instruction,
1377 1,
1378 );
1379
1380 transaction_context.pop().unwrap();
1381 }
1382
1383 #[test]
1384 fn test_get_current_instruction_index() {
1385 let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
1386 let mut transaction_context =
1387 TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 2);
1388
1389 transaction_context
1391 .configure_instruction_at_index(
1392 0,
1393 1,
1394 vec![
1395 InstructionAccount::new(0, false, false),
1396 InstructionAccount::new(1, false, false),
1397 ],
1398 vec![u8::MAX; 3],
1399 Cow::Owned(Vec::new()),
1400 None,
1401 )
1402 .unwrap();
1403
1404 transaction_context
1406 .configure_instruction_at_index(
1407 1,
1408 1,
1409 vec![
1410 InstructionAccount::new(0, false, false),
1411 InstructionAccount::new(1, false, true),
1412 ],
1413 vec![u8::MAX; 3],
1414 Cow::Owned(Vec::new()),
1415 None,
1416 )
1417 .unwrap();
1418
1419 transaction_context.push().unwrap();
1420 assert_eq!(
1421 transaction_context.get_current_instruction_index().unwrap(),
1422 0
1423 );
1424
1425 transaction_context.pop().unwrap();
1426
1427 transaction_context.push().unwrap();
1428 assert_eq!(
1429 transaction_context.get_current_instruction_index().unwrap(),
1430 1
1431 );
1432
1433 transaction_context
1435 .configure_next_cpi_for_tests(
1436 1,
1437 vec![
1438 InstructionAccount::new(0, false, true),
1439 InstructionAccount::new(1, false, false),
1440 ],
1441 Vec::new(),
1442 )
1443 .unwrap();
1444 transaction_context.push().unwrap();
1445 assert_eq!(
1446 transaction_context.get_current_instruction_index().unwrap(),
1447 2
1448 );
1449
1450 transaction_context
1452 .configure_next_cpi_for_tests(
1453 1,
1454 vec![
1455 InstructionAccount::new(0, false, true),
1456 InstructionAccount::new(1, false, false),
1457 ],
1458 Vec::new(),
1459 )
1460 .unwrap();
1461 transaction_context.push().unwrap();
1462 assert_eq!(
1463 transaction_context.get_current_instruction_index().unwrap(),
1464 3
1465 );
1466
1467 transaction_context.pop().unwrap();
1469 assert_eq!(
1470 transaction_context.get_current_instruction_index().unwrap(),
1471 2
1472 );
1473
1474 transaction_context.pop().unwrap();
1476 assert_eq!(
1477 transaction_context.get_current_instruction_index().unwrap(),
1478 1
1479 );
1480 }
1481}