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_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#[derive(Debug)]
102#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
103pub struct TransactionContext<'ix_data> {
104 pub(crate) accounts: Rc<TransactionAccounts>,
105 instruction_stack_capacity: usize,
106 instruction_trace_capacity: usize,
107 instruction_stack: Vec<usize>,
108 instruction_trace: Vec<InstructionFrame>,
109 transaction_frame: TransactionFrame,
110 return_data_bytes: Vec<u8>,
111 next_top_level_instruction_index: usize,
112 #[cfg(not(target_os = "solana"))]
115 pub compute_meter: ComputeMeter,
116 #[cfg(not(target_os = "solana"))]
117 pub(crate) rent: Rent,
118 deduplication_maps: Vec<Box<[u8]>>,
122 instruction_accounts: Vec<Box<[InstructionAccount]>>,
124 instruction_data: Vec<Cow<'ix_data, [u8]>>,
127}
128
129#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
130impl<'ix_data> TransactionContext<'ix_data> {
131 pub fn new_with_feature_flags(
133 transaction_accounts: Vec<KeyedAccountSharedData>,
134 rent: Rent,
135 instruction_stack_capacity: usize,
136 instruction_trace_capacity: usize,
137 number_of_top_level_instructions: usize,
138 compute_meter: ComputeMeter,
139 drop_on_bail_out: DropOnBailOut,
140 ) -> Self {
141 let transaction_frame = TransactionFrame {
142 return_data_pubkey: Pubkey::default(),
143 return_data_scratchpad: VmSlice::new(RETURN_DATA_SCRATCHPAD, 0),
144 cpi_data_scratchpad: VmSlice::new(
145 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(
146 GUEST_REGION_SIZE.saturating_mul(number_of_top_level_instructions as u64),
147 ),
148 0,
149 ),
150 cpi_accounts_scratchpad: VmSlice::new(
151 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS.saturating_add(
152 GUEST_REGION_SIZE.saturating_mul(number_of_top_level_instructions as u64),
153 ),
154 0,
155 ),
156 current_executing_instruction: 0,
157 total_number_of_instructions_in_trace: number_of_top_level_instructions as u16,
158 number_of_cpis_in_trace: 0,
159 number_of_transaction_accounts: transaction_accounts.len() as u16,
160 };
161
162 let mut instruction_trace =
164 Vec::with_capacity(instruction_trace_capacity.saturating_add(1));
165 instruction_trace.resize_with(
166 number_of_top_level_instructions.saturating_add(1),
167 InstructionFrame::default,
168 );
169
170 Self {
171 accounts: Rc::new(TransactionAccounts::new_with_feature_flags(
172 transaction_accounts,
173 drop_on_bail_out,
174 )),
175 instruction_stack_capacity,
176 instruction_trace_capacity,
177 instruction_stack: Vec::with_capacity(instruction_stack_capacity),
178 instruction_trace,
179 return_data_bytes: Vec::new(),
180 transaction_frame,
181 next_top_level_instruction_index: 0,
182 compute_meter,
183 rent,
184 instruction_accounts: Vec::with_capacity(instruction_trace_capacity),
185 deduplication_maps: Vec::with_capacity(instruction_trace_capacity),
186 instruction_data: Vec::with_capacity(instruction_trace_capacity),
187 }
188 }
189
190 #[cfg(feature = "dev-context-only-utils")]
192 pub fn new(
193 transaction_accounts: Vec<KeyedAccountSharedData>,
194 rent: Rent,
195 instruction_stack_capacity: usize,
196 instruction_trace_capacity: usize,
197 number_of_top_level_instructions: usize,
198 ) -> Self {
199 Self::new_with_feature_flags(
200 transaction_accounts,
201 rent,
202 instruction_stack_capacity,
203 instruction_trace_capacity,
204 number_of_top_level_instructions,
205 ComputeMeter::new(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64),
206 DropOnBailOut::Disabled,
207 )
208 }
209
210 pub fn deconstruct_without_keys(self) -> Result<Vec<AccountSharedData>, InstructionError> {
212 if !self.instruction_stack.is_empty() {
213 return Err(InstructionError::CallDepth);
214 }
215
216 let accounts = Rc::try_unwrap(self.accounts)
217 .expect("transaction_context.accounts has unexpected outstanding refs")
218 .deconstruct_into_account_shared_data();
219
220 Ok(accounts)
221 }
222
223 pub fn accounts(&self) -> &Rc<TransactionAccounts> {
224 &self.accounts
225 }
226
227 pub fn get_number_of_accounts(&self) -> IndexOfAccount {
229 self.accounts.len() as IndexOfAccount
230 }
231
232 pub fn get_key_of_account_at_index(
234 &self,
235 index_in_transaction: IndexOfAccount,
236 ) -> Result<&Pubkey, InstructionError> {
237 self.accounts
238 .account_key(index_in_transaction)
239 .ok_or(InstructionError::MissingAccount)
240 }
241
242 pub fn find_index_of_account(&self, pubkey: &Pubkey) -> Option<IndexOfAccount> {
244 self.accounts
245 .account_keys_iter()
246 .position(|key| key == pubkey)
247 .map(|index| index as IndexOfAccount)
248 }
249
250 pub fn get_instruction_trace_capacity(&self) -> usize {
252 self.instruction_trace_capacity
253 }
254
255 pub fn get_instruction_trace_length(&self) -> usize {
259 self.instruction_trace.len().saturating_sub(1)
260 }
261
262 pub fn get_instruction_context_at_index_in_trace(
264 &self,
265 index_in_trace: usize,
266 ) -> Result<InstructionContext<'_, '_>, InstructionError> {
267 let instruction = self
268 .instruction_trace
269 .get(index_in_trace)
270 .ok_or(InstructionError::CallDepth)?;
271
272 let instruction_accounts = self
275 .instruction_accounts
276 .get(index_in_trace)
277 .map(|item| item.as_ref())
278 .unwrap_or_default();
279 let dedup_map = self
280 .deduplication_maps
281 .get(index_in_trace)
282 .map(|item| item.as_ref())
283 .unwrap_or_default();
284 let instruction_data = self
285 .instruction_data
286 .get(index_in_trace)
287 .map(|item| item.as_ref())
288 .unwrap_or_default();
289 Ok(InstructionContext {
290 transaction_context: self,
291 index_in_trace,
292 nesting_level: instruction.nesting_level as usize,
293 program_account_index_in_tx: instruction.program_account_index_in_tx as IndexOfAccount,
294 instruction_accounts,
295 dedup_map,
296 instruction_data,
297 index_of_caller_instruction: instruction.index_of_caller_instruction as usize,
298 })
299 }
300
301 pub fn get_instruction_context_at_nesting_level(
303 &self,
304 nesting_level: usize,
305 ) -> Result<InstructionContext<'_, '_>, InstructionError> {
306 let index_in_trace = *self
307 .instruction_stack
308 .get(nesting_level)
309 .ok_or(InstructionError::CallDepth)?;
310 let instruction_context = self.get_instruction_context_at_index_in_trace(index_in_trace)?;
311 debug_assert_eq!(instruction_context.nesting_level, nesting_level);
312 Ok(instruction_context)
313 }
314
315 pub fn get_instruction_stack_capacity(&self) -> usize {
317 self.instruction_stack_capacity
318 }
319
320 pub fn get_instruction_stack_height(&self) -> usize {
323 self.instruction_stack.len()
324 }
325
326 pub fn get_current_instruction_index(&self) -> Result<usize, InstructionError> {
328 self.instruction_stack
329 .last()
330 .copied()
331 .ok_or(InstructionError::CallDepth)
332 }
333
334 pub fn get_current_instruction_context(
336 &self,
337 ) -> Result<InstructionContext<'_, '_>, InstructionError> {
338 let index_in_trace = self.get_current_instruction_index()?;
339 self.get_instruction_context_at_index_in_trace(index_in_trace)
340 }
341
342 pub fn get_next_instruction_context(
346 &self,
347 ) -> Result<InstructionContext<'_, '_>, InstructionError> {
348 let index_in_trace = if self.instruction_stack.is_empty() {
349 self.next_top_level_instruction_index
350 } else {
351 self.instruction_trace
352 .len()
353 .checked_sub(1)
354 .ok_or(InstructionError::CallDepth)?
355 };
356 self.get_instruction_context_at_index_in_trace(index_in_trace)
357 }
358
359 pub fn configure_instruction_at_index(
361 &mut self,
362 instruction_index: usize,
363 program_index: IndexOfAccount,
364 instruction_accounts: Vec<InstructionAccount>,
365 deduplication_map: Vec<u8>,
366 instruction_data: Cow<'ix_data, [u8]>,
367 caller_index: Option<u16>,
368 ) -> Result<(), InstructionError> {
369 debug_assert_eq!(
370 deduplication_map.len(),
371 usize::from(self.get_number_of_accounts()).min(MAX_ACCOUNTS_PER_TRANSACTION)
372 );
373
374 let instruction = self
375 .instruction_trace
376 .get_mut(instruction_index)
377 .ok_or(InstructionError::MaxInstructionTraceLengthExceeded)?;
378
379 if let Some(caller_index) = caller_index {
381 self.transaction_frame.total_number_of_instructions_in_trace = self
382 .transaction_frame
383 .total_number_of_instructions_in_trace
384 .saturating_add(1);
385 instruction.index_of_caller_instruction = caller_index;
386 let next_data_ptr = self
387 .transaction_frame
388 .cpi_data_scratchpad
389 .ptr()
390 .saturating_add(GUEST_REGION_SIZE);
391 self.transaction_frame.cpi_data_scratchpad = VmSlice::new(next_data_ptr, 0);
392 let next_accounts_ptr = self
393 .transaction_frame
394 .cpi_accounts_scratchpad
395 .ptr()
396 .saturating_add(GUEST_REGION_SIZE);
397 self.transaction_frame.cpi_accounts_scratchpad = VmSlice::new(next_accounts_ptr, 0);
398 }
399
400 instruction.program_account_index_in_tx = program_index;
401 instruction.configure_vm_slices(
402 instruction_index as u64,
403 instruction_accounts.len(),
404 instruction_data.len() as u64,
405 );
406 self.deduplication_maps
407 .push(deduplication_map.into_boxed_slice());
408 self.instruction_accounts
409 .push(instruction_accounts.into_boxed_slice());
410 self.instruction_data.push(instruction_data);
411 Ok(())
412 }
413
414 fn deduplicate_accounts_for_tests(
416 &self,
417 instruction_accounts: &[InstructionAccount],
418 ) -> Vec<u8> {
419 let mut dedup_map = vec![
420 u8::MAX;
421 usize::from(self.get_number_of_accounts())
422 .min(MAX_ACCOUNTS_PER_TRANSACTION)
423 ];
424 for (idx, account) in instruction_accounts.iter().enumerate() {
425 let index_in_instruction = dedup_map
426 .get_mut(account.index_in_transaction as usize)
427 .unwrap();
428 if *index_in_instruction == u8::MAX {
429 *index_in_instruction = idx as u8;
430 }
431 }
432 dedup_map
433 }
434
435 pub fn configure_top_level_instruction_for_tests(
437 &mut self,
438 program_index: IndexOfAccount,
439 instruction_accounts: Vec<InstructionAccount>,
440 instruction_data: Vec<u8>,
441 ) -> Result<(), InstructionError> {
442 debug_assert!(instruction_accounts.len() <= u8::MAX as usize);
443 let dedup_map = self.deduplicate_accounts_for_tests(&instruction_accounts);
444
445 self.configure_instruction_at_index(
446 self.next_top_level_instruction_index,
447 program_index,
448 instruction_accounts,
449 dedup_map,
450 Cow::Owned(instruction_data),
451 None,
452 )?;
453 Ok(())
454 }
455
456 pub fn configure_next_cpi_for_tests(
458 &mut self,
459 program_index: IndexOfAccount,
460 instruction_accounts: Vec<InstructionAccount>,
461 instruction_data: Vec<u8>,
462 ) -> Result<(), InstructionError> {
463 debug_assert!(instruction_accounts.len() <= u8::MAX as usize);
464 let dedup_map = self.deduplicate_accounts_for_tests(&instruction_accounts);
465 let caller_index = self.get_current_instruction_index()?;
466 let cpi_index = self.get_instruction_trace_length();
467 self.configure_instruction_at_index(
468 cpi_index,
469 program_index,
470 instruction_accounts,
471 dedup_map,
472 Cow::Owned(instruction_data),
473 Some(caller_index as u16),
474 )?;
475 Ok(())
476 }
477
478 pub fn push(&mut self) -> Result<(), InstructionError> {
480 let nesting_level = self.get_instruction_stack_height();
481 if !self.instruction_stack.is_empty() && self.accounts.get_lamports_delta() != 0 {
482 return Err(InstructionError::UnbalancedInstruction);
483 }
484 {
485 let instruction = self
486 .instruction_trace
487 .last_mut()
488 .ok_or(InstructionError::CallDepth)?;
489 instruction.nesting_level = nesting_level as u16;
490 }
491
492 if self.number_of_called_instructions_in_trace() >= self.instruction_trace_capacity {
493 return Err(InstructionError::MaxInstructionTraceLengthExceeded);
494 }
495
496 let (index_in_trace, current_top_level_instruction) = if self.instruction_stack.is_empty() {
497 let index = self.next_top_level_instruction_index;
498 self.next_top_level_instruction_index =
499 self.next_top_level_instruction_index.saturating_add(1);
500 (index, index)
501 } else {
502 let index = self.get_instruction_trace_length();
503 self.transaction_frame.number_of_cpis_in_trace = self
504 .transaction_frame
505 .number_of_cpis_in_trace
506 .saturating_add(1);
507 self.instruction_trace.push(InstructionFrame::default());
508 (
509 index,
510 self.next_top_level_instruction_index.saturating_sub(1),
511 )
512 };
513
514 if nesting_level >= self.instruction_stack_capacity {
515 return Err(InstructionError::CallDepth);
516 }
517 self.transaction_frame.current_executing_instruction = index_in_trace as u16;
518 self.instruction_stack.push(index_in_trace);
519 if let Some(index_in_transaction) = self.find_index_of_account(&instructions::id()) {
520 let mut mut_account_ref = self.accounts.try_borrow_mut(index_in_transaction)?;
521 if mut_account_ref.owner() != &solana_sdk_ids::sysvar::id() {
522 return Err(InstructionError::InvalidAccountOwner);
523 }
524 instructions::store_current_index_checked(
525 mut_account_ref.data_as_mut_slice(),
526 current_top_level_instruction as u16,
527 )?;
528 }
529 Ok(())
530 }
531
532 pub fn pop(&mut self) -> Result<(), InstructionError> {
534 if self.instruction_stack.is_empty() {
535 return Err(InstructionError::CallDepth);
536 }
537 let detected_an_unbalanced_instruction =
539 self.get_current_instruction_context()
540 .and_then(|instruction_context| {
541 self.accounts
543 .try_borrow_mut(
544 instruction_context.get_index_of_program_account_in_transaction()?,
545 )
546 .map_err(|err| {
547 if err == InstructionError::AccountBorrowFailed {
548 InstructionError::AccountBorrowOutstanding
549 } else {
550 err
551 }
552 })?;
553 Ok(self.accounts.get_lamports_delta() != 0)
554 });
555 self.instruction_stack.pop();
557 if let Some(instr_idx) = self.instruction_stack.last() {
558 self.transaction_frame.current_executing_instruction = *instr_idx as u16;
559 }
560 if detected_an_unbalanced_instruction? {
561 Err(InstructionError::UnbalancedInstruction)
562 } else {
563 Ok(())
564 }
565 }
566
567 pub fn get_return_data(&self) -> (&Pubkey, &[u8]) {
569 (
570 &self.transaction_frame.return_data_pubkey,
571 &self.return_data_bytes,
572 )
573 }
574
575 pub fn set_return_data(
577 &mut self,
578 program_id: Pubkey,
579 data: Vec<u8>,
580 ) -> Result<(), InstructionError> {
581 self.transaction_frame.return_data_pubkey = program_id;
582 self.transaction_frame
583 .return_data_scratchpad
584 .set_len(data.len() as u64);
585 self.return_data_bytes = data;
586 Ok(())
587 }
588
589 pub fn access_violation_handler(
591 &self,
592 virtual_address_space_adjustments: bool,
593 account_data_direct_mapping: bool,
594 ) -> AccessViolationHandler {
595 let accounts = Rc::clone(&self.accounts);
596 Box::new(
597 move |region: &mut MemoryRegion,
598 address_space_reserved_for_account: u64,
599 access_type: AccessType,
600 vm_addr: u64,
601 len: u64| {
602 if access_type == AccessType::Load {
603 return;
604 }
605 let Some(index_in_transaction) = region.access_violation_handler_payload else {
606 return;
608 };
609 let region_vm_addr_start = region.vm_addr_range().start;
610 let requested_length = vm_addr
611 .saturating_add(len)
612 .saturating_sub(region_vm_addr_start)
613 as usize;
614 if requested_length > address_space_reserved_for_account as usize {
615 return;
617 }
618
619 let Ok(mut account) = accounts.try_borrow_mut(index_in_transaction) else {
623 debug_assert!(false);
624 return;
625 };
626 if accounts.touch(index_in_transaction).is_err() {
627 debug_assert!(false);
628 return;
629 }
630
631 let remaining_allowed_growth = MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION
632 .saturating_sub(accounts.resize_delta())
633 .max(0) as usize;
634
635 if requested_length > region.len() {
636 let old_len = account.data().len();
640 let new_len = (address_space_reserved_for_account as usize)
641 .min(MAX_ACCOUNT_DATA_LEN as usize)
642 .min(old_len.saturating_add(remaining_allowed_growth));
643 debug_assert!(accounts.can_data_be_resized(old_len, new_len).is_ok());
645 if accounts
646 .update_accounts_resize_delta(old_len, new_len)
647 .is_err()
648 {
649 return;
650 }
651
652 account.resize(new_len, 0);
653 let data_ptr = region.host_buffer().ptr() as *mut u8;
654 let new_buffer = std::ptr::slice_from_raw_parts_mut(data_ptr, new_len);
655 unsafe {
656 region.redirect(new_buffer);
685 }
686 }
687
688 if virtual_address_space_adjustments && account_data_direct_mapping {
690 unsafe {
691 region.redirect(account.raw_mut_data_slice());
693 }
694 }
695 },
696 )
697 }
698
699 pub fn take_instruction_trace(&mut self) -> InstructionTrace<'_> {
701 self.instruction_trace.pop();
704 (
705 std::mem::take(&mut self.instruction_trace),
706 std::mem::take(&mut self.instruction_accounts),
707 std::mem::take(&mut self.instruction_data),
708 )
709 }
710
711 pub fn number_of_called_instructions_in_trace(&self) -> usize {
717 self.next_top_level_instruction_index
718 .saturating_add(self.transaction_frame.number_of_cpis_in_trace as usize)
719 }
720
721 pub fn next_top_level_instruction_index(&self) -> usize {
723 self.next_top_level_instruction_index
724 }
725
726 pub fn number_of_cpis_in_trace(&self) -> usize {
728 self.transaction_frame.number_of_cpis_in_trace as usize
729 }
730}
731
732#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
734#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
735#[cfg_attr(feature = "wincode", derive(wincode::SchemaRead, wincode::SchemaWrite))]
736#[derive(Clone, Debug, Default, PartialEq, Eq)]
737pub struct TransactionReturnData {
738 pub program_id: Pubkey,
739 pub data: Vec<u8>,
740}
741
742#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
744pub struct ExecutionRecord {
745 pub accounts: Vec<KeyedAccountSharedData>,
746 pub return_data: TransactionReturnData,
747 pub touched_flags: Box<[bool]>,
749 pub accounts_resize_delta: i64,
750}
751
752#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
754impl From<TransactionContext<'_>> for ExecutionRecord {
755 fn from(context: TransactionContext) -> Self {
756 let (accounts, touched_flags, resize_delta) = Rc::try_unwrap(context.accounts)
757 .expect("transaction_context.accounts has unexpected outstanding refs")
758 .take();
759
760 let touched_flags: Box<[bool]> = Vec::from(touched_flags)
766 .into_iter()
767 .map(|flag| flag.into_inner())
768 .collect();
769
770 let return_data = TransactionReturnData {
771 program_id: context.transaction_frame.return_data_pubkey,
772 data: context.return_data_bytes,
773 };
774
775 Self {
776 accounts,
777 return_data,
778 touched_flags,
779 accounts_resize_delta: Cell::into_inner(resize_delta),
780 }
781 }
782}
783
784#[cfg(all(test, not(target_arch = "sbf"), not(target_arch = "bpf")))]
785mod tests {
786 use super::*;
787
788 #[test]
789 fn test_instructions_sysvar_store_index_checked() {
790 let build_transaction_context = |account: AccountSharedData| {
791 TransactionContext::new(
792 vec![
793 (Pubkey::new_unique(), AccountSharedData::default()),
794 (instructions::id(), account),
795 ],
796 Rent::default(),
797 2,
798 2,
799 1,
800 )
801 };
802
803 let correct_space = 2;
804 let rent_exempt_lamports = Rent::default().minimum_balance(correct_space);
805
806 let account =
808 AccountSharedData::new(rent_exempt_lamports, correct_space, &Pubkey::new_unique());
809 assert_eq!(
810 build_transaction_context(account).push(),
811 Err(InstructionError::InvalidAccountOwner),
812 );
813
814 let account =
816 AccountSharedData::new(rent_exempt_lamports, 0, &solana_sdk_ids::sysvar::id());
817 assert_eq!(
818 build_transaction_context(account).push(),
819 Err(InstructionError::AccountDataTooSmall),
820 );
821
822 let account = AccountSharedData::new(
824 rent_exempt_lamports,
825 correct_space,
826 &solana_sdk_ids::sysvar::id(),
827 );
828 assert_eq!(build_transaction_context(account).push(), Ok(()),);
829 }
830
831 #[test]
832 fn test_invalid_native_loader_index() {
833 let mut transaction_context = TransactionContext::new(
834 vec![(
835 Pubkey::new_unique(),
836 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
837 )],
838 Rent::default(),
839 20,
840 20,
841 1,
842 );
843
844 transaction_context
845 .configure_top_level_instruction_for_tests(
846 u16::MAX,
847 vec![InstructionAccount::new(0, false, false)],
848 vec![],
849 )
850 .unwrap();
851 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
852
853 let result = instruction_context.get_index_of_program_account_in_transaction();
854 assert_eq!(result, Err(InstructionError::MissingAccount));
855
856 let result = instruction_context.get_program_key();
857 assert_eq!(result, Err(InstructionError::MissingAccount));
858
859 let result = instruction_context.get_program_owner();
860 assert_eq!(result.err(), Some(InstructionError::MissingAccount));
861 }
862
863 #[test]
864 fn test_instruction_shared_items() {
865 let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 11];
866 let mut transaction_context =
867 TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 3);
868
869 let instruction_accounts_1 = vec![
870 InstructionAccount::new(0, false, true),
871 InstructionAccount::new(3, true, false),
872 ];
873 transaction_context
874 .configure_top_level_instruction_for_tests(
875 1,
876 instruction_accounts_1.clone(),
877 vec![1, 2, 3, 4],
878 )
879 .unwrap();
880 transaction_context.push().unwrap();
881
882 let instruction_accounts_2 = vec![
883 InstructionAccount::new(0, false, true),
884 InstructionAccount::new(3, true, false),
885 InstructionAccount::new(5, false, false),
886 ];
887 transaction_context
888 .configure_top_level_instruction_for_tests(
889 1,
890 instruction_accounts_2.clone(),
891 vec![5, 6, 7, 8, 9],
892 )
893 .unwrap();
894 transaction_context.push().unwrap();
895
896 let instruction_accounts_3 = vec![
897 InstructionAccount::new(0, false, true),
898 InstructionAccount::new(3, true, false),
899 InstructionAccount::new(5, false, false),
900 InstructionAccount::new(3, false, false),
901 InstructionAccount::new(10, false, false),
902 ];
903 transaction_context
904 .configure_top_level_instruction_for_tests(
905 1,
906 instruction_accounts_3.clone(),
907 vec![10, 11],
908 )
909 .unwrap();
910 transaction_context.push().unwrap();
911
912 let first_ix_context = transaction_context
913 .get_instruction_context_at_index_in_trace(0)
914 .unwrap();
915 assert_eq!(
916 instruction_accounts_1.as_slice(),
917 first_ix_context.instruction_accounts
918 );
919 assert_eq!(
920 *first_ix_context.instruction_data,
921 **transaction_context.instruction_data.first().unwrap()
922 );
923 for (idx_in_ix, acc) in instruction_accounts_1.iter().enumerate() {
924 assert_eq!(
925 *first_ix_context
926 .dedup_map
927 .get(acc.index_in_transaction as usize)
928 .unwrap(),
929 idx_in_ix as u8
930 );
931 }
932
933 let second_ix_context = transaction_context
934 .get_instruction_context_at_index_in_trace(1)
935 .unwrap();
936 assert_eq!(
937 instruction_accounts_2.as_slice(),
938 second_ix_context.instruction_accounts
939 );
940 assert_eq!(
941 *second_ix_context.instruction_data,
942 **transaction_context.instruction_data.get(1).unwrap()
943 );
944 for (idx_in_ix, acc) in instruction_accounts_2.iter().enumerate() {
945 assert_eq!(
946 *second_ix_context
947 .dedup_map
948 .get(acc.index_in_transaction as usize)
949 .unwrap(),
950 idx_in_ix as u8
951 );
952 }
953
954 let third_ix_context = transaction_context
955 .get_instruction_context_at_index_in_trace(2)
956 .unwrap();
957 assert_eq!(
958 instruction_accounts_3.as_slice(),
959 third_ix_context.instruction_accounts
960 );
961 assert_eq!(
962 *third_ix_context.instruction_data,
963 **transaction_context.instruction_data.get(2).unwrap()
964 );
965 for (idx_in_ix, acc) in instruction_accounts_3.iter().enumerate() {
966 if idx_in_ix == 3 {
967 assert_eq!(
968 *third_ix_context
969 .dedup_map
970 .get(acc.index_in_transaction as usize)
971 .unwrap(),
972 1
973 );
974 } else {
975 assert_eq!(
976 *third_ix_context
977 .dedup_map
978 .get(acc.index_in_transaction as usize)
979 .unwrap(),
980 idx_in_ix as u8
981 );
982 }
983 }
984 }
985
986 #[test]
987 fn test_number_of_instructions() {
988 let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
989 let mut transaction_context =
990 TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 2);
991 assert_eq!(
992 transaction_context
993 .transaction_frame
994 .number_of_cpis_in_trace,
995 0
996 );
997
998 transaction_context
1000 .configure_instruction_at_index(
1001 0,
1002 0,
1003 vec![InstructionAccount::new(1, false, false)],
1004 vec![0; 3],
1005 Vec::new().into(),
1006 None,
1007 )
1008 .unwrap();
1009
1010 transaction_context
1012 .configure_instruction_at_index(
1013 1,
1014 0,
1015 vec![InstructionAccount::new(1, false, false)],
1016 vec![0; 3],
1017 Vec::new().into(),
1018 None,
1019 )
1020 .unwrap();
1021
1022 transaction_context.push().unwrap();
1024 assert_eq!(
1025 transaction_context
1026 .transaction_frame
1027 .current_executing_instruction,
1028 0
1029 );
1030 assert_eq!(
1031 transaction_context.number_of_called_instructions_in_trace(),
1032 1
1033 );
1034
1035 assert_eq!(
1036 transaction_context
1037 .transaction_frame
1038 .total_number_of_instructions_in_trace,
1039 2
1040 );
1041
1042 assert_eq!(
1043 transaction_context
1044 .transaction_frame
1045 .number_of_cpis_in_trace,
1046 0
1047 );
1048
1049 assert_eq!(
1050 transaction_context
1051 .transaction_frame
1052 .cpi_data_scratchpad
1053 .ptr(),
1054 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(2))
1055 );
1056 assert_eq!(
1057 transaction_context
1058 .transaction_frame
1059 .cpi_data_scratchpad
1060 .len(),
1061 0,
1062 );
1063 assert_eq!(
1064 transaction_context
1065 .transaction_frame
1066 .cpi_accounts_scratchpad
1067 .ptr(),
1068 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1069 .saturating_add(GUEST_REGION_SIZE.saturating_mul(2))
1070 );
1071 assert_eq!(
1072 transaction_context
1073 .transaction_frame
1074 .cpi_data_scratchpad
1075 .len(),
1076 0,
1077 );
1078
1079 assert_eq!(
1080 transaction_context.number_of_called_instructions_in_trace(),
1081 1
1082 );
1083
1084 transaction_context
1086 .configure_next_cpi_for_tests(
1087 0,
1088 vec![InstructionAccount::new(2, false, true)],
1089 Vec::new(),
1090 )
1091 .unwrap();
1092
1093 transaction_context.push().unwrap();
1094 assert_eq!(
1095 transaction_context
1096 .transaction_frame
1097 .current_executing_instruction,
1098 2
1099 );
1100
1101 assert_eq!(
1102 transaction_context
1103 .transaction_frame
1104 .total_number_of_instructions_in_trace,
1105 3
1106 );
1107 assert_eq!(
1108 transaction_context
1109 .transaction_frame
1110 .number_of_cpis_in_trace,
1111 1
1112 );
1113 assert_eq!(
1114 transaction_context.number_of_called_instructions_in_trace(),
1115 2
1116 );
1117
1118 assert_eq!(
1119 transaction_context
1120 .transaction_frame
1121 .cpi_data_scratchpad
1122 .ptr(),
1123 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(3))
1124 );
1125 assert_eq!(
1126 transaction_context
1127 .transaction_frame
1128 .cpi_accounts_scratchpad
1129 .ptr(),
1130 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1131 .saturating_add(GUEST_REGION_SIZE.saturating_mul(3))
1132 );
1133
1134 transaction_context
1136 .configure_next_cpi_for_tests(
1137 0,
1138 vec![InstructionAccount::new(2, false, true)],
1139 Vec::new(),
1140 )
1141 .unwrap();
1142
1143 transaction_context.push().unwrap();
1144 assert_eq!(
1145 transaction_context
1146 .transaction_frame
1147 .current_executing_instruction,
1148 3
1149 );
1150
1151 assert_eq!(
1152 transaction_context
1153 .transaction_frame
1154 .total_number_of_instructions_in_trace,
1155 4
1156 );
1157
1158 assert_eq!(
1159 transaction_context
1160 .transaction_frame
1161 .cpi_data_scratchpad
1162 .ptr(),
1163 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(4))
1164 );
1165 assert_eq!(
1166 transaction_context
1167 .transaction_frame
1168 .cpi_accounts_scratchpad
1169 .ptr(),
1170 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1171 .saturating_add(GUEST_REGION_SIZE.saturating_mul(4))
1172 );
1173
1174 assert_eq!(
1175 transaction_context
1176 .transaction_frame
1177 .number_of_cpis_in_trace,
1178 2
1179 );
1180
1181 assert_eq!(
1182 transaction_context.number_of_called_instructions_in_trace(),
1183 3
1184 );
1185 transaction_context.pop().unwrap();
1187 assert_eq!(
1188 transaction_context.number_of_called_instructions_in_trace(),
1189 3
1190 );
1191
1192 assert_eq!(
1193 transaction_context
1194 .transaction_frame
1195 .total_number_of_instructions_in_trace,
1196 4
1197 );
1198 assert_eq!(
1199 transaction_context
1200 .transaction_frame
1201 .number_of_cpis_in_trace,
1202 2,
1203 );
1204 assert_eq!(
1205 transaction_context
1206 .transaction_frame
1207 .current_executing_instruction,
1208 2
1209 );
1210
1211 transaction_context
1213 .configure_next_cpi_for_tests(
1214 0,
1215 vec![InstructionAccount::new(2, false, true)],
1216 Vec::new(),
1217 )
1218 .unwrap();
1219
1220 transaction_context.push().unwrap();
1221 assert_eq!(
1222 transaction_context
1223 .transaction_frame
1224 .current_executing_instruction,
1225 4
1226 );
1227
1228 assert_eq!(
1229 transaction_context
1230 .transaction_frame
1231 .total_number_of_instructions_in_trace,
1232 5
1233 );
1234
1235 assert_eq!(
1236 transaction_context
1237 .transaction_frame
1238 .cpi_data_scratchpad
1239 .ptr(),
1240 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1241 );
1242 assert_eq!(
1243 transaction_context
1244 .transaction_frame
1245 .cpi_accounts_scratchpad
1246 .ptr(),
1247 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1248 .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1249 );
1250
1251 assert_eq!(
1252 transaction_context
1253 .transaction_frame
1254 .number_of_cpis_in_trace,
1255 3
1256 );
1257 assert_eq!(
1258 transaction_context.number_of_called_instructions_in_trace(),
1259 4
1260 );
1261
1262 transaction_context.pop().unwrap();
1264
1265 assert_eq!(
1266 transaction_context
1267 .transaction_frame
1268 .current_executing_instruction,
1269 2
1270 );
1271
1272 assert_eq!(
1273 transaction_context
1274 .transaction_frame
1275 .total_number_of_instructions_in_trace,
1276 5
1277 );
1278
1279 assert_eq!(
1280 transaction_context
1281 .transaction_frame
1282 .cpi_data_scratchpad
1283 .ptr(),
1284 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1285 );
1286 assert_eq!(
1287 transaction_context
1288 .transaction_frame
1289 .cpi_accounts_scratchpad
1290 .ptr(),
1291 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1292 .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1293 );
1294
1295 assert_eq!(
1296 transaction_context
1297 .transaction_frame
1298 .number_of_cpis_in_trace,
1299 3
1300 );
1301
1302 transaction_context.pop().unwrap();
1304 assert_eq!(
1305 transaction_context.number_of_called_instructions_in_trace(),
1306 4
1307 );
1308
1309 assert_eq!(
1310 transaction_context
1311 .transaction_frame
1312 .current_executing_instruction,
1313 0
1314 );
1315
1316 assert_eq!(
1317 transaction_context
1318 .transaction_frame
1319 .total_number_of_instructions_in_trace,
1320 5
1321 );
1322
1323 assert_eq!(
1324 transaction_context
1325 .transaction_frame
1326 .cpi_data_scratchpad
1327 .ptr(),
1328 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1329 );
1330 assert_eq!(
1331 transaction_context
1332 .transaction_frame
1333 .cpi_accounts_scratchpad
1334 .ptr(),
1335 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1336 .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1337 );
1338
1339 assert_eq!(
1340 transaction_context
1341 .transaction_frame
1342 .number_of_cpis_in_trace,
1343 3,
1344 );
1345
1346 transaction_context.pop().unwrap();
1348 transaction_context.push().unwrap();
1349 assert_eq!(
1350 transaction_context
1351 .transaction_frame
1352 .current_executing_instruction,
1353 1,
1354 );
1355 assert_eq!(
1356 transaction_context
1357 .transaction_frame
1358 .number_of_cpis_in_trace,
1359 3
1360 );
1361
1362 transaction_context
1364 .configure_next_cpi_for_tests(
1365 0,
1366 vec![InstructionAccount::new(2, false, true)],
1367 Vec::new(),
1368 )
1369 .unwrap();
1370
1371 transaction_context.push().unwrap();
1372
1373 assert_eq!(
1374 transaction_context
1375 .transaction_frame
1376 .current_executing_instruction,
1377 5,
1378 );
1379
1380 assert_eq!(
1381 transaction_context
1382 .transaction_frame
1383 .total_number_of_instructions_in_trace,
1384 6
1385 );
1386
1387 assert_eq!(
1388 transaction_context
1389 .transaction_frame
1390 .cpi_data_scratchpad
1391 .ptr(),
1392 GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(6))
1393 );
1394 assert_eq!(
1395 transaction_context
1396 .transaction_frame
1397 .cpi_accounts_scratchpad
1398 .ptr(),
1399 GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1400 .saturating_add(GUEST_REGION_SIZE.saturating_mul(6))
1401 );
1402
1403 assert_eq!(
1404 transaction_context
1405 .transaction_frame
1406 .number_of_cpis_in_trace,
1407 4
1408 );
1409 assert_eq!(
1410 transaction_context.number_of_called_instructions_in_trace(),
1411 6
1412 );
1413
1414 transaction_context.pop().unwrap();
1416 assert_eq!(
1417 transaction_context
1418 .transaction_frame
1419 .number_of_cpis_in_trace,
1420 4
1421 );
1422 assert_eq!(
1423 transaction_context
1424 .transaction_frame
1425 .current_executing_instruction,
1426 1,
1427 );
1428
1429 transaction_context.pop().unwrap();
1430 }
1431
1432 #[test]
1433 fn test_get_current_instruction_index() {
1434 let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
1435 let mut transaction_context =
1436 TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 2);
1437
1438 transaction_context
1440 .configure_instruction_at_index(
1441 0,
1442 1,
1443 vec![
1444 InstructionAccount::new(0, false, false),
1445 InstructionAccount::new(1, false, false),
1446 ],
1447 vec![u8::MAX; 3],
1448 Cow::Owned(Vec::new()),
1449 None,
1450 )
1451 .unwrap();
1452
1453 transaction_context
1455 .configure_instruction_at_index(
1456 1,
1457 1,
1458 vec![
1459 InstructionAccount::new(0, false, false),
1460 InstructionAccount::new(1, false, true),
1461 ],
1462 vec![u8::MAX; 3],
1463 Cow::Owned(Vec::new()),
1464 None,
1465 )
1466 .unwrap();
1467
1468 transaction_context.push().unwrap();
1469 assert_eq!(
1470 transaction_context.get_current_instruction_index().unwrap(),
1471 0
1472 );
1473
1474 transaction_context.pop().unwrap();
1475
1476 transaction_context.push().unwrap();
1477 assert_eq!(
1478 transaction_context.get_current_instruction_index().unwrap(),
1479 1
1480 );
1481
1482 transaction_context
1484 .configure_next_cpi_for_tests(
1485 1,
1486 vec![
1487 InstructionAccount::new(0, false, true),
1488 InstructionAccount::new(1, false, false),
1489 ],
1490 Vec::new(),
1491 )
1492 .unwrap();
1493 transaction_context.push().unwrap();
1494 assert_eq!(
1495 transaction_context.get_current_instruction_index().unwrap(),
1496 2
1497 );
1498
1499 transaction_context
1501 .configure_next_cpi_for_tests(
1502 1,
1503 vec![
1504 InstructionAccount::new(0, false, true),
1505 InstructionAccount::new(1, false, false),
1506 ],
1507 Vec::new(),
1508 )
1509 .unwrap();
1510 transaction_context.push().unwrap();
1511 assert_eq!(
1512 transaction_context.get_current_instruction_index().unwrap(),
1513 3
1514 );
1515
1516 transaction_context.pop().unwrap();
1518 assert_eq!(
1519 transaction_context.get_current_instruction_index().unwrap(),
1520 2
1521 );
1522
1523 transaction_context.pop().unwrap();
1525 assert_eq!(
1526 transaction_context.get_current_instruction_index().unwrap(),
1527 1
1528 );
1529 }
1530}