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!(deduplication_map.len(), MAX_ACCOUNTS_PER_TRANSACTION);
298
299 let instruction = self
300 .instruction_trace
301 .get_mut(instruction_index)
302 .ok_or(InstructionError::MaxInstructionTraceLengthExceeded)?;
303
304 if let Some(caller_index) = caller_index {
306 self.transaction_frame.total_number_of_instructions_in_trace = self
307 .transaction_frame
308 .total_number_of_instructions_in_trace
309 .saturating_add(1);
310 instruction.index_of_caller_instruction = caller_index;
311 let next_data_ptr = self
312 .transaction_frame
313 .cpi_data_scratchpad
314 .ptr()
315 .saturating_add(GUEST_REGION_SIZE);
316 self.transaction_frame.cpi_data_scratchpad = VmSlice::new(next_data_ptr, 0);
317 let next_accounts_ptr = self
318 .transaction_frame
319 .cpi_accounts_scratchpad
320 .ptr()
321 .saturating_add(GUEST_REGION_SIZE);
322 self.transaction_frame.cpi_accounts_scratchpad = VmSlice::new(next_accounts_ptr, 0);
323 }
324
325 instruction.program_account_index_in_tx = program_index;
326 instruction.configure_vm_slices(
327 instruction_index as u64,
328 instruction_accounts.len(),
329 instruction_data.len() as u64,
330 );
331 self.deduplication_maps
332 .push(deduplication_map.into_boxed_slice());
333 self.instruction_accounts
334 .push(instruction_accounts.into_boxed_slice());
335 self.instruction_data.push(instruction_data);
336 Ok(())
337 }
338
339 fn deduplicate_accounts_for_tests(instruction_accounts: &[InstructionAccount]) -> Vec<u16> {
341 let mut dedup_map = vec![u16::MAX; MAX_ACCOUNTS_PER_TRANSACTION];
342 for (idx, account) in instruction_accounts.iter().enumerate() {
343 let index_in_instruction = dedup_map
344 .get_mut(account.index_in_transaction as usize)
345 .unwrap();
346 if *index_in_instruction == u16::MAX {
347 *index_in_instruction = idx as u16;
348 }
349 }
350 dedup_map
351 }
352
353 pub fn configure_top_level_instruction_for_tests(
355 &mut self,
356 program_index: IndexOfAccount,
357 instruction_accounts: Vec<InstructionAccount>,
358 instruction_data: Vec<u8>,
359 ) -> Result<(), InstructionError> {
360 debug_assert!(instruction_accounts.len() <= u16::MAX as usize);
361 let dedup_map = Self::deduplicate_accounts_for_tests(&instruction_accounts);
362
363 self.configure_instruction_at_index(
364 self.next_top_level_instruction_index,
365 program_index,
366 instruction_accounts,
367 dedup_map,
368 Cow::Owned(instruction_data),
369 None,
370 )?;
371 Ok(())
372 }
373
374 pub fn configure_next_cpi_for_tests(
376 &mut self,
377 program_index: IndexOfAccount,
378 instruction_accounts: Vec<InstructionAccount>,
379 instruction_data: Vec<u8>,
380 ) -> Result<(), InstructionError> {
381 debug_assert!(instruction_accounts.len() <= u16::MAX as usize);
382 let dedup_map = Self::deduplicate_accounts_for_tests(&instruction_accounts);
383 let caller_index = self.get_current_instruction_index()?;
384 let cpi_index = self.get_instruction_trace_length();
385 self.configure_instruction_at_index(
386 cpi_index,
387 program_index,
388 instruction_accounts,
389 dedup_map,
390 Cow::Owned(instruction_data),
391 Some(caller_index as u16),
392 )?;
393 Ok(())
394 }
395
396 pub fn push(&mut self) -> Result<(), InstructionError> {
398 let nesting_level = self.get_instruction_stack_height();
399 if !self.instruction_stack.is_empty() && self.accounts.get_lamports_delta() != 0 {
400 return Err(InstructionError::UnbalancedInstruction);
401 }
402 {
403 let instruction = self
404 .instruction_trace
405 .last_mut()
406 .ok_or(InstructionError::CallDepth)?;
407 instruction.nesting_level = nesting_level as u16;
408 }
409
410 if self.number_of_called_instructions_in_trace() >= self.instruction_trace_capacity {
411 return Err(InstructionError::MaxInstructionTraceLengthExceeded);
412 }
413
414 let (index_in_trace, current_top_level_instruction) = if self.instruction_stack.is_empty() {
415 let index = self.next_top_level_instruction_index;
416 self.next_top_level_instruction_index =
417 self.next_top_level_instruction_index.saturating_add(1);
418 (index, index)
419 } else {
420 let index = self.get_instruction_trace_length();
421 self.transaction_frame.number_of_cpis_in_trace = self
422 .transaction_frame
423 .number_of_cpis_in_trace
424 .saturating_add(1);
425 self.instruction_trace.push(InstructionFrame::default());
426 (
427 index,
428 self.next_top_level_instruction_index.saturating_sub(1),
429 )
430 };
431
432 if nesting_level >= self.instruction_stack_capacity {
433 return Err(InstructionError::CallDepth);
434 }
435 self.transaction_frame.current_executing_instruction = index_in_trace as u16;
436 self.instruction_stack.push(index_in_trace);
437 if let Some(index_in_transaction) = self.find_index_of_account(&instructions::id()) {
438 let mut mut_account_ref = self.accounts.try_borrow_mut(index_in_transaction)?;
439 if mut_account_ref.owner() != &solana_sdk_ids::sysvar::id() {
440 return Err(InstructionError::InvalidAccountOwner);
441 }
442 instructions::store_current_index_checked(
443 mut_account_ref.data_as_mut_slice(),
444 current_top_level_instruction as u16,
445 )?;
446 }
447 Ok(())
448 }
449
450 pub fn pop(&mut self) -> Result<(), InstructionError> {
452 if self.instruction_stack.is_empty() {
453 return Err(InstructionError::CallDepth);
454 }
455 let detected_an_unbalanced_instruction =
457 self.get_current_instruction_context()
458 .and_then(|instruction_context| {
459 self.accounts
461 .try_borrow_mut(
462 instruction_context.get_index_of_program_account_in_transaction()?,
463 )
464 .map_err(|err| {
465 if err == InstructionError::AccountBorrowFailed {
466 InstructionError::AccountBorrowOutstanding
467 } else {
468 err
469 }
470 })?;
471 Ok(self.accounts.get_lamports_delta() != 0)
472 });
473 self.instruction_stack.pop();
475 if let Some(instr_idx) = self.instruction_stack.last() {
476 self.transaction_frame.current_executing_instruction = *instr_idx as u16;
477 }
478 if detected_an_unbalanced_instruction? {
479 Err(InstructionError::UnbalancedInstruction)
480 } else {
481 Ok(())
482 }
483 }
484
485 pub fn get_return_data(&self) -> (&Pubkey, &[u8]) {
487 (
488 &self.transaction_frame.return_data_pubkey,
489 &self.return_data_bytes,
490 )
491 }
492
493 pub fn set_return_data(
495 &mut self,
496 program_id: Pubkey,
497 data: Vec<u8>,
498 ) -> Result<(), InstructionError> {
499 self.transaction_frame.return_data_pubkey = program_id;
500 self.transaction_frame
501 .return_data_scratchpad
502 .set_len(data.len() as u64);
503 self.return_data_bytes = data;
504 Ok(())
505 }
506
507 pub fn access_violation_handler(
509 &self,
510 virtual_address_space_adjustments: bool,
511 account_data_direct_mapping: bool,
512 ) -> AccessViolationHandler {
513 let accounts = Rc::clone(&self.accounts);
514 Box::new(
515 move |region: &mut MemoryRegion,
516 address_space_reserved_for_account: u64,
517 access_type: AccessType,
518 vm_addr: u64,
519 len: u64| {
520 if access_type == AccessType::Load {
521 return;
522 }
523 let Some(index_in_transaction) = region.access_violation_handler_payload else {
524 return;
526 };
527 let region_vm_addr_start = region.vm_addr_range().start;
528 let requested_length = vm_addr
529 .saturating_add(len)
530 .saturating_sub(region_vm_addr_start)
531 as usize;
532 if requested_length > address_space_reserved_for_account as usize {
533 return;
535 }
536
537 let Ok(mut account) = accounts.try_borrow_mut(index_in_transaction) else {
541 debug_assert!(false);
542 return;
543 };
544 if accounts.touch(index_in_transaction).is_err() {
545 debug_assert!(false);
546 return;
547 }
548
549 let remaining_allowed_growth = MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION
550 .saturating_sub(accounts.resize_delta())
551 .max(0) as usize;
552
553 if requested_length > region.len() {
554 let old_len = account.data().len();
558 let new_len = (address_space_reserved_for_account as usize)
559 .min(MAX_ACCOUNT_DATA_LEN as usize)
560 .min(old_len.saturating_add(remaining_allowed_growth));
561 debug_assert!(accounts.can_data_be_resized(old_len, new_len).is_ok());
563 if accounts
564 .update_accounts_resize_delta(old_len, new_len)
565 .is_err()
566 {
567 return;
568 }
569 unsafe {
570 account.resize(new_len, 0);
571 region.redirect(region.host_buffer().mutable());
600 }
601 }
602
603 if virtual_address_space_adjustments && account_data_direct_mapping {
605 unsafe {
606 region.redirect(account.raw_mut_data_slice());
608 }
609 }
610 },
611 )
612 }
613
614 pub fn take_instruction_trace(&mut self) -> InstructionTrace<'_> {
616 self.instruction_trace.pop();
619 (
620 std::mem::take(&mut self.instruction_trace),
621 std::mem::take(&mut self.instruction_accounts),
622 std::mem::take(&mut self.instruction_data),
623 )
624 }
625
626 pub fn number_of_called_instructions_in_trace(&self) -> usize {
632 self.next_top_level_instruction_index
633 .saturating_add(self.transaction_frame.number_of_cpis_in_trace as usize)
634 }
635
636 pub fn next_top_level_instruction_index(&self) -> usize {
638 self.next_top_level_instruction_index
639 }
640
641 pub fn number_of_cpis_in_trace(&self) -> usize {
643 self.transaction_frame.number_of_cpis_in_trace as usize
644 }
645}
646
647#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
649#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
650#[cfg_attr(feature = "wincode", derive(wincode::SchemaRead, wincode::SchemaWrite))]
651#[derive(Clone, Debug, Default, PartialEq, Eq)]
652pub struct TransactionReturnData {
653 pub program_id: Pubkey,
654 pub data: Vec<u8>,
655}
656
657#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
659pub struct ExecutionRecord {
660 pub accounts: Vec<KeyedAccountSharedData>,
661 pub return_data: TransactionReturnData,
662 pub touched_flags: Box<[bool]>,
664 pub accounts_resize_delta: i64,
665}
666
667#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
669impl From<TransactionContext<'_>> for ExecutionRecord {
670 fn from(context: TransactionContext) -> Self {
671 let (accounts, touched_flags, resize_delta) = Rc::try_unwrap(context.accounts)
672 .expect("transaction_context.accounts has unexpected outstanding refs")
673 .take();
674
675 let touched_flags: Box<[bool]> = Vec::from(touched_flags)
681 .into_iter()
682 .map(|flag| flag.into_inner())
683 .collect();
684
685 let return_data = TransactionReturnData {
686 program_id: context.transaction_frame.return_data_pubkey,
687 data: context.return_data_bytes,
688 };
689
690 Self {
691 accounts,
692 return_data,
693 touched_flags,
694 accounts_resize_delta: Cell::into_inner(resize_delta),
695 }
696 }
697}
698
699#[cfg(all(test, not(target_arch = "sbf"), not(target_arch = "bpf")))]
700mod tests {
701 use super::*;
702
703 #[test]
704 fn test_instructions_sysvar_store_index_checked() {
705 let build_transaction_context = |account: AccountSharedData| {
706 TransactionContext::new(
707 vec![
708 (Pubkey::new_unique(), AccountSharedData::default()),
709 (instructions::id(), account),
710 ],
711 Rent::default(),
712 2,
713 2,
714 1,
715 )
716 };
717
718 let correct_space = 2;
719 let rent_exempt_lamports = Rent::default().minimum_balance(correct_space);
720
721 let account =
723 AccountSharedData::new(rent_exempt_lamports, correct_space, &Pubkey::new_unique());
724 assert_eq!(
725 build_transaction_context(account).push(),
726 Err(InstructionError::InvalidAccountOwner),
727 );
728
729 let account =
731 AccountSharedData::new(rent_exempt_lamports, 0, &solana_sdk_ids::sysvar::id());
732 assert_eq!(
733 build_transaction_context(account).push(),
734 Err(InstructionError::AccountDataTooSmall),
735 );
736
737 let account = AccountSharedData::new(
739 rent_exempt_lamports,
740 correct_space,
741 &solana_sdk_ids::sysvar::id(),
742 );
743 assert_eq!(build_transaction_context(account).push(), Ok(()),);
744 }
745
746 #[test]
747 fn test_invalid_native_loader_index() {
748 let mut transaction_context = TransactionContext::new(
749 vec![(
750 Pubkey::new_unique(),
751 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
752 )],
753 Rent::default(),
754 20,
755 20,
756 1,
757 );
758
759 transaction_context
760 .configure_top_level_instruction_for_tests(
761 u16::MAX,
762 vec![InstructionAccount::new(0, false, false)],
763 vec![],
764 )
765 .unwrap();
766 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
767
768 let result = instruction_context.get_index_of_program_account_in_transaction();
769 assert_eq!(result, Err(InstructionError::MissingAccount));
770
771 let result = instruction_context.get_program_key();
772 assert_eq!(result, Err(InstructionError::MissingAccount));
773
774 let result = instruction_context.get_program_owner();
775 assert_eq!(result.err(), Some(InstructionError::MissingAccount));
776 }
777
778 #[test]
779 fn test_instruction_shared_items() {
780 let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 10];
781 let mut transaction_context =
782 TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 3);
783
784 let instruction_accounts_1 = vec![
785 InstructionAccount::new(0, false, true),
786 InstructionAccount::new(3, true, false),
787 ];
788 transaction_context
789 .configure_top_level_instruction_for_tests(
790 1,
791 instruction_accounts_1.clone(),
792 vec![1, 2, 3, 4],
793 )
794 .unwrap();
795 transaction_context.push().unwrap();
796
797 let instruction_accounts_2 = vec![
798 InstructionAccount::new(0, false, true),
799 InstructionAccount::new(3, true, false),
800 InstructionAccount::new(5, false, false),
801 ];
802 transaction_context
803 .configure_top_level_instruction_for_tests(
804 1,
805 instruction_accounts_2.clone(),
806 vec![5, 6, 7, 8, 9],
807 )
808 .unwrap();
809 transaction_context.push().unwrap();
810
811 let instruction_accounts_3 = vec![
812 InstructionAccount::new(0, false, true),
813 InstructionAccount::new(3, true, false),
814 InstructionAccount::new(5, false, false),
815 InstructionAccount::new(3, false, false),
816 InstructionAccount::new(10, false, false),
817 ];
818 transaction_context
819 .configure_top_level_instruction_for_tests(
820 1,
821 instruction_accounts_3.clone(),
822 vec![10, 11],
823 )
824 .unwrap();
825 transaction_context.push().unwrap();
826
827 let first_ix_context = transaction_context
828 .get_instruction_context_at_index_in_trace(0)
829 .unwrap();
830 assert_eq!(
831 instruction_accounts_1.as_slice(),
832 first_ix_context.instruction_accounts
833 );
834 assert_eq!(
835 *first_ix_context.instruction_data,
836 **transaction_context.instruction_data.first().unwrap()
837 );
838 for (idx_in_ix, acc) in instruction_accounts_1.iter().enumerate() {
839 assert_eq!(
840 *first_ix_context
841 .dedup_map
842 .get(acc.index_in_transaction as usize)
843 .unwrap(),
844 idx_in_ix as u16
845 );
846 }
847
848 let second_ix_context = transaction_context
849 .get_instruction_context_at_index_in_trace(1)
850 .unwrap();
851 assert_eq!(
852 instruction_accounts_2.as_slice(),
853 second_ix_context.instruction_accounts
854 );
855 assert_eq!(
856 *second_ix_context.instruction_data,
857 **transaction_context.instruction_data.get(1).unwrap()
858 );
859 for (idx_in_ix, acc) in instruction_accounts_2.iter().enumerate() {
860 assert_eq!(
861 *second_ix_context
862 .dedup_map
863 .get(acc.index_in_transaction as usize)
864 .unwrap(),
865 idx_in_ix as u16
866 );
867 }
868
869 let third_ix_context = transaction_context
870 .get_instruction_context_at_index_in_trace(2)
871 .unwrap();
872 assert_eq!(
873 instruction_accounts_3.as_slice(),
874 third_ix_context.instruction_accounts
875 );
876 assert_eq!(
877 *third_ix_context.instruction_data,
878 **transaction_context.instruction_data.get(2).unwrap()
879 );
880 for (idx_in_ix, acc) in instruction_accounts_3.iter().enumerate() {
881 if idx_in_ix == 3 {
882 assert_eq!(
883 *third_ix_context
884 .dedup_map
885 .get(acc.index_in_transaction as usize)
886 .unwrap(),
887 1
888 );
889 } else {
890 assert_eq!(
891 *third_ix_context
892 .dedup_map
893 .get(acc.index_in_transaction as usize)
894 .unwrap(),
895 idx_in_ix as u16
896 );
897 }
898 }
899 }
900
901 #[test]
902 fn test_number_of_instructions() {
903 let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
904 let mut transaction_context =
905 TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 2);
906 assert_eq!(
907 transaction_context
908 .transaction_frame
909 .number_of_cpis_in_trace,
910 0
911 );
912
913 transaction_context
915 .configure_instruction_at_index(
916 0,
917 0,
918 vec![InstructionAccount::new(1, false, false)],
919 vec![0; MAX_ACCOUNTS_PER_TRANSACTION],
920 Vec::new().into(),
921 None,
922 )
923 .unwrap();
924
925 transaction_context
927 .configure_instruction_at_index(
928 1,
929 0,
930 vec![InstructionAccount::new(1, false, false)],
931 vec![0; MAX_ACCOUNTS_PER_TRANSACTION],
932 Vec::new().into(),
933 None,
934 )
935 .unwrap();
936
937 transaction_context.push().unwrap();
939 assert_eq!(
940 transaction_context
941 .transaction_frame
942 .current_executing_instruction,
943 0
944 );
945 assert_eq!(
946 transaction_context.number_of_called_instructions_in_trace(),
947 1
948 );
949
950 assert_eq!(
951 transaction_context
952 .transaction_frame
953 .total_number_of_instructions_in_trace,
954 2
955 );
956
957 assert_eq!(
958 transaction_context
959 .transaction_frame
960 .number_of_cpis_in_trace,
961 0
962 );
963
964 assert_eq!(
965 transaction_context
966 .transaction_frame
967 .cpi_data_scratchpad
968 .ptr(),
969 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(2))
970 );
971 assert_eq!(
972 transaction_context
973 .transaction_frame
974 .cpi_data_scratchpad
975 .len(),
976 0,
977 );
978 assert_eq!(
979 transaction_context
980 .transaction_frame
981 .cpi_accounts_scratchpad
982 .ptr(),
983 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
984 .saturating_add(GUEST_REGION_SIZE.saturating_mul(2))
985 );
986 assert_eq!(
987 transaction_context
988 .transaction_frame
989 .cpi_data_scratchpad
990 .len(),
991 0,
992 );
993
994 assert_eq!(
995 transaction_context.number_of_called_instructions_in_trace(),
996 1
997 );
998
999 transaction_context
1001 .configure_next_cpi_for_tests(
1002 0,
1003 vec![InstructionAccount::new(2, false, true)],
1004 Vec::new(),
1005 )
1006 .unwrap();
1007
1008 transaction_context.push().unwrap();
1009 assert_eq!(
1010 transaction_context
1011 .transaction_frame
1012 .current_executing_instruction,
1013 2
1014 );
1015
1016 assert_eq!(
1017 transaction_context
1018 .transaction_frame
1019 .total_number_of_instructions_in_trace,
1020 3
1021 );
1022 assert_eq!(
1023 transaction_context
1024 .transaction_frame
1025 .number_of_cpis_in_trace,
1026 1
1027 );
1028 assert_eq!(
1029 transaction_context.number_of_called_instructions_in_trace(),
1030 2
1031 );
1032
1033 assert_eq!(
1034 transaction_context
1035 .transaction_frame
1036 .cpi_data_scratchpad
1037 .ptr(),
1038 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(3))
1039 );
1040 assert_eq!(
1041 transaction_context
1042 .transaction_frame
1043 .cpi_accounts_scratchpad
1044 .ptr(),
1045 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1046 .saturating_add(GUEST_REGION_SIZE.saturating_mul(3))
1047 );
1048
1049 transaction_context
1051 .configure_next_cpi_for_tests(
1052 0,
1053 vec![InstructionAccount::new(2, false, true)],
1054 Vec::new(),
1055 )
1056 .unwrap();
1057
1058 transaction_context.push().unwrap();
1059 assert_eq!(
1060 transaction_context
1061 .transaction_frame
1062 .current_executing_instruction,
1063 3
1064 );
1065
1066 assert_eq!(
1067 transaction_context
1068 .transaction_frame
1069 .total_number_of_instructions_in_trace,
1070 4
1071 );
1072
1073 assert_eq!(
1074 transaction_context
1075 .transaction_frame
1076 .cpi_data_scratchpad
1077 .ptr(),
1078 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(4))
1079 );
1080 assert_eq!(
1081 transaction_context
1082 .transaction_frame
1083 .cpi_accounts_scratchpad
1084 .ptr(),
1085 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1086 .saturating_add(GUEST_REGION_SIZE.saturating_mul(4))
1087 );
1088
1089 assert_eq!(
1090 transaction_context
1091 .transaction_frame
1092 .number_of_cpis_in_trace,
1093 2
1094 );
1095
1096 assert_eq!(
1097 transaction_context.number_of_called_instructions_in_trace(),
1098 3
1099 );
1100 transaction_context.pop().unwrap();
1102 assert_eq!(
1103 transaction_context.number_of_called_instructions_in_trace(),
1104 3
1105 );
1106
1107 assert_eq!(
1108 transaction_context
1109 .transaction_frame
1110 .total_number_of_instructions_in_trace,
1111 4
1112 );
1113 assert_eq!(
1114 transaction_context
1115 .transaction_frame
1116 .number_of_cpis_in_trace,
1117 2,
1118 );
1119 assert_eq!(
1120 transaction_context
1121 .transaction_frame
1122 .current_executing_instruction,
1123 2
1124 );
1125
1126 transaction_context
1128 .configure_next_cpi_for_tests(
1129 0,
1130 vec![InstructionAccount::new(2, false, true)],
1131 Vec::new(),
1132 )
1133 .unwrap();
1134
1135 transaction_context.push().unwrap();
1136 assert_eq!(
1137 transaction_context
1138 .transaction_frame
1139 .current_executing_instruction,
1140 4
1141 );
1142
1143 assert_eq!(
1144 transaction_context
1145 .transaction_frame
1146 .total_number_of_instructions_in_trace,
1147 5
1148 );
1149
1150 assert_eq!(
1151 transaction_context
1152 .transaction_frame
1153 .cpi_data_scratchpad
1154 .ptr(),
1155 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1156 );
1157 assert_eq!(
1158 transaction_context
1159 .transaction_frame
1160 .cpi_accounts_scratchpad
1161 .ptr(),
1162 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1163 .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1164 );
1165
1166 assert_eq!(
1167 transaction_context
1168 .transaction_frame
1169 .number_of_cpis_in_trace,
1170 3
1171 );
1172 assert_eq!(
1173 transaction_context.number_of_called_instructions_in_trace(),
1174 4
1175 );
1176
1177 transaction_context.pop().unwrap();
1179
1180 assert_eq!(
1181 transaction_context
1182 .transaction_frame
1183 .current_executing_instruction,
1184 2
1185 );
1186
1187 assert_eq!(
1188 transaction_context
1189 .transaction_frame
1190 .total_number_of_instructions_in_trace,
1191 5
1192 );
1193
1194 assert_eq!(
1195 transaction_context
1196 .transaction_frame
1197 .cpi_data_scratchpad
1198 .ptr(),
1199 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1200 );
1201 assert_eq!(
1202 transaction_context
1203 .transaction_frame
1204 .cpi_accounts_scratchpad
1205 .ptr(),
1206 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1207 .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1208 );
1209
1210 assert_eq!(
1211 transaction_context
1212 .transaction_frame
1213 .number_of_cpis_in_trace,
1214 3
1215 );
1216
1217 transaction_context.pop().unwrap();
1219 assert_eq!(
1220 transaction_context.number_of_called_instructions_in_trace(),
1221 4
1222 );
1223
1224 assert_eq!(
1225 transaction_context
1226 .transaction_frame
1227 .current_executing_instruction,
1228 0
1229 );
1230
1231 assert_eq!(
1232 transaction_context
1233 .transaction_frame
1234 .total_number_of_instructions_in_trace,
1235 5
1236 );
1237
1238 assert_eq!(
1239 transaction_context
1240 .transaction_frame
1241 .cpi_data_scratchpad
1242 .ptr(),
1243 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1244 );
1245 assert_eq!(
1246 transaction_context
1247 .transaction_frame
1248 .cpi_accounts_scratchpad
1249 .ptr(),
1250 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1251 .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1252 );
1253
1254 assert_eq!(
1255 transaction_context
1256 .transaction_frame
1257 .number_of_cpis_in_trace,
1258 3,
1259 );
1260
1261 transaction_context.pop().unwrap();
1263 transaction_context.push().unwrap();
1264 assert_eq!(
1265 transaction_context
1266 .transaction_frame
1267 .current_executing_instruction,
1268 1,
1269 );
1270 assert_eq!(
1271 transaction_context
1272 .transaction_frame
1273 .number_of_cpis_in_trace,
1274 3
1275 );
1276
1277 transaction_context
1279 .configure_next_cpi_for_tests(
1280 0,
1281 vec![InstructionAccount::new(2, false, true)],
1282 Vec::new(),
1283 )
1284 .unwrap();
1285
1286 transaction_context.push().unwrap();
1287
1288 assert_eq!(
1289 transaction_context
1290 .transaction_frame
1291 .current_executing_instruction,
1292 5,
1293 );
1294
1295 assert_eq!(
1296 transaction_context
1297 .transaction_frame
1298 .total_number_of_instructions_in_trace,
1299 6
1300 );
1301
1302 assert_eq!(
1303 transaction_context
1304 .transaction_frame
1305 .cpi_data_scratchpad
1306 .ptr(),
1307 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(6))
1308 );
1309 assert_eq!(
1310 transaction_context
1311 .transaction_frame
1312 .cpi_accounts_scratchpad
1313 .ptr(),
1314 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1315 .saturating_add(GUEST_REGION_SIZE.saturating_mul(6))
1316 );
1317
1318 assert_eq!(
1319 transaction_context
1320 .transaction_frame
1321 .number_of_cpis_in_trace,
1322 4
1323 );
1324 assert_eq!(
1325 transaction_context.number_of_called_instructions_in_trace(),
1326 6
1327 );
1328
1329 transaction_context.pop().unwrap();
1331 assert_eq!(
1332 transaction_context
1333 .transaction_frame
1334 .number_of_cpis_in_trace,
1335 4
1336 );
1337 assert_eq!(
1338 transaction_context
1339 .transaction_frame
1340 .current_executing_instruction,
1341 1,
1342 );
1343
1344 transaction_context.pop().unwrap();
1345 }
1346
1347 #[test]
1348 fn test_get_current_instruction_index() {
1349 let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
1350 let mut transaction_context =
1351 TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 2);
1352
1353 transaction_context
1355 .configure_instruction_at_index(
1356 0,
1357 1,
1358 vec![
1359 InstructionAccount::new(0, false, false),
1360 InstructionAccount::new(1, false, false),
1361 ],
1362 vec![u16::MAX; 256],
1363 Cow::Owned(Vec::new()),
1364 None,
1365 )
1366 .unwrap();
1367
1368 transaction_context
1370 .configure_instruction_at_index(
1371 1,
1372 1,
1373 vec![
1374 InstructionAccount::new(0, false, false),
1375 InstructionAccount::new(1, false, true),
1376 ],
1377 vec![u16::MAX; 256],
1378 Cow::Owned(Vec::new()),
1379 None,
1380 )
1381 .unwrap();
1382
1383 transaction_context.push().unwrap();
1384 assert_eq!(
1385 transaction_context.get_current_instruction_index().unwrap(),
1386 0
1387 );
1388
1389 transaction_context.pop().unwrap();
1390
1391 transaction_context.push().unwrap();
1392 assert_eq!(
1393 transaction_context.get_current_instruction_index().unwrap(),
1394 1
1395 );
1396
1397 transaction_context
1399 .configure_next_cpi_for_tests(
1400 1,
1401 vec![
1402 InstructionAccount::new(0, false, true),
1403 InstructionAccount::new(1, false, false),
1404 ],
1405 Vec::new(),
1406 )
1407 .unwrap();
1408 transaction_context.push().unwrap();
1409 assert_eq!(
1410 transaction_context.get_current_instruction_index().unwrap(),
1411 2
1412 );
1413
1414 transaction_context
1416 .configure_next_cpi_for_tests(
1417 1,
1418 vec![
1419 InstructionAccount::new(0, false, true),
1420 InstructionAccount::new(1, false, false),
1421 ],
1422 Vec::new(),
1423 )
1424 .unwrap();
1425 transaction_context.push().unwrap();
1426 assert_eq!(
1427 transaction_context.get_current_instruction_index().unwrap(),
1428 3
1429 );
1430
1431 transaction_context.pop().unwrap();
1433 assert_eq!(
1434 transaction_context.get_current_instruction_index().unwrap(),
1435 2
1436 );
1437
1438 transaction_context.pop().unwrap();
1440 assert_eq!(
1441 transaction_context.get_current_instruction_index().unwrap(),
1442 1
1443 );
1444 }
1445}