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