1#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
2use {
3 crate::{
4 DropOnBailOut, IndexOfAccount, MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION,
5 MAX_ACCOUNT_DATA_LEN, MAX_ACCOUNTS_PER_TRANSACTION,
6 instruction::{InstructionContext, InstructionFrame},
7 transaction_accounts::{KeyedAccountSharedData, TransactionAccounts},
8 vm_addresses::{
9 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS, GUEST_INSTRUCTION_DATA_BASE_ADDRESS,
10 GUEST_REGION_SIZE, RETURN_DATA_SCRATCHPAD,
11 },
12 },
13 solana_account::{AccountSharedData, ReadableAccount, WritableAccount},
14 solana_instruction_error::InstructionError,
15 solana_instructions_sysvar as instructions,
16 solana_rent::Rent,
17 solana_sbpf::memory_region::{AccessType, AccessViolationHandler, MemoryRegion},
18 std::{borrow::Cow, cell::Cell, rc::Rc},
19};
20use {
21 crate::{instruction_accounts::InstructionAccount, vm_slice::VmSlice},
22 solana_pubkey::Pubkey,
23};
24
25pub type InstructionTrace<'ix_data> = (
27 Vec<InstructionFrame>,
28 Vec<Box<[InstructionAccount]>>,
29 Vec<Cow<'ix_data, [u8]>>,
30);
31
32#[repr(C)]
37#[derive(Debug)]
38struct TransactionFrame {
39 return_data_pubkey: Pubkey,
41 return_data_scratchpad: VmSlice<u8>,
42 pub cpi_data_scratchpad: VmSlice<u8>,
44 pub cpi_accounts_scratchpad: VmSlice<InstructionAccount>,
46 current_executing_instruction: u16,
48 total_number_of_instructions_in_trace: u16,
50 number_of_cpis_in_trace: u16,
52 number_of_transaction_accounts: u16,
54}
55
56#[cfg(not(any(target_arch = "sbf", target_arch = "bpf")))]
57impl TransactionFrame {
58 fn configure_cpi(&mut self) {
59 self.total_number_of_instructions_in_trace =
60 self.total_number_of_instructions_in_trace.saturating_add(1);
61 let next_data_ptr = self
62 .cpi_data_scratchpad
63 .ptr()
64 .saturating_add(GUEST_REGION_SIZE);
65 self.cpi_data_scratchpad = VmSlice::new(next_data_ptr, 0);
66 let next_accounts_ptr = self
67 .cpi_accounts_scratchpad
68 .ptr()
69 .saturating_add(GUEST_REGION_SIZE);
70 self.cpi_accounts_scratchpad = VmSlice::new(next_accounts_ptr, 0);
71 }
72}
73
74#[derive(Debug)]
78#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
79pub struct TransactionContext<'ix_data> {
80 pub(crate) accounts: Rc<TransactionAccounts>,
81 instruction_stack_capacity: usize,
82 instruction_trace_capacity: usize,
83 instruction_stack: Vec<usize>,
84 instruction_trace: Vec<InstructionFrame>,
85 transaction_frame: TransactionFrame,
86 return_data_bytes: Vec<u8>,
87 next_top_level_instruction_index: usize,
88 #[cfg(not(target_os = "solana"))]
89 pub(crate) rent: Rent,
90 deduplication_maps: Vec<Box<[u8]>>,
94 instruction_accounts: Vec<Box<[InstructionAccount]>>,
96 instruction_data: Vec<Cow<'ix_data, [u8]>>,
99}
100
101#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
102impl<'ix_data> TransactionContext<'ix_data> {
103 pub fn new_with_feature_flags(
105 transaction_accounts: Vec<KeyedAccountSharedData>,
106 rent: Rent,
107 instruction_stack_capacity: usize,
108 instruction_trace_capacity: usize,
109 number_of_top_level_instructions: usize,
110 drop_on_bail_out: DropOnBailOut,
111 ) -> Self {
112 let transaction_frame = TransactionFrame {
113 return_data_pubkey: Pubkey::default(),
114 return_data_scratchpad: VmSlice::new(RETURN_DATA_SCRATCHPAD, 0),
115 cpi_data_scratchpad: VmSlice::new(
116 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(
117 GUEST_REGION_SIZE.saturating_mul(number_of_top_level_instructions as u64),
118 ),
119 0,
120 ),
121 cpi_accounts_scratchpad: VmSlice::new(
122 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS.saturating_add(
123 GUEST_REGION_SIZE.saturating_mul(number_of_top_level_instructions as u64),
124 ),
125 0,
126 ),
127 current_executing_instruction: 0,
128 total_number_of_instructions_in_trace: number_of_top_level_instructions as u16,
129 number_of_cpis_in_trace: 0,
130 number_of_transaction_accounts: transaction_accounts.len() as u16,
131 };
132
133 let mut instruction_trace =
135 Vec::with_capacity(instruction_trace_capacity.saturating_add(1));
136 instruction_trace.resize_with(
137 number_of_top_level_instructions.saturating_add(1),
138 InstructionFrame::default,
139 );
140
141 Self {
142 accounts: Rc::new(TransactionAccounts::new_with_feature_flags(
143 transaction_accounts,
144 drop_on_bail_out,
145 )),
146 instruction_stack_capacity,
147 instruction_trace_capacity,
148 instruction_stack: Vec::with_capacity(instruction_stack_capacity),
149 instruction_trace,
150 return_data_bytes: Vec::new(),
151 transaction_frame,
152 next_top_level_instruction_index: 0,
153 rent,
154 instruction_accounts: Vec::with_capacity(instruction_trace_capacity),
155 deduplication_maps: Vec::with_capacity(instruction_trace_capacity),
156 instruction_data: Vec::with_capacity(instruction_trace_capacity),
157 }
158 }
159
160 #[cfg(feature = "dev-context-only-utils")]
162 pub fn new(
163 transaction_accounts: Vec<KeyedAccountSharedData>,
164 rent: Rent,
165 instruction_stack_capacity: usize,
166 instruction_trace_capacity: usize,
167 number_of_top_level_instructions: usize,
168 ) -> Self {
169 Self::new_with_feature_flags(
170 transaction_accounts,
171 rent,
172 instruction_stack_capacity,
173 instruction_trace_capacity,
174 number_of_top_level_instructions,
175 DropOnBailOut::Disabled,
176 )
177 }
178
179 pub fn deconstruct_without_keys(self) -> Result<Vec<AccountSharedData>, InstructionError> {
181 if !self.instruction_stack.is_empty() {
182 return Err(InstructionError::CallDepth);
183 }
184
185 let accounts = Rc::try_unwrap(self.accounts)
186 .expect("transaction_context.accounts has unexpected outstanding refs")
187 .deconstruct_into_account_shared_data();
188
189 Ok(accounts)
190 }
191
192 pub fn accounts(&self) -> &Rc<TransactionAccounts> {
193 &self.accounts
194 }
195
196 pub fn get_number_of_accounts(&self) -> IndexOfAccount {
198 self.accounts.len() as IndexOfAccount
199 }
200
201 pub fn get_key_of_account_at_index(
203 &self,
204 index_in_transaction: IndexOfAccount,
205 ) -> Result<&Pubkey, InstructionError> {
206 self.accounts
207 .account_key(index_in_transaction)
208 .ok_or(InstructionError::MissingAccount)
209 }
210
211 pub fn find_index_of_account(&self, pubkey: &Pubkey) -> Option<IndexOfAccount> {
213 self.accounts
214 .account_keys_iter()
215 .position(|key| key == pubkey)
216 .map(|index| index as IndexOfAccount)
217 }
218
219 pub fn get_instruction_trace_capacity(&self) -> usize {
221 self.instruction_trace_capacity
222 }
223
224 pub fn get_instruction_trace_length(&self) -> usize {
228 self.instruction_trace.len().saturating_sub(1)
229 }
230
231 pub fn get_instruction_context_at_index_in_trace(
233 &self,
234 index_in_trace: usize,
235 ) -> Result<InstructionContext<'_, '_>, InstructionError> {
236 let instruction = self
237 .instruction_trace
238 .get(index_in_trace)
239 .ok_or(InstructionError::CallDepth)?;
240
241 let instruction_accounts = self
244 .instruction_accounts
245 .get(index_in_trace)
246 .map(|item| item.as_ref())
247 .unwrap_or_default();
248 let dedup_map = self
249 .deduplication_maps
250 .get(index_in_trace)
251 .map(|item| item.as_ref())
252 .unwrap_or_default();
253 let instruction_data = self
254 .instruction_data
255 .get(index_in_trace)
256 .map(|item| item.as_ref())
257 .unwrap_or_default();
258 Ok(InstructionContext {
259 transaction_context: self,
260 index_in_trace,
261 nesting_level: instruction.nesting_level as usize,
262 program_account_index_in_tx: instruction.program_account_index_in_tx as IndexOfAccount,
263 instruction_accounts,
264 dedup_map,
265 instruction_data,
266 index_of_caller_instruction: instruction.index_of_caller_instruction as usize,
267 })
268 }
269
270 pub fn get_instruction_context_at_nesting_level(
272 &self,
273 nesting_level: usize,
274 ) -> Result<InstructionContext<'_, '_>, InstructionError> {
275 let index_in_trace = *self
276 .instruction_stack
277 .get(nesting_level)
278 .ok_or(InstructionError::CallDepth)?;
279 let instruction_context = self.get_instruction_context_at_index_in_trace(index_in_trace)?;
280 debug_assert_eq!(instruction_context.nesting_level, nesting_level);
281 Ok(instruction_context)
282 }
283
284 pub fn get_instruction_stack_capacity(&self) -> usize {
286 self.instruction_stack_capacity
287 }
288
289 pub fn get_instruction_stack_height(&self) -> usize {
292 self.instruction_stack.len()
293 }
294
295 pub fn get_current_instruction_index(&self) -> Result<usize, InstructionError> {
297 self.instruction_stack
298 .last()
299 .copied()
300 .ok_or(InstructionError::CallDepth)
301 }
302
303 pub fn get_current_instruction_context(
305 &self,
306 ) -> Result<InstructionContext<'_, '_>, InstructionError> {
307 let index_in_trace = self.get_current_instruction_index()?;
308 self.get_instruction_context_at_index_in_trace(index_in_trace)
309 }
310
311 pub fn get_next_instruction_context(
315 &self,
316 ) -> Result<InstructionContext<'_, '_>, InstructionError> {
317 let index_in_trace = if self.instruction_stack.is_empty() {
318 self.next_top_level_instruction_index
319 } else {
320 self.instruction_trace
321 .len()
322 .checked_sub(1)
323 .ok_or(InstructionError::CallDepth)?
324 };
325 self.get_instruction_context_at_index_in_trace(index_in_trace)
326 }
327
328 pub fn configure_instruction_at_index(
330 &mut self,
331 instruction_index: usize,
332 program_index: IndexOfAccount,
333 instruction_accounts: Vec<InstructionAccount>,
334 deduplication_map: Vec<u8>,
335 instruction_data: Cow<'ix_data, [u8]>,
336 caller_index: Option<u16>,
337 ) -> Result<(), InstructionError> {
338 debug_assert_eq!(
339 deduplication_map.len(),
340 usize::from(self.get_number_of_accounts()).min(MAX_ACCOUNTS_PER_TRANSACTION)
341 );
342
343 let instruction = self
344 .instruction_trace
345 .get_mut(instruction_index)
346 .ok_or(InstructionError::MaxInstructionTraceLengthExceeded)?;
347
348 if let Some(caller_index) = caller_index {
350 instruction.index_of_caller_instruction = caller_index;
351 self.transaction_frame.configure_cpi();
352 }
353
354 instruction.program_account_index_in_tx = program_index;
355 instruction.configure_vm_slices(
356 instruction_index as u64,
357 instruction_accounts.len(),
358 instruction_data.len() as u64,
359 );
360 self.deduplication_maps
361 .push(deduplication_map.into_boxed_slice());
362 self.instruction_accounts
363 .push(instruction_accounts.into_boxed_slice());
364 self.instruction_data.push(instruction_data);
365 Ok(())
366 }
367
368 fn deduplicate_accounts(
369 num_accounts: usize,
370 instruction_accounts: &mut [InstructionAccount],
371 ) -> Vec<u8> {
372 let mut dedup_map = vec![u8::MAX; num_accounts];
373 for idx_in_ix in 0..instruction_accounts.len() {
374 let first_occurrence_in_ix = dedup_map
375 .get_mut(
376 instruction_accounts
377 .get(idx_in_ix)
378 .unwrap()
379 .index_in_transaction as usize,
380 )
381 .unwrap();
382 if *first_occurrence_in_ix == u8::MAX {
383 *first_occurrence_in_ix = idx_in_ix as u8;
384 } else {
385 let [this_account, other_account] = instruction_accounts
388 .get_disjoint_mut([idx_in_ix, *first_occurrence_in_ix as usize])
389 .expect("Accounts indices must exist in array");
390
391 other_account.set_is_signer(other_account.is_signer() || this_account.is_signer());
392 other_account
393 .set_is_writable(other_account.is_writable() || this_account.is_writable());
394 }
395 }
396
397 Self::replicate_account_flags(instruction_accounts, &dedup_map);
398 dedup_map
399 }
400
401 pub fn replicate_account_flags(
405 instruction_accounts: &mut [InstructionAccount],
406 dedup_map: &[u8],
407 ) {
408 for current_index in 0..instruction_accounts.len() {
409 let instruction_account = instruction_accounts.get(current_index).unwrap();
410 let other_account_index = *dedup_map
411 .get(instruction_account.index_in_transaction as usize)
412 .expect("Deduplication map must contain this account")
413 as usize;
414
415 let Ok([current_account, reference_account]) =
416 instruction_accounts.get_disjoint_mut([current_index, other_account_index])
417 else {
418 continue;
419 };
420
421 current_account
424 .set_is_signer(current_account.is_signer() || reference_account.is_signer());
425 current_account
426 .set_is_writable(current_account.is_writable() || reference_account.is_writable());
427 }
428 }
429
430 pub fn configure_top_level_instruction_for_tests(
432 &mut self,
433 program_index: IndexOfAccount,
434 mut instruction_accounts: Vec<InstructionAccount>,
435 instruction_data: Vec<u8>,
436 ) -> Result<(), InstructionError> {
437 debug_assert!(instruction_accounts.len() <= u8::MAX as usize);
438 let dedup_map = Self::deduplicate_accounts(
439 self.get_number_of_accounts() as usize,
440 &mut instruction_accounts,
441 );
442
443 self.configure_instruction_at_index(
444 self.next_top_level_instruction_index,
445 program_index,
446 instruction_accounts,
447 dedup_map,
448 Cow::Owned(instruction_data),
449 None,
450 )?;
451 Ok(())
452 }
453
454 pub fn configure_next_cpi_for_tests(
456 &mut self,
457 program_index: IndexOfAccount,
458 mut instruction_accounts: Vec<InstructionAccount>,
459 instruction_data: Vec<u8>,
460 ) -> Result<(), InstructionError> {
461 debug_assert!(instruction_accounts.len() <= u8::MAX as usize);
462 let dedup_map = Self::deduplicate_accounts(
463 self.get_number_of_accounts() as usize,
464 &mut instruction_accounts,
465 );
466 let caller_index = self.get_current_instruction_index()?;
467 let cpi_index = self.get_instruction_trace_length();
468 self.configure_instruction_at_index(
469 cpi_index,
470 program_index,
471 instruction_accounts,
472 dedup_map,
473 Cow::Owned(instruction_data),
474 Some(caller_index as u16),
475 )?;
476 Ok(())
477 }
478
479 pub fn push(&mut self, detect_overflow_early: bool) -> Result<(), InstructionError> {
481 let nesting_level = self.get_instruction_stack_height();
482 if !self.instruction_stack.is_empty() && self.accounts.get_lamports_delta() != 0 {
483 return Err(InstructionError::UnbalancedInstruction);
484 }
485 {
486 let instruction = self
487 .instruction_trace
488 .last_mut()
489 .ok_or(InstructionError::CallDepth)?;
490 instruction.nesting_level = nesting_level as u16;
491 }
492
493 if !detect_overflow_early
494 && self.number_of_called_instructions_in_trace() >= self.instruction_trace_capacity
495 {
496 return Err(InstructionError::MaxInstructionTraceLengthExceeded);
497 }
498
499 if detect_overflow_early
500 && ((self.transaction_frame.total_number_of_instructions_in_trace as usize
501 > self.instruction_trace_capacity)
502 || (self.number_of_called_instructions_in_trace()
503 >= self.instruction_trace_capacity))
504 {
505 return Err(InstructionError::MaxInstructionTraceLengthExceeded);
514 }
515
516 let (index_in_trace, current_top_level_instruction) = if self.instruction_stack.is_empty() {
517 let index = self.next_top_level_instruction_index;
518 self.next_top_level_instruction_index =
519 self.next_top_level_instruction_index.saturating_add(1);
520 (index, index)
521 } else {
522 let index = self.get_instruction_trace_length();
523 self.transaction_frame.number_of_cpis_in_trace = self
524 .transaction_frame
525 .number_of_cpis_in_trace
526 .saturating_add(1);
527 self.instruction_trace.push(InstructionFrame::default());
528 (
529 index,
530 self.next_top_level_instruction_index.saturating_sub(1),
531 )
532 };
533
534 if nesting_level >= self.instruction_stack_capacity {
535 return Err(InstructionError::CallDepth);
536 }
537 self.transaction_frame.current_executing_instruction = index_in_trace as u16;
538 self.instruction_stack.push(index_in_trace);
539 if let Some(index_in_transaction) = self.find_index_of_account(&instructions::id()) {
540 let mut mut_account_ref = self.accounts.try_borrow_mut(index_in_transaction)?;
541 if mut_account_ref.owner() != &solana_sdk_ids::sysvar::id() {
542 return Err(InstructionError::InvalidAccountOwner);
543 }
544 instructions::store_current_index_checked(
545 mut_account_ref.data_as_mut_slice(),
546 current_top_level_instruction as u16,
547 )?;
548 }
549 Ok(())
550 }
551
552 pub fn pop(&mut self) -> Result<(), InstructionError> {
554 if self.instruction_stack.is_empty() {
555 return Err(InstructionError::CallDepth);
556 }
557 let detected_an_unbalanced_instruction =
559 self.get_current_instruction_context()
560 .and_then(|instruction_context| {
561 self.accounts
563 .try_borrow_mut(
564 instruction_context.get_index_of_program_account_in_transaction()?,
565 )
566 .map_err(|err| {
567 if err == InstructionError::AccountBorrowFailed {
568 InstructionError::AccountBorrowOutstanding
569 } else {
570 err
571 }
572 })?;
573 Ok(self.accounts.get_lamports_delta() != 0)
574 });
575 self.instruction_stack.pop();
577 if let Some(instr_idx) = self.instruction_stack.last() {
578 self.transaction_frame.current_executing_instruction = *instr_idx as u16;
579 }
580 if detected_an_unbalanced_instruction? {
581 Err(InstructionError::UnbalancedInstruction)
582 } else {
583 Ok(())
584 }
585 }
586
587 pub fn get_return_data(&self) -> (&Pubkey, &[u8]) {
589 (
590 &self.transaction_frame.return_data_pubkey,
591 &self.return_data_bytes,
592 )
593 }
594
595 pub fn set_return_data(
597 &mut self,
598 program_id: Pubkey,
599 data: Vec<u8>,
600 ) -> Result<(), InstructionError> {
601 self.transaction_frame.return_data_pubkey = program_id;
602 self.transaction_frame
603 .return_data_scratchpad
604 .set_len(data.len() as u64);
605 self.return_data_bytes = data;
606 Ok(())
607 }
608
609 pub fn access_violation_handler(
611 &self,
612 virtual_address_space_adjustments: bool,
613 account_data_direct_mapping: bool,
614 ) -> AccessViolationHandler {
615 let accounts = Rc::clone(&self.accounts);
616 Box::new(
617 move |region: &mut MemoryRegion,
618 address_space_reserved_for_account: u64,
619 access_type: AccessType,
620 vm_addr: u64,
621 len: u64| {
622 if access_type == AccessType::Load {
623 return;
624 }
625 let Some(index_in_transaction) = region.access_violation_handler_payload else {
626 return;
628 };
629 let region_vm_addr_start = region.vm_addr_range().start;
630 let requested_length = vm_addr
631 .saturating_add(len)
632 .saturating_sub(region_vm_addr_start)
633 as usize;
634 if requested_length > address_space_reserved_for_account as usize {
635 return;
637 }
638
639 let Ok(mut account) = accounts.try_borrow_mut(index_in_transaction) else {
643 debug_assert!(false);
644 return;
645 };
646 if accounts.touch(index_in_transaction).is_err() {
647 debug_assert!(false);
648 return;
649 }
650
651 let remaining_allowed_growth = MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION
652 .saturating_sub(accounts.resize_delta())
653 .max(0) as usize;
654
655 if requested_length > region.len() {
656 let old_len = account.data().len();
660 let new_len = (address_space_reserved_for_account as usize)
661 .min(MAX_ACCOUNT_DATA_LEN as usize)
662 .min(old_len.saturating_add(remaining_allowed_growth));
663 debug_assert!(accounts.can_data_be_resized(old_len, new_len).is_ok());
665 if accounts
666 .update_accounts_resize_delta(old_len, new_len)
667 .is_err()
668 {
669 return;
670 }
671
672 account.resize(new_len, 0);
673 let data_ptr = region.host_buffer().ptr() as *mut u8;
674 let new_buffer = std::ptr::slice_from_raw_parts_mut(data_ptr, new_len);
675 unsafe {
676 region.redirect(new_buffer);
705 }
706 }
707
708 if virtual_address_space_adjustments && account_data_direct_mapping {
710 unsafe {
711 region.redirect(account.raw_mut_data_slice());
713 }
714 }
715 },
716 )
717 }
718
719 pub fn take_instruction_trace(&mut self) -> InstructionTrace<'_> {
721 self.instruction_trace.pop();
724 (
725 std::mem::take(&mut self.instruction_trace),
726 std::mem::take(&mut self.instruction_accounts),
727 std::mem::take(&mut self.instruction_data),
728 )
729 }
730
731 pub fn number_of_called_instructions_in_trace(&self) -> usize {
737 self.next_top_level_instruction_index
738 .saturating_add(self.transaction_frame.number_of_cpis_in_trace as usize)
739 }
740
741 pub fn next_top_level_instruction_index(&self) -> usize {
743 self.next_top_level_instruction_index
744 }
745
746 pub fn number_of_cpis_in_trace(&self) -> usize {
748 self.transaction_frame.number_of_cpis_in_trace as usize
749 }
750}
751
752#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
754#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
755#[cfg_attr(feature = "wincode", derive(wincode::SchemaRead, wincode::SchemaWrite))]
756#[derive(Clone, Debug, Default, PartialEq, Eq)]
757pub struct TransactionReturnData {
758 pub program_id: Pubkey,
759 pub data: Vec<u8>,
760}
761
762#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
764pub struct ExecutionRecord {
765 pub accounts: Vec<KeyedAccountSharedData>,
766 pub return_data: TransactionReturnData,
767 pub touched_flags: Box<[bool]>,
769 pub accounts_resize_delta: i64,
770}
771
772#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
774impl From<TransactionContext<'_>> for ExecutionRecord {
775 fn from(context: TransactionContext) -> Self {
776 let (accounts, touched_flags, resize_delta) = Rc::try_unwrap(context.accounts)
777 .expect("transaction_context.accounts has unexpected outstanding refs")
778 .take();
779
780 let touched_flags: Box<[bool]> = Vec::from(touched_flags)
786 .into_iter()
787 .map(|flag| flag.into_inner())
788 .collect();
789
790 let return_data = TransactionReturnData {
791 program_id: context.transaction_frame.return_data_pubkey,
792 data: context.return_data_bytes,
793 };
794
795 Self {
796 accounts,
797 return_data,
798 touched_flags,
799 accounts_resize_delta: Cell::into_inner(resize_delta),
800 }
801 }
802}
803
804#[cfg(all(test, not(target_arch = "sbf"), not(target_arch = "bpf")))]
805mod tests {
806 use {super::*, crate::MAX_INSTRUCTION_TRACE_LENGTH, test_case::test_case};
807
808 #[test]
809 fn test_instructions_sysvar_store_index_checked() {
810 let build_transaction_context = |account: AccountSharedData| {
811 TransactionContext::new(
812 vec![
813 (Pubkey::new_unique(), AccountSharedData::default()),
814 (instructions::id(), account),
815 ],
816 Rent::default(),
817 2,
818 2,
819 1,
820 )
821 };
822
823 let correct_space = 2;
824 let rent_exempt_lamports = Rent::default().minimum_balance(correct_space);
825
826 let account =
828 AccountSharedData::new(rent_exempt_lamports, correct_space, &Pubkey::new_unique());
829 assert_eq!(
830 build_transaction_context(account).push(true),
831 Err(InstructionError::InvalidAccountOwner),
832 );
833
834 let account =
836 AccountSharedData::new(rent_exempt_lamports, 0, &solana_sdk_ids::sysvar::id());
837 assert_eq!(
838 build_transaction_context(account).push(true),
839 Err(InstructionError::AccountDataTooSmall),
840 );
841
842 let account = AccountSharedData::new(
844 rent_exempt_lamports,
845 correct_space,
846 &solana_sdk_ids::sysvar::id(),
847 );
848 assert_eq!(build_transaction_context(account).push(true), Ok(()),);
849 }
850
851 #[test]
852 fn test_invalid_native_loader_index() {
853 let mut transaction_context = TransactionContext::new(
854 vec![(
855 Pubkey::new_unique(),
856 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
857 )],
858 Rent::default(),
859 20,
860 20,
861 1,
862 );
863
864 transaction_context
865 .configure_top_level_instruction_for_tests(
866 u16::MAX,
867 vec![InstructionAccount::new(0, false, false)],
868 vec![],
869 )
870 .unwrap();
871 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
872
873 let result = instruction_context.get_index_of_program_account_in_transaction();
874 assert_eq!(result, Err(InstructionError::MissingAccount));
875
876 let result = instruction_context.get_program_key();
877 assert_eq!(result, Err(InstructionError::MissingAccount));
878
879 let result = instruction_context.get_program_owner();
880 assert_eq!(result.err(), Some(InstructionError::MissingAccount));
881 }
882
883 #[test]
884 fn test_instruction_shared_items() {
885 let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 11];
886 let mut transaction_context =
887 TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 3);
888
889 let instruction_accounts_1 = vec![
890 InstructionAccount::new(0, false, true),
891 InstructionAccount::new(3, true, false),
892 ];
893 transaction_context
894 .configure_top_level_instruction_for_tests(
895 1,
896 instruction_accounts_1.clone(),
897 vec![1, 2, 3, 4],
898 )
899 .unwrap();
900 transaction_context.push(true).unwrap();
901
902 let instruction_accounts_2 = vec![
903 InstructionAccount::new(0, false, true),
904 InstructionAccount::new(3, true, false),
905 InstructionAccount::new(5, false, false),
906 ];
907 transaction_context
908 .configure_top_level_instruction_for_tests(
909 1,
910 instruction_accounts_2.clone(),
911 vec![5, 6, 7, 8, 9],
912 )
913 .unwrap();
914 transaction_context.push(true).unwrap();
915
916 let instruction_accounts_3 = vec![
917 InstructionAccount::new(0, false, true),
918 InstructionAccount::new(3, true, false),
919 InstructionAccount::new(5, false, false),
920 InstructionAccount::new(3, false, false),
921 InstructionAccount::new(10, false, false),
922 ];
923 transaction_context
924 .configure_top_level_instruction_for_tests(
925 1,
926 instruction_accounts_3.clone(),
927 vec![10, 11],
928 )
929 .unwrap();
930 transaction_context.push(true).unwrap();
931
932 let first_ix_context = transaction_context
933 .get_instruction_context_at_index_in_trace(0)
934 .unwrap();
935 assert_eq!(
936 instruction_accounts_1.as_slice(),
937 first_ix_context.instruction_accounts
938 );
939 assert_eq!(
940 *first_ix_context.instruction_data,
941 **transaction_context.instruction_data.first().unwrap()
942 );
943 for (idx_in_ix, acc) in instruction_accounts_1.iter().enumerate() {
944 assert_eq!(
945 *first_ix_context
946 .dedup_map
947 .get(acc.index_in_transaction as usize)
948 .unwrap(),
949 idx_in_ix as u8
950 );
951 }
952
953 let second_ix_context = transaction_context
954 .get_instruction_context_at_index_in_trace(1)
955 .unwrap();
956 assert_eq!(
957 instruction_accounts_2.as_slice(),
958 second_ix_context.instruction_accounts
959 );
960 assert_eq!(
961 *second_ix_context.instruction_data,
962 **transaction_context.instruction_data.get(1).unwrap()
963 );
964 for (idx_in_ix, acc) in instruction_accounts_2.iter().enumerate() {
965 assert_eq!(
966 *second_ix_context
967 .dedup_map
968 .get(acc.index_in_transaction as usize)
969 .unwrap(),
970 idx_in_ix as u8
971 );
972 }
973
974 let third_ix_context = transaction_context
975 .get_instruction_context_at_index_in_trace(2)
976 .unwrap();
977 assert_eq!(
978 vec![
979 InstructionAccount::new(0, false, true),
980 InstructionAccount::new(3, true, false),
981 InstructionAccount::new(5, false, false),
982 InstructionAccount::new(3, true, false),
983 InstructionAccount::new(10, false, false),
984 ]
985 .as_slice(),
986 third_ix_context.instruction_accounts
987 );
988
989 assert_eq!(
990 *third_ix_context.instruction_data,
991 **transaction_context.instruction_data.get(2).unwrap()
992 );
993 for (idx_in_ix, acc) in instruction_accounts_3.iter().enumerate() {
994 if idx_in_ix == 3 {
995 assert_eq!(
996 *third_ix_context
997 .dedup_map
998 .get(acc.index_in_transaction as usize)
999 .unwrap(),
1000 1
1001 );
1002 } else {
1003 assert_eq!(
1004 *third_ix_context
1005 .dedup_map
1006 .get(acc.index_in_transaction as usize)
1007 .unwrap(),
1008 idx_in_ix as u8
1009 );
1010 }
1011 }
1012 }
1013
1014 #[test]
1015 fn test_number_of_instructions() {
1016 let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
1017 let mut transaction_context =
1018 TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 2);
1019 assert_eq!(
1020 transaction_context
1021 .transaction_frame
1022 .number_of_cpis_in_trace,
1023 0
1024 );
1025
1026 transaction_context
1028 .configure_instruction_at_index(
1029 0,
1030 0,
1031 vec![InstructionAccount::new(1, false, false)],
1032 vec![0; 3],
1033 Vec::new().into(),
1034 None,
1035 )
1036 .unwrap();
1037
1038 transaction_context
1040 .configure_instruction_at_index(
1041 1,
1042 0,
1043 vec![InstructionAccount::new(1, false, false)],
1044 vec![0; 3],
1045 Vec::new().into(),
1046 None,
1047 )
1048 .unwrap();
1049
1050 transaction_context.push(true).unwrap();
1052 assert_eq!(
1053 transaction_context
1054 .transaction_frame
1055 .current_executing_instruction,
1056 0
1057 );
1058 assert_eq!(
1059 transaction_context.number_of_called_instructions_in_trace(),
1060 1
1061 );
1062
1063 assert_eq!(
1064 transaction_context
1065 .transaction_frame
1066 .total_number_of_instructions_in_trace,
1067 2
1068 );
1069
1070 assert_eq!(
1071 transaction_context
1072 .transaction_frame
1073 .number_of_cpis_in_trace,
1074 0
1075 );
1076
1077 assert_eq!(
1078 transaction_context
1079 .transaction_frame
1080 .cpi_data_scratchpad
1081 .ptr(),
1082 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(2))
1083 );
1084 assert_eq!(
1085 transaction_context
1086 .transaction_frame
1087 .cpi_data_scratchpad
1088 .len(),
1089 0,
1090 );
1091 assert_eq!(
1092 transaction_context
1093 .transaction_frame
1094 .cpi_accounts_scratchpad
1095 .ptr(),
1096 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1097 .saturating_add(GUEST_REGION_SIZE.saturating_mul(2))
1098 );
1099 assert_eq!(
1100 transaction_context
1101 .transaction_frame
1102 .cpi_data_scratchpad
1103 .len(),
1104 0,
1105 );
1106
1107 assert_eq!(
1108 transaction_context.number_of_called_instructions_in_trace(),
1109 1
1110 );
1111
1112 transaction_context
1114 .configure_next_cpi_for_tests(
1115 0,
1116 vec![InstructionAccount::new(2, false, true)],
1117 Vec::new(),
1118 )
1119 .unwrap();
1120
1121 transaction_context.push(true).unwrap();
1122 assert_eq!(
1123 transaction_context
1124 .transaction_frame
1125 .current_executing_instruction,
1126 2
1127 );
1128
1129 assert_eq!(
1130 transaction_context
1131 .transaction_frame
1132 .total_number_of_instructions_in_trace,
1133 3
1134 );
1135 assert_eq!(
1136 transaction_context
1137 .transaction_frame
1138 .number_of_cpis_in_trace,
1139 1
1140 );
1141 assert_eq!(
1142 transaction_context.number_of_called_instructions_in_trace(),
1143 2
1144 );
1145
1146 assert_eq!(
1147 transaction_context
1148 .transaction_frame
1149 .cpi_data_scratchpad
1150 .ptr(),
1151 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(3))
1152 );
1153 assert_eq!(
1154 transaction_context
1155 .transaction_frame
1156 .cpi_accounts_scratchpad
1157 .ptr(),
1158 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1159 .saturating_add(GUEST_REGION_SIZE.saturating_mul(3))
1160 );
1161
1162 transaction_context
1164 .configure_next_cpi_for_tests(
1165 0,
1166 vec![InstructionAccount::new(2, false, true)],
1167 Vec::new(),
1168 )
1169 .unwrap();
1170
1171 transaction_context.push(true).unwrap();
1172 assert_eq!(
1173 transaction_context
1174 .transaction_frame
1175 .current_executing_instruction,
1176 3
1177 );
1178
1179 assert_eq!(
1180 transaction_context
1181 .transaction_frame
1182 .total_number_of_instructions_in_trace,
1183 4
1184 );
1185
1186 assert_eq!(
1187 transaction_context
1188 .transaction_frame
1189 .cpi_data_scratchpad
1190 .ptr(),
1191 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(4))
1192 );
1193 assert_eq!(
1194 transaction_context
1195 .transaction_frame
1196 .cpi_accounts_scratchpad
1197 .ptr(),
1198 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1199 .saturating_add(GUEST_REGION_SIZE.saturating_mul(4))
1200 );
1201
1202 assert_eq!(
1203 transaction_context
1204 .transaction_frame
1205 .number_of_cpis_in_trace,
1206 2
1207 );
1208
1209 assert_eq!(
1210 transaction_context.number_of_called_instructions_in_trace(),
1211 3
1212 );
1213 transaction_context.pop().unwrap();
1215 assert_eq!(
1216 transaction_context.number_of_called_instructions_in_trace(),
1217 3
1218 );
1219
1220 assert_eq!(
1221 transaction_context
1222 .transaction_frame
1223 .total_number_of_instructions_in_trace,
1224 4
1225 );
1226 assert_eq!(
1227 transaction_context
1228 .transaction_frame
1229 .number_of_cpis_in_trace,
1230 2,
1231 );
1232 assert_eq!(
1233 transaction_context
1234 .transaction_frame
1235 .current_executing_instruction,
1236 2
1237 );
1238
1239 transaction_context
1241 .configure_next_cpi_for_tests(
1242 0,
1243 vec![InstructionAccount::new(2, false, true)],
1244 Vec::new(),
1245 )
1246 .unwrap();
1247
1248 transaction_context.push(true).unwrap();
1249 assert_eq!(
1250 transaction_context
1251 .transaction_frame
1252 .current_executing_instruction,
1253 4
1254 );
1255
1256 assert_eq!(
1257 transaction_context
1258 .transaction_frame
1259 .total_number_of_instructions_in_trace,
1260 5
1261 );
1262
1263 assert_eq!(
1264 transaction_context
1265 .transaction_frame
1266 .cpi_data_scratchpad
1267 .ptr(),
1268 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1269 );
1270 assert_eq!(
1271 transaction_context
1272 .transaction_frame
1273 .cpi_accounts_scratchpad
1274 .ptr(),
1275 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1276 .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1277 );
1278
1279 assert_eq!(
1280 transaction_context
1281 .transaction_frame
1282 .number_of_cpis_in_trace,
1283 3
1284 );
1285 assert_eq!(
1286 transaction_context.number_of_called_instructions_in_trace(),
1287 4
1288 );
1289
1290 transaction_context.pop().unwrap();
1292
1293 assert_eq!(
1294 transaction_context
1295 .transaction_frame
1296 .current_executing_instruction,
1297 2
1298 );
1299
1300 assert_eq!(
1301 transaction_context
1302 .transaction_frame
1303 .total_number_of_instructions_in_trace,
1304 5
1305 );
1306
1307 assert_eq!(
1308 transaction_context
1309 .transaction_frame
1310 .cpi_data_scratchpad
1311 .ptr(),
1312 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1313 );
1314 assert_eq!(
1315 transaction_context
1316 .transaction_frame
1317 .cpi_accounts_scratchpad
1318 .ptr(),
1319 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1320 .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1321 );
1322
1323 assert_eq!(
1324 transaction_context
1325 .transaction_frame
1326 .number_of_cpis_in_trace,
1327 3
1328 );
1329
1330 transaction_context.pop().unwrap();
1332 assert_eq!(
1333 transaction_context.number_of_called_instructions_in_trace(),
1334 4
1335 );
1336
1337 assert_eq!(
1338 transaction_context
1339 .transaction_frame
1340 .current_executing_instruction,
1341 0
1342 );
1343
1344 assert_eq!(
1345 transaction_context
1346 .transaction_frame
1347 .total_number_of_instructions_in_trace,
1348 5
1349 );
1350
1351 assert_eq!(
1352 transaction_context
1353 .transaction_frame
1354 .cpi_data_scratchpad
1355 .ptr(),
1356 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1357 );
1358 assert_eq!(
1359 transaction_context
1360 .transaction_frame
1361 .cpi_accounts_scratchpad
1362 .ptr(),
1363 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1364 .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1365 );
1366
1367 assert_eq!(
1368 transaction_context
1369 .transaction_frame
1370 .number_of_cpis_in_trace,
1371 3,
1372 );
1373
1374 transaction_context.pop().unwrap();
1376 transaction_context.push(true).unwrap();
1377 assert_eq!(
1378 transaction_context
1379 .transaction_frame
1380 .current_executing_instruction,
1381 1,
1382 );
1383 assert_eq!(
1384 transaction_context
1385 .transaction_frame
1386 .number_of_cpis_in_trace,
1387 3
1388 );
1389
1390 transaction_context
1392 .configure_next_cpi_for_tests(
1393 0,
1394 vec![InstructionAccount::new(2, false, true)],
1395 Vec::new(),
1396 )
1397 .unwrap();
1398
1399 transaction_context.push(true).unwrap();
1400
1401 assert_eq!(
1402 transaction_context
1403 .transaction_frame
1404 .current_executing_instruction,
1405 5,
1406 );
1407
1408 assert_eq!(
1409 transaction_context
1410 .transaction_frame
1411 .total_number_of_instructions_in_trace,
1412 6
1413 );
1414
1415 assert_eq!(
1416 transaction_context
1417 .transaction_frame
1418 .cpi_data_scratchpad
1419 .ptr(),
1420 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(6))
1421 );
1422 assert_eq!(
1423 transaction_context
1424 .transaction_frame
1425 .cpi_accounts_scratchpad
1426 .ptr(),
1427 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1428 .saturating_add(GUEST_REGION_SIZE.saturating_mul(6))
1429 );
1430
1431 assert_eq!(
1432 transaction_context
1433 .transaction_frame
1434 .number_of_cpis_in_trace,
1435 4
1436 );
1437 assert_eq!(
1438 transaction_context.number_of_called_instructions_in_trace(),
1439 6
1440 );
1441
1442 transaction_context.pop().unwrap();
1444 assert_eq!(
1445 transaction_context
1446 .transaction_frame
1447 .number_of_cpis_in_trace,
1448 4
1449 );
1450 assert_eq!(
1451 transaction_context
1452 .transaction_frame
1453 .current_executing_instruction,
1454 1,
1455 );
1456
1457 transaction_context.pop().unwrap();
1458 }
1459
1460 #[test]
1461 fn test_get_current_instruction_index() {
1462 let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
1463 let mut transaction_context =
1464 TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 2);
1465
1466 transaction_context
1468 .configure_instruction_at_index(
1469 0,
1470 1,
1471 vec![
1472 InstructionAccount::new(0, false, false),
1473 InstructionAccount::new(1, false, false),
1474 ],
1475 vec![u8::MAX; 3],
1476 Cow::Owned(Vec::new()),
1477 None,
1478 )
1479 .unwrap();
1480
1481 transaction_context
1483 .configure_instruction_at_index(
1484 1,
1485 1,
1486 vec![
1487 InstructionAccount::new(0, false, false),
1488 InstructionAccount::new(1, false, true),
1489 ],
1490 vec![u8::MAX; 3],
1491 Cow::Owned(Vec::new()),
1492 None,
1493 )
1494 .unwrap();
1495
1496 transaction_context.push(true).unwrap();
1497 assert_eq!(
1498 transaction_context.get_current_instruction_index().unwrap(),
1499 0
1500 );
1501
1502 transaction_context.pop().unwrap();
1503
1504 transaction_context.push(true).unwrap();
1505 assert_eq!(
1506 transaction_context.get_current_instruction_index().unwrap(),
1507 1
1508 );
1509
1510 transaction_context
1512 .configure_next_cpi_for_tests(
1513 1,
1514 vec![
1515 InstructionAccount::new(0, false, true),
1516 InstructionAccount::new(1, false, false),
1517 ],
1518 Vec::new(),
1519 )
1520 .unwrap();
1521 transaction_context.push(true).unwrap();
1522 assert_eq!(
1523 transaction_context.get_current_instruction_index().unwrap(),
1524 2
1525 );
1526
1527 transaction_context
1529 .configure_next_cpi_for_tests(
1530 1,
1531 vec![
1532 InstructionAccount::new(0, false, true),
1533 InstructionAccount::new(1, false, false),
1534 ],
1535 Vec::new(),
1536 )
1537 .unwrap();
1538 transaction_context.push(true).unwrap();
1539 assert_eq!(
1540 transaction_context.get_current_instruction_index().unwrap(),
1541 3
1542 );
1543
1544 transaction_context.pop().unwrap();
1546 assert_eq!(
1547 transaction_context.get_current_instruction_index().unwrap(),
1548 2
1549 );
1550
1551 transaction_context.pop().unwrap();
1553 assert_eq!(
1554 transaction_context.get_current_instruction_index().unwrap(),
1555 1
1556 );
1557 }
1558
1559 #[test]
1560 fn test_deduplicate_accounts() {
1561 let mut instruction_accounts = vec![
1562 InstructionAccount::new(0, false, true), InstructionAccount::new(1, true, false), InstructionAccount::new(0, false, false), InstructionAccount::new(2, false, true), InstructionAccount::new(1, true, false), ];
1568
1569 let dedup_map = TransactionContext::deduplicate_accounts(
1570 instruction_accounts.len(),
1571 &mut instruction_accounts,
1572 );
1573
1574 assert_eq!(
1576 *dedup_map.first().unwrap(),
1577 0,
1578 "account must be a duplicate of itself"
1579 );
1580 assert_eq!(
1581 *dedup_map.get(1).unwrap(),
1582 1,
1583 "account must be a duplicate of itself"
1584 );
1585 assert_eq!(
1586 *dedup_map.get(2).unwrap(),
1587 3,
1588 "account must be a duplicate of itself"
1589 );
1590
1591 let acc = instruction_accounts.first().unwrap();
1593 assert_eq!(acc.index_in_transaction, 0);
1594 assert!(
1595 !acc.is_signer(),
1596 "Must not be a signer because account 1 is not signer"
1597 );
1598 assert!(
1599 acc.is_writable(),
1600 "Must be writable because account 0 is writable"
1601 );
1602
1603 let acc = instruction_accounts.get(1).unwrap();
1604 assert_eq!(acc.index_in_transaction, 1);
1605 assert!(
1606 acc.is_signer(),
1607 "Must be signer because account 1 is signer"
1608 );
1609 assert!(
1610 !acc.is_writable(),
1611 "Must not be writable because account 1 is not writable"
1612 );
1613
1614 let acc = instruction_accounts.get(2).unwrap();
1615 assert_eq!(acc.index_in_transaction, 0);
1616 assert!(!acc.is_signer(), "Should be merged from account 1");
1617 assert!(acc.is_writable(), "Should be merged from account 0");
1618
1619 let acc = instruction_accounts.get(3).unwrap();
1620 assert_eq!(acc.index_in_transaction, 2);
1621 assert!(!acc.is_signer());
1622 assert!(acc.is_writable());
1623
1624 let acc = instruction_accounts.get(4).unwrap();
1625 assert_eq!(acc.index_in_transaction, 1);
1626 assert!(
1627 acc.is_signer(),
1628 "Must be signer because account 1 is signer"
1629 );
1630 assert!(
1631 !acc.is_writable(),
1632 "Must not be writable because account 1 is not writable"
1633 );
1634
1635 assert_eq!(
1637 *dedup_map.first().unwrap(),
1638 0,
1639 "account must be a duplicate of itself"
1640 );
1641 assert_eq!(
1642 *dedup_map.get(1).unwrap(),
1643 1,
1644 "account must be a duplicate of itself"
1645 );
1646 assert_eq!(
1647 *dedup_map.get(2).unwrap(),
1648 3,
1649 "account must be a duplicate of itself"
1650 );
1651 }
1652
1653 #[test]
1654 fn test_deduplicate_accounts_no_duplicates() {
1655 let mut instruction_accounts = vec![
1656 InstructionAccount::new(0, false, true),
1657 InstructionAccount::new(1, true, false),
1658 InstructionAccount::new(2, false, false),
1659 ];
1660
1661 let dedup_map = TransactionContext::deduplicate_accounts(
1662 instruction_accounts.len(),
1663 &mut instruction_accounts,
1664 );
1665
1666 assert_eq!(
1668 *dedup_map.first().unwrap(),
1669 0,
1670 "account must be a duplicate of itself"
1671 );
1672 assert_eq!(
1673 *dedup_map.get(1).unwrap(),
1674 1,
1675 "account must be a duplicate of itself"
1676 );
1677 assert_eq!(
1678 *dedup_map.get(2).unwrap(),
1679 2,
1680 "account must be a duplicate of itself"
1681 );
1682
1683 let acc = instruction_accounts.first().unwrap();
1685 assert_eq!(acc.index_in_transaction, 0);
1686 assert!(!acc.is_signer());
1687 assert!(acc.is_writable());
1688
1689 let acc = instruction_accounts.get(1).unwrap();
1690 assert_eq!(acc.index_in_transaction, 1);
1691 assert!(acc.is_signer());
1692 assert!(!acc.is_writable());
1693
1694 let acc = instruction_accounts.get(2).unwrap();
1695 assert_eq!(acc.index_in_transaction, 2);
1696 assert!(!acc.is_signer());
1697 assert!(!acc.is_writable());
1698 }
1699
1700 #[test]
1701 fn test_deduplicate_accounts_all_duplicates() {
1702 let mut instruction_accounts = vec![
1703 InstructionAccount::new(0, false, true),
1704 InstructionAccount::new(0, true, false),
1705 InstructionAccount::new(0, false, false),
1706 ];
1707
1708 let dedup_map = TransactionContext::deduplicate_accounts(
1709 instruction_accounts.len(),
1710 &mut instruction_accounts,
1711 );
1712
1713 assert_eq!(
1715 *dedup_map.first().unwrap(),
1716 0,
1717 "account must be a duplicate of itself"
1718 );
1719 for idx in dedup_map.iter().skip(1) {
1720 assert_eq!(*idx, u8::MAX);
1721 }
1722
1723 let acc = instruction_accounts.first().unwrap();
1725 assert_eq!(acc.index_in_transaction, 0);
1726 assert!(
1727 acc.is_signer(),
1728 "Should be signer because of second account"
1729 );
1730 assert!(
1731 acc.is_writable(),
1732 "Should be writable because of first account"
1733 );
1734
1735 let acc = instruction_accounts.get(1).unwrap();
1737 assert_eq!(acc.index_in_transaction, 0);
1738 assert!(acc.is_signer());
1739 assert!(acc.is_writable());
1740
1741 let acc = instruction_accounts.get(2).unwrap();
1742 assert_eq!(acc.index_in_transaction, 0);
1743 assert!(acc.is_signer());
1744 assert!(acc.is_writable());
1745 }
1746
1747 #[test_case(false; "simd_582_disabled")]
1748 #[test_case(true; "simd_582_enabled")]
1749 fn test_max_instruction_trace_len_exceeded(fail_early: bool) {
1750 const IX_TRACE_CAPACITY: usize = 5;
1751 let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
1752
1753 for number_of_top_level_instructions in 4..=5 {
1754 let mut transaction_context = TransactionContext::new(
1756 transaction_accounts.clone(),
1757 Rent::default(),
1758 6,
1759 IX_TRACE_CAPACITY,
1760 number_of_top_level_instructions,
1761 );
1762
1763 for idx_to_configure in 0..number_of_top_level_instructions {
1765 transaction_context
1766 .configure_instruction_at_index(
1767 idx_to_configure,
1768 0,
1769 vec![InstructionAccount::new(1, false, false)],
1770 vec![0; 3],
1771 Vec::new().into(),
1772 None,
1773 )
1774 .unwrap();
1775 }
1776
1777 let mut pushed_ixs: usize = 0;
1779 let mut ix_in_trace = number_of_top_level_instructions;
1780 for ix_num in 0..number_of_top_level_instructions {
1781 let result = transaction_context.push(fail_early);
1782 pushed_ixs = pushed_ixs.saturating_add(1);
1783 if !fail_early && pushed_ixs > IX_TRACE_CAPACITY {
1784 assert_eq!(pushed_ixs, IX_TRACE_CAPACITY.saturating_add(1));
1785 assert_eq!(
1786 transaction_context.number_of_called_instructions_in_trace(),
1787 IX_TRACE_CAPACITY
1788 );
1789 assert_eq!(
1790 result,
1791 Err(InstructionError::MaxInstructionTraceLengthExceeded)
1792 );
1793 break;
1794 } else {
1795 assert!(result.is_ok());
1796 }
1797
1798 if ix_num < 2 {
1800 transaction_context
1801 .configure_next_cpi_for_tests(
1802 0,
1803 vec![InstructionAccount::new(2, false, false)],
1804 Vec::new(),
1805 )
1806 .unwrap();
1807
1808 ix_in_trace = ix_in_trace.saturating_add(1);
1809 pushed_ixs = pushed_ixs.saturating_add(1);
1810 let result = transaction_context.push(fail_early);
1811 if fail_early && ix_in_trace > IX_TRACE_CAPACITY {
1812 assert_eq!(ix_in_trace, IX_TRACE_CAPACITY.saturating_add(1));
1813 assert_eq!(
1814 transaction_context
1815 .transaction_frame
1816 .total_number_of_instructions_in_trace
1817 as usize,
1818 IX_TRACE_CAPACITY.saturating_add(1)
1819 );
1820 assert_eq!(
1821 result,
1822 Err(InstructionError::MaxInstructionTraceLengthExceeded)
1823 );
1824 break;
1825 } else {
1826 assert!(result.is_ok());
1827 }
1828 transaction_context.pop().unwrap();
1829 }
1830 transaction_context.pop().unwrap();
1831 }
1832 }
1833 }
1834
1835 #[test_case(false; "simd_582_disabled")]
1836 #[test_case(true; "simd_582_enabled")]
1837 fn test_max_instruction_trace_len_exceeded_64_ixs(fail_early: bool) {
1838 let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
1839
1840 let mut transaction_context = TransactionContext::new(
1842 transaction_accounts.clone(),
1843 Rent::default(),
1844 6,
1845 MAX_INSTRUCTION_TRACE_LENGTH,
1846 MAX_INSTRUCTION_TRACE_LENGTH,
1847 );
1848
1849 for idx_to_configure in 0..MAX_INSTRUCTION_TRACE_LENGTH {
1851 transaction_context
1852 .configure_instruction_at_index(
1853 idx_to_configure,
1854 0,
1855 vec![InstructionAccount::new(1, false, false)],
1856 vec![0; 3],
1857 Vec::new().into(),
1858 None,
1859 )
1860 .unwrap();
1861 }
1862
1863 transaction_context.push(fail_early).unwrap();
1865 transaction_context
1867 .configure_next_cpi_for_tests(
1868 0,
1869 vec![InstructionAccount::new(2, false, false)],
1870 Vec::new(),
1871 )
1872 .unwrap();
1873
1874 let result = transaction_context.push(fail_early);
1875 if fail_early {
1876 assert_eq!(
1877 result,
1878 Err(InstructionError::MaxInstructionTraceLengthExceeded)
1879 );
1880 } else {
1881 assert!(result.is_ok());
1882 }
1883 }
1884}