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 unsafe {
504 self.transaction_frame
505 .return_data_scratchpad
506 .set_len(data.len() as u64);
507 }
508 self.return_data_bytes = data;
509 Ok(())
510 }
511
512 pub fn access_violation_handler(
514 &self,
515 virtual_address_space_adjustments: bool,
516 account_data_direct_mapping: bool,
517 ) -> AccessViolationHandler {
518 let accounts = Rc::clone(&self.accounts);
519 Box::new(
520 move |region: &mut MemoryRegion,
521 address_space_reserved_for_account: u64,
522 access_type: AccessType,
523 vm_addr: u64,
524 len: u64| {
525 if access_type == AccessType::Load {
526 return;
527 }
528 let Some(index_in_transaction) = region.access_violation_handler_payload else {
529 return;
531 };
532 let requested_length =
533 vm_addr.saturating_add(len).saturating_sub(region.vm_addr) as usize;
534 if requested_length > address_space_reserved_for_account as usize {
535 return;
537 }
538
539 let Ok(mut account) = accounts.try_borrow_mut(index_in_transaction) else {
543 debug_assert!(false);
544 return;
545 };
546 if accounts.touch(index_in_transaction).is_err() {
547 debug_assert!(false);
548 return;
549 }
550
551 let remaining_allowed_growth = MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION
552 .saturating_sub(accounts.resize_delta())
553 .max(0) as usize;
554
555 if requested_length > region.len as usize {
556 let old_len = account.data().len();
560 let new_len = (address_space_reserved_for_account as usize)
561 .min(MAX_ACCOUNT_DATA_LEN as usize)
562 .min(old_len.saturating_add(remaining_allowed_growth));
563 debug_assert!(accounts.can_data_be_resized(old_len, new_len).is_ok());
565 if accounts
566 .update_accounts_resize_delta(old_len, new_len)
567 .is_err()
568 {
569 return;
570 }
571 account.resize(new_len, 0);
572 region.len = new_len as u64;
573 }
574
575 if virtual_address_space_adjustments && account_data_direct_mapping {
577 region.host_addr = account.data_as_mut_slice().as_mut_ptr() as u64;
578 region.writable = true;
579 }
580 },
581 )
582 }
583
584 pub fn take_instruction_trace(&mut self) -> InstructionTrace<'_> {
586 self.instruction_trace.pop();
589 (
590 std::mem::take(&mut self.instruction_trace),
591 std::mem::take(&mut self.instruction_accounts),
592 std::mem::take(&mut self.instruction_data),
593 )
594 }
595
596 pub fn number_of_called_instructions_in_trace(&self) -> usize {
602 self.next_top_level_instruction_index
603 .saturating_add(self.transaction_frame.number_of_cpis_in_trace as usize)
604 }
605
606 pub fn next_top_level_instruction_index(&self) -> usize {
608 self.next_top_level_instruction_index
609 }
610
611 pub fn number_of_cpis_in_trace(&self) -> usize {
613 self.transaction_frame.number_of_cpis_in_trace as usize
614 }
615}
616
617#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
619#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
620#[cfg_attr(feature = "wincode", derive(wincode::SchemaRead, wincode::SchemaWrite))]
621#[derive(Clone, Debug, Default, PartialEq, Eq)]
622pub struct TransactionReturnData {
623 pub program_id: Pubkey,
624 pub data: Vec<u8>,
625}
626
627#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
629pub struct ExecutionRecord {
630 pub accounts: Vec<KeyedAccountSharedData>,
631 pub return_data: TransactionReturnData,
632 pub touched_flags: Box<[bool]>,
634 pub accounts_resize_delta: i64,
635}
636
637#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
639impl From<TransactionContext<'_>> for ExecutionRecord {
640 fn from(context: TransactionContext) -> Self {
641 let (accounts, touched_flags, resize_delta) = Rc::try_unwrap(context.accounts)
642 .expect("transaction_context.accounts has unexpected outstanding refs")
643 .take();
644
645 let touched_flags: Box<[bool]> = Vec::from(touched_flags)
651 .into_iter()
652 .map(|flag| flag.into_inner())
653 .collect();
654
655 let return_data = TransactionReturnData {
656 program_id: context.transaction_frame.return_data_pubkey,
657 data: context.return_data_bytes,
658 };
659
660 Self {
661 accounts,
662 return_data,
663 touched_flags,
664 accounts_resize_delta: Cell::into_inner(resize_delta),
665 }
666 }
667}
668
669#[cfg(all(test, not(target_arch = "sbf"), not(target_arch = "bpf")))]
670mod tests {
671 use super::*;
672
673 #[test]
674 fn test_instructions_sysvar_store_index_checked() {
675 let build_transaction_context = |account: AccountSharedData| {
676 TransactionContext::new(
677 vec![
678 (Pubkey::new_unique(), AccountSharedData::default()),
679 (instructions::id(), account),
680 ],
681 Rent::default(),
682 2,
683 2,
684 1,
685 )
686 };
687
688 let correct_space = 2;
689 let rent_exempt_lamports = Rent::default().minimum_balance(correct_space);
690
691 let account =
693 AccountSharedData::new(rent_exempt_lamports, correct_space, &Pubkey::new_unique());
694 assert_eq!(
695 build_transaction_context(account).push(),
696 Err(InstructionError::InvalidAccountOwner),
697 );
698
699 let account =
701 AccountSharedData::new(rent_exempt_lamports, 0, &solana_sdk_ids::sysvar::id());
702 assert_eq!(
703 build_transaction_context(account).push(),
704 Err(InstructionError::AccountDataTooSmall),
705 );
706
707 let account = AccountSharedData::new(
709 rent_exempt_lamports,
710 correct_space,
711 &solana_sdk_ids::sysvar::id(),
712 );
713 assert_eq!(build_transaction_context(account).push(), Ok(()),);
714 }
715
716 #[test]
717 fn test_invalid_native_loader_index() {
718 let mut transaction_context = TransactionContext::new(
719 vec![(
720 Pubkey::new_unique(),
721 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
722 )],
723 Rent::default(),
724 20,
725 20,
726 1,
727 );
728
729 transaction_context
730 .configure_top_level_instruction_for_tests(
731 u16::MAX,
732 vec![InstructionAccount::new(0, false, false)],
733 vec![],
734 )
735 .unwrap();
736 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
737
738 let result = instruction_context.get_index_of_program_account_in_transaction();
739 assert_eq!(result, Err(InstructionError::MissingAccount));
740
741 let result = instruction_context.get_program_key();
742 assert_eq!(result, Err(InstructionError::MissingAccount));
743
744 let result = instruction_context.get_program_owner();
745 assert_eq!(result.err(), Some(InstructionError::MissingAccount));
746 }
747
748 #[test]
749 fn test_instruction_shared_items() {
750 let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 10];
751 let mut transaction_context =
752 TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 3);
753
754 let instruction_accounts_1 = vec![
755 InstructionAccount::new(0, false, true),
756 InstructionAccount::new(3, true, false),
757 ];
758 transaction_context
759 .configure_top_level_instruction_for_tests(
760 1,
761 instruction_accounts_1.clone(),
762 vec![1, 2, 3, 4],
763 )
764 .unwrap();
765 transaction_context.push().unwrap();
766
767 let instruction_accounts_2 = vec![
768 InstructionAccount::new(0, false, true),
769 InstructionAccount::new(3, true, false),
770 InstructionAccount::new(5, false, false),
771 ];
772 transaction_context
773 .configure_top_level_instruction_for_tests(
774 1,
775 instruction_accounts_2.clone(),
776 vec![5, 6, 7, 8, 9],
777 )
778 .unwrap();
779 transaction_context.push().unwrap();
780
781 let instruction_accounts_3 = vec![
782 InstructionAccount::new(0, false, true),
783 InstructionAccount::new(3, true, false),
784 InstructionAccount::new(5, false, false),
785 InstructionAccount::new(3, false, false),
786 InstructionAccount::new(10, false, false),
787 ];
788 transaction_context
789 .configure_top_level_instruction_for_tests(
790 1,
791 instruction_accounts_3.clone(),
792 vec![10, 11],
793 )
794 .unwrap();
795 transaction_context.push().unwrap();
796
797 let first_ix_context = transaction_context
798 .get_instruction_context_at_index_in_trace(0)
799 .unwrap();
800 assert_eq!(
801 instruction_accounts_1.as_slice(),
802 first_ix_context.instruction_accounts
803 );
804 assert_eq!(
805 *first_ix_context.instruction_data,
806 **transaction_context.instruction_data.first().unwrap()
807 );
808 for (idx_in_ix, acc) in instruction_accounts_1.iter().enumerate() {
809 assert_eq!(
810 *first_ix_context
811 .dedup_map
812 .get(acc.index_in_transaction as usize)
813 .unwrap(),
814 idx_in_ix as u16
815 );
816 }
817
818 let second_ix_context = transaction_context
819 .get_instruction_context_at_index_in_trace(1)
820 .unwrap();
821 assert_eq!(
822 instruction_accounts_2.as_slice(),
823 second_ix_context.instruction_accounts
824 );
825 assert_eq!(
826 *second_ix_context.instruction_data,
827 **transaction_context.instruction_data.get(1).unwrap()
828 );
829 for (idx_in_ix, acc) in instruction_accounts_2.iter().enumerate() {
830 assert_eq!(
831 *second_ix_context
832 .dedup_map
833 .get(acc.index_in_transaction as usize)
834 .unwrap(),
835 idx_in_ix as u16
836 );
837 }
838
839 let third_ix_context = transaction_context
840 .get_instruction_context_at_index_in_trace(2)
841 .unwrap();
842 assert_eq!(
843 instruction_accounts_3.as_slice(),
844 third_ix_context.instruction_accounts
845 );
846 assert_eq!(
847 *third_ix_context.instruction_data,
848 **transaction_context.instruction_data.get(2).unwrap()
849 );
850 for (idx_in_ix, acc) in instruction_accounts_3.iter().enumerate() {
851 if idx_in_ix == 3 {
852 assert_eq!(
853 *third_ix_context
854 .dedup_map
855 .get(acc.index_in_transaction as usize)
856 .unwrap(),
857 1
858 );
859 } else {
860 assert_eq!(
861 *third_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 }
870
871 #[test]
872 fn test_number_of_instructions() {
873 let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
874 let mut transaction_context =
875 TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 2);
876 assert_eq!(
877 transaction_context
878 .transaction_frame
879 .number_of_cpis_in_trace,
880 0
881 );
882
883 transaction_context
885 .configure_instruction_at_index(
886 0,
887 0,
888 vec![InstructionAccount::new(1, false, false)],
889 vec![0; MAX_ACCOUNTS_PER_TRANSACTION],
890 Vec::new().into(),
891 None,
892 )
893 .unwrap();
894
895 transaction_context
897 .configure_instruction_at_index(
898 1,
899 0,
900 vec![InstructionAccount::new(1, false, false)],
901 vec![0; MAX_ACCOUNTS_PER_TRANSACTION],
902 Vec::new().into(),
903 None,
904 )
905 .unwrap();
906
907 transaction_context.push().unwrap();
909 assert_eq!(
910 transaction_context
911 .transaction_frame
912 .current_executing_instruction,
913 0
914 );
915 assert_eq!(
916 transaction_context.number_of_called_instructions_in_trace(),
917 1
918 );
919
920 assert_eq!(
921 transaction_context
922 .transaction_frame
923 .total_number_of_instructions_in_trace,
924 2
925 );
926
927 assert_eq!(
928 transaction_context
929 .transaction_frame
930 .number_of_cpis_in_trace,
931 0
932 );
933
934 assert_eq!(
935 transaction_context
936 .transaction_frame
937 .cpi_data_scratchpad
938 .ptr(),
939 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(2))
940 );
941 assert_eq!(
942 transaction_context
943 .transaction_frame
944 .cpi_data_scratchpad
945 .len(),
946 0,
947 );
948 assert_eq!(
949 transaction_context
950 .transaction_frame
951 .cpi_accounts_scratchpad
952 .ptr(),
953 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
954 .saturating_add(GUEST_REGION_SIZE.saturating_mul(2))
955 );
956 assert_eq!(
957 transaction_context
958 .transaction_frame
959 .cpi_data_scratchpad
960 .len(),
961 0,
962 );
963
964 assert_eq!(
965 transaction_context.number_of_called_instructions_in_trace(),
966 1
967 );
968
969 transaction_context
971 .configure_next_cpi_for_tests(
972 0,
973 vec![InstructionAccount::new(2, false, true)],
974 Vec::new(),
975 )
976 .unwrap();
977
978 transaction_context.push().unwrap();
979 assert_eq!(
980 transaction_context
981 .transaction_frame
982 .current_executing_instruction,
983 2
984 );
985
986 assert_eq!(
987 transaction_context
988 .transaction_frame
989 .total_number_of_instructions_in_trace,
990 3
991 );
992 assert_eq!(
993 transaction_context
994 .transaction_frame
995 .number_of_cpis_in_trace,
996 1
997 );
998 assert_eq!(
999 transaction_context.number_of_called_instructions_in_trace(),
1000 2
1001 );
1002
1003 assert_eq!(
1004 transaction_context
1005 .transaction_frame
1006 .cpi_data_scratchpad
1007 .ptr(),
1008 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(3))
1009 );
1010 assert_eq!(
1011 transaction_context
1012 .transaction_frame
1013 .cpi_accounts_scratchpad
1014 .ptr(),
1015 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1016 .saturating_add(GUEST_REGION_SIZE.saturating_mul(3))
1017 );
1018
1019 transaction_context
1021 .configure_next_cpi_for_tests(
1022 0,
1023 vec![InstructionAccount::new(2, false, true)],
1024 Vec::new(),
1025 )
1026 .unwrap();
1027
1028 transaction_context.push().unwrap();
1029 assert_eq!(
1030 transaction_context
1031 .transaction_frame
1032 .current_executing_instruction,
1033 3
1034 );
1035
1036 assert_eq!(
1037 transaction_context
1038 .transaction_frame
1039 .total_number_of_instructions_in_trace,
1040 4
1041 );
1042
1043 assert_eq!(
1044 transaction_context
1045 .transaction_frame
1046 .cpi_data_scratchpad
1047 .ptr(),
1048 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(4))
1049 );
1050 assert_eq!(
1051 transaction_context
1052 .transaction_frame
1053 .cpi_accounts_scratchpad
1054 .ptr(),
1055 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1056 .saturating_add(GUEST_REGION_SIZE.saturating_mul(4))
1057 );
1058
1059 assert_eq!(
1060 transaction_context
1061 .transaction_frame
1062 .number_of_cpis_in_trace,
1063 2
1064 );
1065
1066 assert_eq!(
1067 transaction_context.number_of_called_instructions_in_trace(),
1068 3
1069 );
1070 transaction_context.pop().unwrap();
1072 assert_eq!(
1073 transaction_context.number_of_called_instructions_in_trace(),
1074 3
1075 );
1076
1077 assert_eq!(
1078 transaction_context
1079 .transaction_frame
1080 .total_number_of_instructions_in_trace,
1081 4
1082 );
1083 assert_eq!(
1084 transaction_context
1085 .transaction_frame
1086 .number_of_cpis_in_trace,
1087 2,
1088 );
1089 assert_eq!(
1090 transaction_context
1091 .transaction_frame
1092 .current_executing_instruction,
1093 2
1094 );
1095
1096 transaction_context
1098 .configure_next_cpi_for_tests(
1099 0,
1100 vec![InstructionAccount::new(2, false, true)],
1101 Vec::new(),
1102 )
1103 .unwrap();
1104
1105 transaction_context.push().unwrap();
1106 assert_eq!(
1107 transaction_context
1108 .transaction_frame
1109 .current_executing_instruction,
1110 4
1111 );
1112
1113 assert_eq!(
1114 transaction_context
1115 .transaction_frame
1116 .total_number_of_instructions_in_trace,
1117 5
1118 );
1119
1120 assert_eq!(
1121 transaction_context
1122 .transaction_frame
1123 .cpi_data_scratchpad
1124 .ptr(),
1125 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1126 );
1127 assert_eq!(
1128 transaction_context
1129 .transaction_frame
1130 .cpi_accounts_scratchpad
1131 .ptr(),
1132 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1133 .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1134 );
1135
1136 assert_eq!(
1137 transaction_context
1138 .transaction_frame
1139 .number_of_cpis_in_trace,
1140 3
1141 );
1142 assert_eq!(
1143 transaction_context.number_of_called_instructions_in_trace(),
1144 4
1145 );
1146
1147 transaction_context.pop().unwrap();
1149
1150 assert_eq!(
1151 transaction_context
1152 .transaction_frame
1153 .current_executing_instruction,
1154 2
1155 );
1156
1157 assert_eq!(
1158 transaction_context
1159 .transaction_frame
1160 .total_number_of_instructions_in_trace,
1161 5
1162 );
1163
1164 assert_eq!(
1165 transaction_context
1166 .transaction_frame
1167 .cpi_data_scratchpad
1168 .ptr(),
1169 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1170 );
1171 assert_eq!(
1172 transaction_context
1173 .transaction_frame
1174 .cpi_accounts_scratchpad
1175 .ptr(),
1176 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1177 .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1178 );
1179
1180 assert_eq!(
1181 transaction_context
1182 .transaction_frame
1183 .number_of_cpis_in_trace,
1184 3
1185 );
1186
1187 transaction_context.pop().unwrap();
1189 assert_eq!(
1190 transaction_context.number_of_called_instructions_in_trace(),
1191 4
1192 );
1193
1194 assert_eq!(
1195 transaction_context
1196 .transaction_frame
1197 .current_executing_instruction,
1198 0
1199 );
1200
1201 assert_eq!(
1202 transaction_context
1203 .transaction_frame
1204 .total_number_of_instructions_in_trace,
1205 5
1206 );
1207
1208 assert_eq!(
1209 transaction_context
1210 .transaction_frame
1211 .cpi_data_scratchpad
1212 .ptr(),
1213 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1214 );
1215 assert_eq!(
1216 transaction_context
1217 .transaction_frame
1218 .cpi_accounts_scratchpad
1219 .ptr(),
1220 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1221 .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1222 );
1223
1224 assert_eq!(
1225 transaction_context
1226 .transaction_frame
1227 .number_of_cpis_in_trace,
1228 3,
1229 );
1230
1231 transaction_context.pop().unwrap();
1233 transaction_context.push().unwrap();
1234 assert_eq!(
1235 transaction_context
1236 .transaction_frame
1237 .current_executing_instruction,
1238 1,
1239 );
1240 assert_eq!(
1241 transaction_context
1242 .transaction_frame
1243 .number_of_cpis_in_trace,
1244 3
1245 );
1246
1247 transaction_context
1249 .configure_next_cpi_for_tests(
1250 0,
1251 vec![InstructionAccount::new(2, false, true)],
1252 Vec::new(),
1253 )
1254 .unwrap();
1255
1256 transaction_context.push().unwrap();
1257
1258 assert_eq!(
1259 transaction_context
1260 .transaction_frame
1261 .current_executing_instruction,
1262 5,
1263 );
1264
1265 assert_eq!(
1266 transaction_context
1267 .transaction_frame
1268 .total_number_of_instructions_in_trace,
1269 6
1270 );
1271
1272 assert_eq!(
1273 transaction_context
1274 .transaction_frame
1275 .cpi_data_scratchpad
1276 .ptr(),
1277 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(6))
1278 );
1279 assert_eq!(
1280 transaction_context
1281 .transaction_frame
1282 .cpi_accounts_scratchpad
1283 .ptr(),
1284 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1285 .saturating_add(GUEST_REGION_SIZE.saturating_mul(6))
1286 );
1287
1288 assert_eq!(
1289 transaction_context
1290 .transaction_frame
1291 .number_of_cpis_in_trace,
1292 4
1293 );
1294 assert_eq!(
1295 transaction_context.number_of_called_instructions_in_trace(),
1296 6
1297 );
1298
1299 transaction_context.pop().unwrap();
1301 assert_eq!(
1302 transaction_context
1303 .transaction_frame
1304 .number_of_cpis_in_trace,
1305 4
1306 );
1307 assert_eq!(
1308 transaction_context
1309 .transaction_frame
1310 .current_executing_instruction,
1311 1,
1312 );
1313
1314 transaction_context.pop().unwrap();
1315 }
1316
1317 #[test]
1318 fn test_get_current_instruction_index() {
1319 let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
1320 let mut transaction_context =
1321 TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 2);
1322
1323 transaction_context
1325 .configure_instruction_at_index(
1326 0,
1327 1,
1328 vec![
1329 InstructionAccount::new(0, false, false),
1330 InstructionAccount::new(1, false, false),
1331 ],
1332 vec![u16::MAX; 256],
1333 Cow::Owned(Vec::new()),
1334 None,
1335 )
1336 .unwrap();
1337
1338 transaction_context
1340 .configure_instruction_at_index(
1341 1,
1342 1,
1343 vec![
1344 InstructionAccount::new(0, false, false),
1345 InstructionAccount::new(1, false, true),
1346 ],
1347 vec![u16::MAX; 256],
1348 Cow::Owned(Vec::new()),
1349 None,
1350 )
1351 .unwrap();
1352
1353 transaction_context.push().unwrap();
1354 assert_eq!(
1355 transaction_context.get_current_instruction_index().unwrap(),
1356 0
1357 );
1358
1359 transaction_context.pop().unwrap();
1360
1361 transaction_context.push().unwrap();
1362 assert_eq!(
1363 transaction_context.get_current_instruction_index().unwrap(),
1364 1
1365 );
1366
1367 transaction_context
1369 .configure_next_cpi_for_tests(
1370 1,
1371 vec![
1372 InstructionAccount::new(0, false, true),
1373 InstructionAccount::new(1, false, false),
1374 ],
1375 Vec::new(),
1376 )
1377 .unwrap();
1378 transaction_context.push().unwrap();
1379 assert_eq!(
1380 transaction_context.get_current_instruction_index().unwrap(),
1381 2
1382 );
1383
1384 transaction_context
1386 .configure_next_cpi_for_tests(
1387 1,
1388 vec![
1389 InstructionAccount::new(0, false, true),
1390 InstructionAccount::new(1, false, false),
1391 ],
1392 Vec::new(),
1393 )
1394 .unwrap();
1395 transaction_context.push().unwrap();
1396 assert_eq!(
1397 transaction_context.get_current_instruction_index().unwrap(),
1398 3
1399 );
1400
1401 transaction_context.pop().unwrap();
1403 assert_eq!(
1404 transaction_context.get_current_instruction_index().unwrap(),
1405 2
1406 );
1407
1408 transaction_context.pop().unwrap();
1410 assert_eq!(
1411 transaction_context.get_current_instruction_index().unwrap(),
1412 1
1413 );
1414 }
1415}