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