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) -> 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 self.number_of_called_instructions_in_trace() >= self.instruction_trace_capacity {
494 return Err(InstructionError::MaxInstructionTraceLengthExceeded);
495 }
496
497 let (index_in_trace, current_top_level_instruction) = if self.instruction_stack.is_empty() {
498 let index = self.next_top_level_instruction_index;
499 self.next_top_level_instruction_index =
500 self.next_top_level_instruction_index.saturating_add(1);
501 (index, index)
502 } else {
503 let index = self.get_instruction_trace_length();
504 self.transaction_frame.number_of_cpis_in_trace = self
505 .transaction_frame
506 .number_of_cpis_in_trace
507 .saturating_add(1);
508 self.instruction_trace.push(InstructionFrame::default());
509 (
510 index,
511 self.next_top_level_instruction_index.saturating_sub(1),
512 )
513 };
514
515 if nesting_level >= self.instruction_stack_capacity {
516 return Err(InstructionError::CallDepth);
517 }
518 self.transaction_frame.current_executing_instruction = index_in_trace as u16;
519 self.instruction_stack.push(index_in_trace);
520 if let Some(index_in_transaction) = self.find_index_of_account(&instructions::id()) {
521 let mut mut_account_ref = self.accounts.try_borrow_mut(index_in_transaction)?;
522 if mut_account_ref.owner() != &solana_sdk_ids::sysvar::id() {
523 return Err(InstructionError::InvalidAccountOwner);
524 }
525 instructions::store_current_index_checked(
526 mut_account_ref.data_as_mut_slice(),
527 current_top_level_instruction as u16,
528 )?;
529 }
530 Ok(())
531 }
532
533 pub fn pop(&mut self) -> Result<(), InstructionError> {
535 if self.instruction_stack.is_empty() {
536 return Err(InstructionError::CallDepth);
537 }
538 let detected_an_unbalanced_instruction =
540 self.get_current_instruction_context()
541 .and_then(|instruction_context| {
542 self.accounts
544 .try_borrow_mut(
545 instruction_context.get_index_of_program_account_in_transaction()?,
546 )
547 .map_err(|err| {
548 if err == InstructionError::AccountBorrowFailed {
549 InstructionError::AccountBorrowOutstanding
550 } else {
551 err
552 }
553 })?;
554 Ok(self.accounts.get_lamports_delta() != 0)
555 });
556 self.instruction_stack.pop();
558 if let Some(instr_idx) = self.instruction_stack.last() {
559 self.transaction_frame.current_executing_instruction = *instr_idx as u16;
560 }
561 if detected_an_unbalanced_instruction? {
562 Err(InstructionError::UnbalancedInstruction)
563 } else {
564 Ok(())
565 }
566 }
567
568 pub fn get_return_data(&self) -> (&Pubkey, &[u8]) {
570 (
571 &self.transaction_frame.return_data_pubkey,
572 &self.return_data_bytes,
573 )
574 }
575
576 pub fn set_return_data(
578 &mut self,
579 program_id: Pubkey,
580 data: Vec<u8>,
581 ) -> Result<(), InstructionError> {
582 self.transaction_frame.return_data_pubkey = program_id;
583 self.transaction_frame
584 .return_data_scratchpad
585 .set_len(data.len() as u64);
586 self.return_data_bytes = data;
587 Ok(())
588 }
589
590 pub fn access_violation_handler(
592 &self,
593 virtual_address_space_adjustments: bool,
594 account_data_direct_mapping: bool,
595 ) -> AccessViolationHandler {
596 let accounts = Rc::clone(&self.accounts);
597 Box::new(
598 move |region: &mut MemoryRegion,
599 address_space_reserved_for_account: u64,
600 access_type: AccessType,
601 vm_addr: u64,
602 len: u64| {
603 if access_type == AccessType::Load {
604 return;
605 }
606 let Some(index_in_transaction) = region.access_violation_handler_payload else {
607 return;
609 };
610 let region_vm_addr_start = region.vm_addr_range().start;
611 let requested_length = vm_addr
612 .saturating_add(len)
613 .saturating_sub(region_vm_addr_start)
614 as usize;
615 if requested_length > address_space_reserved_for_account as usize {
616 return;
618 }
619
620 let Ok(mut account) = accounts.try_borrow_mut(index_in_transaction) else {
624 debug_assert!(false);
625 return;
626 };
627 if accounts.touch(index_in_transaction).is_err() {
628 debug_assert!(false);
629 return;
630 }
631
632 let remaining_allowed_growth = MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION
633 .saturating_sub(accounts.resize_delta())
634 .max(0) as usize;
635
636 if requested_length > region.len() {
637 let old_len = account.data().len();
641 let new_len = (address_space_reserved_for_account as usize)
642 .min(MAX_ACCOUNT_DATA_LEN as usize)
643 .min(old_len.saturating_add(remaining_allowed_growth));
644 debug_assert!(accounts.can_data_be_resized(old_len, new_len).is_ok());
646 if accounts
647 .update_accounts_resize_delta(old_len, new_len)
648 .is_err()
649 {
650 return;
651 }
652
653 account.resize(new_len, 0);
654 let data_ptr = region.host_buffer().ptr() as *mut u8;
655 let new_buffer = std::ptr::slice_from_raw_parts_mut(data_ptr, new_len);
656 unsafe {
657 region.redirect(new_buffer);
686 }
687 }
688
689 if virtual_address_space_adjustments && account_data_direct_mapping {
691 unsafe {
692 region.redirect(account.raw_mut_data_slice());
694 }
695 }
696 },
697 )
698 }
699
700 pub fn take_instruction_trace(&mut self) -> InstructionTrace<'_> {
702 self.instruction_trace.pop();
705 (
706 std::mem::take(&mut self.instruction_trace),
707 std::mem::take(&mut self.instruction_accounts),
708 std::mem::take(&mut self.instruction_data),
709 )
710 }
711
712 pub fn number_of_called_instructions_in_trace(&self) -> usize {
718 self.next_top_level_instruction_index
719 .saturating_add(self.transaction_frame.number_of_cpis_in_trace as usize)
720 }
721
722 pub fn next_top_level_instruction_index(&self) -> usize {
724 self.next_top_level_instruction_index
725 }
726
727 pub fn number_of_cpis_in_trace(&self) -> usize {
729 self.transaction_frame.number_of_cpis_in_trace as usize
730 }
731}
732
733#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
735#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
736#[cfg_attr(feature = "wincode", derive(wincode::SchemaRead, wincode::SchemaWrite))]
737#[derive(Clone, Debug, Default, PartialEq, Eq)]
738pub struct TransactionReturnData {
739 pub program_id: Pubkey,
740 pub data: Vec<u8>,
741}
742
743#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
745pub struct ExecutionRecord {
746 pub accounts: Vec<KeyedAccountSharedData>,
747 pub return_data: TransactionReturnData,
748 pub touched_flags: Box<[bool]>,
750 pub accounts_resize_delta: i64,
751}
752
753#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
755impl From<TransactionContext<'_>> for ExecutionRecord {
756 fn from(context: TransactionContext) -> Self {
757 let (accounts, touched_flags, resize_delta) = Rc::try_unwrap(context.accounts)
758 .expect("transaction_context.accounts has unexpected outstanding refs")
759 .take();
760
761 let touched_flags: Box<[bool]> = Vec::from(touched_flags)
767 .into_iter()
768 .map(|flag| flag.into_inner())
769 .collect();
770
771 let return_data = TransactionReturnData {
772 program_id: context.transaction_frame.return_data_pubkey,
773 data: context.return_data_bytes,
774 };
775
776 Self {
777 accounts,
778 return_data,
779 touched_flags,
780 accounts_resize_delta: Cell::into_inner(resize_delta),
781 }
782 }
783}
784
785#[cfg(all(test, not(target_arch = "sbf"), not(target_arch = "bpf")))]
786mod tests {
787 use super::*;
788
789 #[test]
790 fn test_instructions_sysvar_store_index_checked() {
791 let build_transaction_context = |account: AccountSharedData| {
792 TransactionContext::new(
793 vec![
794 (Pubkey::new_unique(), AccountSharedData::default()),
795 (instructions::id(), account),
796 ],
797 Rent::default(),
798 2,
799 2,
800 1,
801 )
802 };
803
804 let correct_space = 2;
805 let rent_exempt_lamports = Rent::default().minimum_balance(correct_space);
806
807 let account =
809 AccountSharedData::new(rent_exempt_lamports, correct_space, &Pubkey::new_unique());
810 assert_eq!(
811 build_transaction_context(account).push(),
812 Err(InstructionError::InvalidAccountOwner),
813 );
814
815 let account =
817 AccountSharedData::new(rent_exempt_lamports, 0, &solana_sdk_ids::sysvar::id());
818 assert_eq!(
819 build_transaction_context(account).push(),
820 Err(InstructionError::AccountDataTooSmall),
821 );
822
823 let account = AccountSharedData::new(
825 rent_exempt_lamports,
826 correct_space,
827 &solana_sdk_ids::sysvar::id(),
828 );
829 assert_eq!(build_transaction_context(account).push(), Ok(()),);
830 }
831
832 #[test]
833 fn test_invalid_native_loader_index() {
834 let mut transaction_context = TransactionContext::new(
835 vec![(
836 Pubkey::new_unique(),
837 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
838 )],
839 Rent::default(),
840 20,
841 20,
842 1,
843 );
844
845 transaction_context
846 .configure_top_level_instruction_for_tests(
847 u16::MAX,
848 vec![InstructionAccount::new(0, false, false)],
849 vec![],
850 )
851 .unwrap();
852 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
853
854 let result = instruction_context.get_index_of_program_account_in_transaction();
855 assert_eq!(result, Err(InstructionError::MissingAccount));
856
857 let result = instruction_context.get_program_key();
858 assert_eq!(result, Err(InstructionError::MissingAccount));
859
860 let result = instruction_context.get_program_owner();
861 assert_eq!(result.err(), Some(InstructionError::MissingAccount));
862 }
863
864 #[test]
865 fn test_instruction_shared_items() {
866 let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 11];
867 let mut transaction_context =
868 TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 3);
869
870 let instruction_accounts_1 = vec![
871 InstructionAccount::new(0, false, true),
872 InstructionAccount::new(3, true, false),
873 ];
874 transaction_context
875 .configure_top_level_instruction_for_tests(
876 1,
877 instruction_accounts_1.clone(),
878 vec![1, 2, 3, 4],
879 )
880 .unwrap();
881 transaction_context.push().unwrap();
882
883 let instruction_accounts_2 = vec![
884 InstructionAccount::new(0, false, true),
885 InstructionAccount::new(3, true, false),
886 InstructionAccount::new(5, false, false),
887 ];
888 transaction_context
889 .configure_top_level_instruction_for_tests(
890 1,
891 instruction_accounts_2.clone(),
892 vec![5, 6, 7, 8, 9],
893 )
894 .unwrap();
895 transaction_context.push().unwrap();
896
897 let instruction_accounts_3 = vec![
898 InstructionAccount::new(0, false, true),
899 InstructionAccount::new(3, true, false),
900 InstructionAccount::new(5, false, false),
901 InstructionAccount::new(3, false, false),
902 InstructionAccount::new(10, false, false),
903 ];
904 transaction_context
905 .configure_top_level_instruction_for_tests(
906 1,
907 instruction_accounts_3.clone(),
908 vec![10, 11],
909 )
910 .unwrap();
911 transaction_context.push().unwrap();
912
913 let first_ix_context = transaction_context
914 .get_instruction_context_at_index_in_trace(0)
915 .unwrap();
916 assert_eq!(
917 instruction_accounts_1.as_slice(),
918 first_ix_context.instruction_accounts
919 );
920 assert_eq!(
921 *first_ix_context.instruction_data,
922 **transaction_context.instruction_data.first().unwrap()
923 );
924 for (idx_in_ix, acc) in instruction_accounts_1.iter().enumerate() {
925 assert_eq!(
926 *first_ix_context
927 .dedup_map
928 .get(acc.index_in_transaction as usize)
929 .unwrap(),
930 idx_in_ix as u8
931 );
932 }
933
934 let second_ix_context = transaction_context
935 .get_instruction_context_at_index_in_trace(1)
936 .unwrap();
937 assert_eq!(
938 instruction_accounts_2.as_slice(),
939 second_ix_context.instruction_accounts
940 );
941 assert_eq!(
942 *second_ix_context.instruction_data,
943 **transaction_context.instruction_data.get(1).unwrap()
944 );
945 for (idx_in_ix, acc) in instruction_accounts_2.iter().enumerate() {
946 assert_eq!(
947 *second_ix_context
948 .dedup_map
949 .get(acc.index_in_transaction as usize)
950 .unwrap(),
951 idx_in_ix as u8
952 );
953 }
954
955 let third_ix_context = transaction_context
956 .get_instruction_context_at_index_in_trace(2)
957 .unwrap();
958 assert_eq!(
959 vec![
960 InstructionAccount::new(0, false, true),
961 InstructionAccount::new(3, true, false),
962 InstructionAccount::new(5, false, false),
963 InstructionAccount::new(3, true, false),
964 InstructionAccount::new(10, false, false),
965 ]
966 .as_slice(),
967 third_ix_context.instruction_accounts
968 );
969
970 assert_eq!(
971 *third_ix_context.instruction_data,
972 **transaction_context.instruction_data.get(2).unwrap()
973 );
974 for (idx_in_ix, acc) in instruction_accounts_3.iter().enumerate() {
975 if idx_in_ix == 3 {
976 assert_eq!(
977 *third_ix_context
978 .dedup_map
979 .get(acc.index_in_transaction as usize)
980 .unwrap(),
981 1
982 );
983 } else {
984 assert_eq!(
985 *third_ix_context
986 .dedup_map
987 .get(acc.index_in_transaction as usize)
988 .unwrap(),
989 idx_in_ix as u8
990 );
991 }
992 }
993 }
994
995 #[test]
996 fn test_number_of_instructions() {
997 let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
998 let mut transaction_context =
999 TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 2);
1000 assert_eq!(
1001 transaction_context
1002 .transaction_frame
1003 .number_of_cpis_in_trace,
1004 0
1005 );
1006
1007 transaction_context
1009 .configure_instruction_at_index(
1010 0,
1011 0,
1012 vec![InstructionAccount::new(1, false, false)],
1013 vec![0; 3],
1014 Vec::new().into(),
1015 None,
1016 )
1017 .unwrap();
1018
1019 transaction_context
1021 .configure_instruction_at_index(
1022 1,
1023 0,
1024 vec![InstructionAccount::new(1, false, false)],
1025 vec![0; 3],
1026 Vec::new().into(),
1027 None,
1028 )
1029 .unwrap();
1030
1031 transaction_context.push().unwrap();
1033 assert_eq!(
1034 transaction_context
1035 .transaction_frame
1036 .current_executing_instruction,
1037 0
1038 );
1039 assert_eq!(
1040 transaction_context.number_of_called_instructions_in_trace(),
1041 1
1042 );
1043
1044 assert_eq!(
1045 transaction_context
1046 .transaction_frame
1047 .total_number_of_instructions_in_trace,
1048 2
1049 );
1050
1051 assert_eq!(
1052 transaction_context
1053 .transaction_frame
1054 .number_of_cpis_in_trace,
1055 0
1056 );
1057
1058 assert_eq!(
1059 transaction_context
1060 .transaction_frame
1061 .cpi_data_scratchpad
1062 .ptr(),
1063 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(2))
1064 );
1065 assert_eq!(
1066 transaction_context
1067 .transaction_frame
1068 .cpi_data_scratchpad
1069 .len(),
1070 0,
1071 );
1072 assert_eq!(
1073 transaction_context
1074 .transaction_frame
1075 .cpi_accounts_scratchpad
1076 .ptr(),
1077 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1078 .saturating_add(GUEST_REGION_SIZE.saturating_mul(2))
1079 );
1080 assert_eq!(
1081 transaction_context
1082 .transaction_frame
1083 .cpi_data_scratchpad
1084 .len(),
1085 0,
1086 );
1087
1088 assert_eq!(
1089 transaction_context.number_of_called_instructions_in_trace(),
1090 1
1091 );
1092
1093 transaction_context
1095 .configure_next_cpi_for_tests(
1096 0,
1097 vec![InstructionAccount::new(2, false, true)],
1098 Vec::new(),
1099 )
1100 .unwrap();
1101
1102 transaction_context.push().unwrap();
1103 assert_eq!(
1104 transaction_context
1105 .transaction_frame
1106 .current_executing_instruction,
1107 2
1108 );
1109
1110 assert_eq!(
1111 transaction_context
1112 .transaction_frame
1113 .total_number_of_instructions_in_trace,
1114 3
1115 );
1116 assert_eq!(
1117 transaction_context
1118 .transaction_frame
1119 .number_of_cpis_in_trace,
1120 1
1121 );
1122 assert_eq!(
1123 transaction_context.number_of_called_instructions_in_trace(),
1124 2
1125 );
1126
1127 assert_eq!(
1128 transaction_context
1129 .transaction_frame
1130 .cpi_data_scratchpad
1131 .ptr(),
1132 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(3))
1133 );
1134 assert_eq!(
1135 transaction_context
1136 .transaction_frame
1137 .cpi_accounts_scratchpad
1138 .ptr(),
1139 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1140 .saturating_add(GUEST_REGION_SIZE.saturating_mul(3))
1141 );
1142
1143 transaction_context
1145 .configure_next_cpi_for_tests(
1146 0,
1147 vec![InstructionAccount::new(2, false, true)],
1148 Vec::new(),
1149 )
1150 .unwrap();
1151
1152 transaction_context.push().unwrap();
1153 assert_eq!(
1154 transaction_context
1155 .transaction_frame
1156 .current_executing_instruction,
1157 3
1158 );
1159
1160 assert_eq!(
1161 transaction_context
1162 .transaction_frame
1163 .total_number_of_instructions_in_trace,
1164 4
1165 );
1166
1167 assert_eq!(
1168 transaction_context
1169 .transaction_frame
1170 .cpi_data_scratchpad
1171 .ptr(),
1172 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(4))
1173 );
1174 assert_eq!(
1175 transaction_context
1176 .transaction_frame
1177 .cpi_accounts_scratchpad
1178 .ptr(),
1179 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1180 .saturating_add(GUEST_REGION_SIZE.saturating_mul(4))
1181 );
1182
1183 assert_eq!(
1184 transaction_context
1185 .transaction_frame
1186 .number_of_cpis_in_trace,
1187 2
1188 );
1189
1190 assert_eq!(
1191 transaction_context.number_of_called_instructions_in_trace(),
1192 3
1193 );
1194 transaction_context.pop().unwrap();
1196 assert_eq!(
1197 transaction_context.number_of_called_instructions_in_trace(),
1198 3
1199 );
1200
1201 assert_eq!(
1202 transaction_context
1203 .transaction_frame
1204 .total_number_of_instructions_in_trace,
1205 4
1206 );
1207 assert_eq!(
1208 transaction_context
1209 .transaction_frame
1210 .number_of_cpis_in_trace,
1211 2,
1212 );
1213 assert_eq!(
1214 transaction_context
1215 .transaction_frame
1216 .current_executing_instruction,
1217 2
1218 );
1219
1220 transaction_context
1222 .configure_next_cpi_for_tests(
1223 0,
1224 vec![InstructionAccount::new(2, false, true)],
1225 Vec::new(),
1226 )
1227 .unwrap();
1228
1229 transaction_context.push().unwrap();
1230 assert_eq!(
1231 transaction_context
1232 .transaction_frame
1233 .current_executing_instruction,
1234 4
1235 );
1236
1237 assert_eq!(
1238 transaction_context
1239 .transaction_frame
1240 .total_number_of_instructions_in_trace,
1241 5
1242 );
1243
1244 assert_eq!(
1245 transaction_context
1246 .transaction_frame
1247 .cpi_data_scratchpad
1248 .ptr(),
1249 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1250 );
1251 assert_eq!(
1252 transaction_context
1253 .transaction_frame
1254 .cpi_accounts_scratchpad
1255 .ptr(),
1256 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1257 .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1258 );
1259
1260 assert_eq!(
1261 transaction_context
1262 .transaction_frame
1263 .number_of_cpis_in_trace,
1264 3
1265 );
1266 assert_eq!(
1267 transaction_context.number_of_called_instructions_in_trace(),
1268 4
1269 );
1270
1271 transaction_context.pop().unwrap();
1273
1274 assert_eq!(
1275 transaction_context
1276 .transaction_frame
1277 .current_executing_instruction,
1278 2
1279 );
1280
1281 assert_eq!(
1282 transaction_context
1283 .transaction_frame
1284 .total_number_of_instructions_in_trace,
1285 5
1286 );
1287
1288 assert_eq!(
1289 transaction_context
1290 .transaction_frame
1291 .cpi_data_scratchpad
1292 .ptr(),
1293 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1294 );
1295 assert_eq!(
1296 transaction_context
1297 .transaction_frame
1298 .cpi_accounts_scratchpad
1299 .ptr(),
1300 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1301 .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1302 );
1303
1304 assert_eq!(
1305 transaction_context
1306 .transaction_frame
1307 .number_of_cpis_in_trace,
1308 3
1309 );
1310
1311 transaction_context.pop().unwrap();
1313 assert_eq!(
1314 transaction_context.number_of_called_instructions_in_trace(),
1315 4
1316 );
1317
1318 assert_eq!(
1319 transaction_context
1320 .transaction_frame
1321 .current_executing_instruction,
1322 0
1323 );
1324
1325 assert_eq!(
1326 transaction_context
1327 .transaction_frame
1328 .total_number_of_instructions_in_trace,
1329 5
1330 );
1331
1332 assert_eq!(
1333 transaction_context
1334 .transaction_frame
1335 .cpi_data_scratchpad
1336 .ptr(),
1337 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1338 );
1339 assert_eq!(
1340 transaction_context
1341 .transaction_frame
1342 .cpi_accounts_scratchpad
1343 .ptr(),
1344 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1345 .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1346 );
1347
1348 assert_eq!(
1349 transaction_context
1350 .transaction_frame
1351 .number_of_cpis_in_trace,
1352 3,
1353 );
1354
1355 transaction_context.pop().unwrap();
1357 transaction_context.push().unwrap();
1358 assert_eq!(
1359 transaction_context
1360 .transaction_frame
1361 .current_executing_instruction,
1362 1,
1363 );
1364 assert_eq!(
1365 transaction_context
1366 .transaction_frame
1367 .number_of_cpis_in_trace,
1368 3
1369 );
1370
1371 transaction_context
1373 .configure_next_cpi_for_tests(
1374 0,
1375 vec![InstructionAccount::new(2, false, true)],
1376 Vec::new(),
1377 )
1378 .unwrap();
1379
1380 transaction_context.push().unwrap();
1381
1382 assert_eq!(
1383 transaction_context
1384 .transaction_frame
1385 .current_executing_instruction,
1386 5,
1387 );
1388
1389 assert_eq!(
1390 transaction_context
1391 .transaction_frame
1392 .total_number_of_instructions_in_trace,
1393 6
1394 );
1395
1396 assert_eq!(
1397 transaction_context
1398 .transaction_frame
1399 .cpi_data_scratchpad
1400 .ptr(),
1401 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(6))
1402 );
1403 assert_eq!(
1404 transaction_context
1405 .transaction_frame
1406 .cpi_accounts_scratchpad
1407 .ptr(),
1408 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1409 .saturating_add(GUEST_REGION_SIZE.saturating_mul(6))
1410 );
1411
1412 assert_eq!(
1413 transaction_context
1414 .transaction_frame
1415 .number_of_cpis_in_trace,
1416 4
1417 );
1418 assert_eq!(
1419 transaction_context.number_of_called_instructions_in_trace(),
1420 6
1421 );
1422
1423 transaction_context.pop().unwrap();
1425 assert_eq!(
1426 transaction_context
1427 .transaction_frame
1428 .number_of_cpis_in_trace,
1429 4
1430 );
1431 assert_eq!(
1432 transaction_context
1433 .transaction_frame
1434 .current_executing_instruction,
1435 1,
1436 );
1437
1438 transaction_context.pop().unwrap();
1439 }
1440
1441 #[test]
1442 fn test_get_current_instruction_index() {
1443 let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
1444 let mut transaction_context =
1445 TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 2);
1446
1447 transaction_context
1449 .configure_instruction_at_index(
1450 0,
1451 1,
1452 vec![
1453 InstructionAccount::new(0, false, false),
1454 InstructionAccount::new(1, false, false),
1455 ],
1456 vec![u8::MAX; 3],
1457 Cow::Owned(Vec::new()),
1458 None,
1459 )
1460 .unwrap();
1461
1462 transaction_context
1464 .configure_instruction_at_index(
1465 1,
1466 1,
1467 vec![
1468 InstructionAccount::new(0, false, false),
1469 InstructionAccount::new(1, false, true),
1470 ],
1471 vec![u8::MAX; 3],
1472 Cow::Owned(Vec::new()),
1473 None,
1474 )
1475 .unwrap();
1476
1477 transaction_context.push().unwrap();
1478 assert_eq!(
1479 transaction_context.get_current_instruction_index().unwrap(),
1480 0
1481 );
1482
1483 transaction_context.pop().unwrap();
1484
1485 transaction_context.push().unwrap();
1486 assert_eq!(
1487 transaction_context.get_current_instruction_index().unwrap(),
1488 1
1489 );
1490
1491 transaction_context
1493 .configure_next_cpi_for_tests(
1494 1,
1495 vec![
1496 InstructionAccount::new(0, false, true),
1497 InstructionAccount::new(1, false, false),
1498 ],
1499 Vec::new(),
1500 )
1501 .unwrap();
1502 transaction_context.push().unwrap();
1503 assert_eq!(
1504 transaction_context.get_current_instruction_index().unwrap(),
1505 2
1506 );
1507
1508 transaction_context
1510 .configure_next_cpi_for_tests(
1511 1,
1512 vec![
1513 InstructionAccount::new(0, false, true),
1514 InstructionAccount::new(1, false, false),
1515 ],
1516 Vec::new(),
1517 )
1518 .unwrap();
1519 transaction_context.push().unwrap();
1520 assert_eq!(
1521 transaction_context.get_current_instruction_index().unwrap(),
1522 3
1523 );
1524
1525 transaction_context.pop().unwrap();
1527 assert_eq!(
1528 transaction_context.get_current_instruction_index().unwrap(),
1529 2
1530 );
1531
1532 transaction_context.pop().unwrap();
1534 assert_eq!(
1535 transaction_context.get_current_instruction_index().unwrap(),
1536 1
1537 );
1538 }
1539
1540 #[test]
1541 fn test_deduplicate_accounts() {
1542 let mut instruction_accounts = vec![
1543 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), ];
1549
1550 let dedup_map = TransactionContext::deduplicate_accounts(
1551 instruction_accounts.len(),
1552 &mut instruction_accounts,
1553 );
1554
1555 assert_eq!(
1557 *dedup_map.first().unwrap(),
1558 0,
1559 "account must be a duplicate of itself"
1560 );
1561 assert_eq!(
1562 *dedup_map.get(1).unwrap(),
1563 1,
1564 "account must be a duplicate of itself"
1565 );
1566 assert_eq!(
1567 *dedup_map.get(2).unwrap(),
1568 3,
1569 "account must be a duplicate of itself"
1570 );
1571
1572 let acc = instruction_accounts.first().unwrap();
1574 assert_eq!(acc.index_in_transaction, 0);
1575 assert!(
1576 !acc.is_signer(),
1577 "Must not be a signer because account 1 is not signer"
1578 );
1579 assert!(
1580 acc.is_writable(),
1581 "Must be writable because account 0 is writable"
1582 );
1583
1584 let acc = instruction_accounts.get(1).unwrap();
1585 assert_eq!(acc.index_in_transaction, 1);
1586 assert!(
1587 acc.is_signer(),
1588 "Must be signer because account 1 is signer"
1589 );
1590 assert!(
1591 !acc.is_writable(),
1592 "Must not be writable because account 1 is not writable"
1593 );
1594
1595 let acc = instruction_accounts.get(2).unwrap();
1596 assert_eq!(acc.index_in_transaction, 0);
1597 assert!(!acc.is_signer(), "Should be merged from account 1");
1598 assert!(acc.is_writable(), "Should be merged from account 0");
1599
1600 let acc = instruction_accounts.get(3).unwrap();
1601 assert_eq!(acc.index_in_transaction, 2);
1602 assert!(!acc.is_signer());
1603 assert!(acc.is_writable());
1604
1605 let acc = instruction_accounts.get(4).unwrap();
1606 assert_eq!(acc.index_in_transaction, 1);
1607 assert!(
1608 acc.is_signer(),
1609 "Must be signer because account 1 is signer"
1610 );
1611 assert!(
1612 !acc.is_writable(),
1613 "Must not be writable because account 1 is not writable"
1614 );
1615
1616 assert_eq!(
1618 *dedup_map.first().unwrap(),
1619 0,
1620 "account must be a duplicate of itself"
1621 );
1622 assert_eq!(
1623 *dedup_map.get(1).unwrap(),
1624 1,
1625 "account must be a duplicate of itself"
1626 );
1627 assert_eq!(
1628 *dedup_map.get(2).unwrap(),
1629 3,
1630 "account must be a duplicate of itself"
1631 );
1632 }
1633
1634 #[test]
1635 fn test_deduplicate_accounts_no_duplicates() {
1636 let mut instruction_accounts = vec![
1637 InstructionAccount::new(0, false, true),
1638 InstructionAccount::new(1, true, false),
1639 InstructionAccount::new(2, false, false),
1640 ];
1641
1642 let dedup_map = TransactionContext::deduplicate_accounts(
1643 instruction_accounts.len(),
1644 &mut instruction_accounts,
1645 );
1646
1647 assert_eq!(
1649 *dedup_map.first().unwrap(),
1650 0,
1651 "account must be a duplicate of itself"
1652 );
1653 assert_eq!(
1654 *dedup_map.get(1).unwrap(),
1655 1,
1656 "account must be a duplicate of itself"
1657 );
1658 assert_eq!(
1659 *dedup_map.get(2).unwrap(),
1660 2,
1661 "account must be a duplicate of itself"
1662 );
1663
1664 let acc = instruction_accounts.first().unwrap();
1666 assert_eq!(acc.index_in_transaction, 0);
1667 assert!(!acc.is_signer());
1668 assert!(acc.is_writable());
1669
1670 let acc = instruction_accounts.get(1).unwrap();
1671 assert_eq!(acc.index_in_transaction, 1);
1672 assert!(acc.is_signer());
1673 assert!(!acc.is_writable());
1674
1675 let acc = instruction_accounts.get(2).unwrap();
1676 assert_eq!(acc.index_in_transaction, 2);
1677 assert!(!acc.is_signer());
1678 assert!(!acc.is_writable());
1679 }
1680
1681 #[test]
1682 fn test_deduplicate_accounts_all_duplicates() {
1683 let mut instruction_accounts = vec![
1684 InstructionAccount::new(0, false, true),
1685 InstructionAccount::new(0, true, false),
1686 InstructionAccount::new(0, false, false),
1687 ];
1688
1689 let dedup_map = TransactionContext::deduplicate_accounts(
1690 instruction_accounts.len(),
1691 &mut instruction_accounts,
1692 );
1693
1694 assert_eq!(
1696 *dedup_map.first().unwrap(),
1697 0,
1698 "account must be a duplicate of itself"
1699 );
1700 for idx in dedup_map.iter().skip(1) {
1701 assert_eq!(*idx, u8::MAX);
1702 }
1703
1704 let acc = instruction_accounts.first().unwrap();
1706 assert_eq!(acc.index_in_transaction, 0);
1707 assert!(
1708 acc.is_signer(),
1709 "Should be signer because of second account"
1710 );
1711 assert!(
1712 acc.is_writable(),
1713 "Should be writable because of first account"
1714 );
1715
1716 let acc = instruction_accounts.get(1).unwrap();
1718 assert_eq!(acc.index_in_transaction, 0);
1719 assert!(acc.is_signer());
1720 assert!(acc.is_writable());
1721
1722 let acc = instruction_accounts.get(2).unwrap();
1723 assert_eq!(acc.index_in_transaction, 0);
1724 assert!(acc.is_signer());
1725 assert!(acc.is_writable());
1726 }
1727}