1#![deny(clippy::indexing_slicing)]
3#![cfg_attr(docsrs, feature(doc_auto_cfg))]
4
5#[cfg(feature = "dev-context-only-utils")]
6use qualifier_attr::qualifiers;
7#[cfg(not(target_os = "solana"))]
8use {solana_account::WritableAccount, solana_rent::Rent};
9use {
10 solana_account::{AccountSharedData, ReadableAccount},
11 solana_instruction::error::InstructionError,
12 solana_instructions_sysvar as instructions,
13 solana_pubkey::Pubkey,
14 solana_sbpf::memory_region::{AccessType, AccessViolationHandler, MemoryRegion},
15 std::{
16 cell::{Cell, Ref, RefCell, RefMut},
17 collections::HashSet,
18 pin::Pin,
19 rc::Rc,
20 },
21};
22
23pub const MAX_ACCOUNTS_PER_TRANSACTION: usize = 256;
24pub const MAX_ACCOUNTS_PER_INSTRUCTION: usize = 255;
27pub const MAX_INSTRUCTION_DATA_LEN: usize = 10 * 1024;
28pub const MAX_ACCOUNT_DATA_LEN: u64 = 10 * 1024 * 1024;
29pub const MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION: i64 = MAX_ACCOUNT_DATA_LEN as i64 * 2;
33pub const MAX_ACCOUNT_DATA_GROWTH_PER_INSTRUCTION: usize = 10 * 1_024;
34
35#[cfg(test)]
36static_assertions::const_assert_eq!(
37 MAX_ACCOUNTS_PER_INSTRUCTION,
38 solana_program_entrypoint::NON_DUP_MARKER as usize,
39);
40#[cfg(test)]
41static_assertions::const_assert_eq!(
42 MAX_ACCOUNT_DATA_LEN,
43 solana_system_interface::MAX_PERMITTED_DATA_LENGTH,
44);
45#[cfg(test)]
46static_assertions::const_assert_eq!(
47 MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION,
48 solana_system_interface::MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION,
49);
50#[cfg(test)]
51static_assertions::const_assert_eq!(
52 MAX_ACCOUNT_DATA_GROWTH_PER_INSTRUCTION,
53 solana_account_info::MAX_PERMITTED_DATA_INCREASE,
54);
55
56pub type IndexOfAccount = u16;
58
59#[repr(C)]
66#[derive(Clone, Copy, Debug)]
67pub struct InstructionAccount {
68 pub index_in_transaction: IndexOfAccount,
70 is_signer: u8,
72 is_writable: u8,
74}
75
76impl InstructionAccount {
77 pub fn new(
78 index_in_transaction: IndexOfAccount,
79 is_signer: bool,
80 is_writable: bool,
81 ) -> InstructionAccount {
82 InstructionAccount {
83 index_in_transaction,
84 is_signer: is_signer as u8,
85 is_writable: is_writable as u8,
86 }
87 }
88
89 pub fn is_signer(&self) -> bool {
90 self.is_signer != 0
91 }
92
93 pub fn is_writable(&self) -> bool {
94 self.is_writable != 0
95 }
96
97 pub fn set_is_signer(&mut self, value: bool) {
98 self.is_signer = value as u8;
99 }
100
101 pub fn set_is_writable(&mut self, value: bool) {
102 self.is_writable = value as u8;
103 }
104}
105
106pub type TransactionAccount = (Pubkey, AccountSharedData);
108
109#[derive(Debug)]
110pub struct TransactionAccounts {
111 accounts: Vec<RefCell<AccountSharedData>>,
112 touched_flags: RefCell<Box<[bool]>>,
113 resize_delta: Cell<i64>,
114 lamports_delta: Cell<i128>,
115}
116
117impl TransactionAccounts {
118 #[cfg(not(target_os = "solana"))]
119 fn new(accounts: Vec<RefCell<AccountSharedData>>) -> TransactionAccounts {
120 let touched_flags = vec![false; accounts.len()].into_boxed_slice();
121 TransactionAccounts {
122 accounts,
123 touched_flags: RefCell::new(touched_flags),
124 resize_delta: Cell::new(0),
125 lamports_delta: Cell::new(0),
126 }
127 }
128
129 fn len(&self) -> usize {
130 self.accounts.len()
131 }
132
133 #[cfg(not(target_os = "solana"))]
134 pub fn touch(&self, index: IndexOfAccount) -> Result<(), InstructionError> {
135 *self
136 .touched_flags
137 .borrow_mut()
138 .get_mut(index as usize)
139 .ok_or(InstructionError::NotEnoughAccountKeys)? = true;
140 Ok(())
141 }
142
143 fn update_accounts_resize_delta(
144 &self,
145 old_len: usize,
146 new_len: usize,
147 ) -> Result<(), InstructionError> {
148 let accounts_resize_delta = self.resize_delta.get();
149 self.resize_delta.set(
150 accounts_resize_delta.saturating_add((new_len as i64).saturating_sub(old_len as i64)),
151 );
152 Ok(())
153 }
154
155 fn can_data_be_resized(&self, old_len: usize, new_len: usize) -> Result<(), InstructionError> {
156 if new_len > MAX_ACCOUNT_DATA_LEN as usize {
158 return Err(InstructionError::InvalidRealloc);
159 }
160 let length_delta = (new_len as i64).saturating_sub(old_len as i64);
162 if self.resize_delta.get().saturating_add(length_delta)
163 > MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION
164 {
165 return Err(InstructionError::MaxAccountsDataAllocationsExceeded);
166 }
167 Ok(())
168 }
169
170 #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
171 fn try_borrow_mut(
172 &self,
173 index: IndexOfAccount,
174 ) -> Result<RefMut<'_, AccountSharedData>, InstructionError> {
175 self.accounts
176 .get(index as usize)
177 .ok_or(InstructionError::MissingAccount)?
178 .try_borrow_mut()
179 .map_err(|_| InstructionError::AccountBorrowFailed)
180 }
181
182 pub fn try_borrow(
183 &self,
184 index: IndexOfAccount,
185 ) -> Result<Ref<'_, AccountSharedData>, InstructionError> {
186 self.accounts
187 .get(index as usize)
188 .ok_or(InstructionError::MissingAccount)?
189 .try_borrow()
190 .map_err(|_| InstructionError::AccountBorrowFailed)
191 }
192
193 fn add_lamports_delta(&self, balance: i128) -> Result<(), InstructionError> {
194 let delta = self.lamports_delta.get();
195 self.lamports_delta.set(
196 delta
197 .checked_add(balance)
198 .ok_or(InstructionError::ArithmeticOverflow)?,
199 );
200 Ok(())
201 }
202
203 fn get_lamports_delta(&self) -> i128 {
204 self.lamports_delta.get()
205 }
206}
207
208#[derive(Debug)]
212pub struct TransactionContext {
213 account_keys: Pin<Box<[Pubkey]>>,
214 accounts: Rc<TransactionAccounts>,
215 instruction_stack_capacity: usize,
216 instruction_trace_capacity: usize,
217 instruction_stack: Vec<usize>,
218 instruction_trace: Vec<InstructionFrame>,
219 top_level_instruction_index: usize,
220 return_data: TransactionReturnData,
221 #[cfg(not(target_os = "solana"))]
222 rent: Rent,
223}
224
225impl TransactionContext {
226 #[cfg(not(target_os = "solana"))]
228 pub fn new(
229 transaction_accounts: Vec<TransactionAccount>,
230 rent: Rent,
231 instruction_stack_capacity: usize,
232 instruction_trace_capacity: usize,
233 ) -> Self {
234 let (account_keys, accounts): (Vec<_>, Vec<_>) = transaction_accounts
235 .into_iter()
236 .map(|(key, account)| (key, RefCell::new(account)))
237 .unzip();
238 Self {
239 account_keys: Pin::new(account_keys.into_boxed_slice()),
240 accounts: Rc::new(TransactionAccounts::new(accounts)),
241 instruction_stack_capacity,
242 instruction_trace_capacity,
243 instruction_stack: Vec::with_capacity(instruction_stack_capacity),
244 instruction_trace: vec![InstructionFrame::default()],
245 top_level_instruction_index: 0,
246 return_data: TransactionReturnData::default(),
247 rent,
248 }
249 }
250
251 #[cfg(not(target_os = "solana"))]
253 pub fn deconstruct_without_keys(self) -> Result<Vec<AccountSharedData>, InstructionError> {
254 if !self.instruction_stack.is_empty() {
255 return Err(InstructionError::CallDepth);
256 }
257
258 Ok(Rc::try_unwrap(self.accounts)
259 .expect("transaction_context.accounts has unexpected outstanding refs")
260 .accounts
261 .into_iter()
262 .map(RefCell::into_inner)
263 .collect())
264 }
265
266 #[cfg(not(target_os = "solana"))]
267 pub fn accounts(&self) -> &Rc<TransactionAccounts> {
268 &self.accounts
269 }
270
271 pub fn get_number_of_accounts(&self) -> IndexOfAccount {
273 self.accounts.len() as IndexOfAccount
274 }
275
276 pub fn get_key_of_account_at_index(
278 &self,
279 index_in_transaction: IndexOfAccount,
280 ) -> Result<&Pubkey, InstructionError> {
281 self.account_keys
282 .get(index_in_transaction as usize)
283 .ok_or(InstructionError::NotEnoughAccountKeys)
284 }
285
286 pub fn find_index_of_account(&self, pubkey: &Pubkey) -> Option<IndexOfAccount> {
288 self.account_keys
289 .iter()
290 .position(|key| key == pubkey)
291 .map(|index| index as IndexOfAccount)
292 }
293
294 pub fn get_instruction_trace_capacity(&self) -> usize {
296 self.instruction_trace_capacity
297 }
298
299 pub fn get_instruction_trace_length(&self) -> usize {
304 self.instruction_trace.len().saturating_sub(1)
305 }
306
307 pub fn get_instruction_context_at_index_in_trace(
309 &self,
310 index_in_trace: usize,
311 ) -> Result<InstructionContext, InstructionError> {
312 let instruction = self
313 .instruction_trace
314 .get(index_in_trace)
315 .ok_or(InstructionError::CallDepth)?;
316 Ok(InstructionContext {
317 transaction_context: self,
318 nesting_level: instruction.nesting_level,
319 program_account_index_in_tx: instruction.program_account_index_in_tx,
320 instruction_accounts: &instruction.instruction_accounts,
321 dedup_map: &instruction.dedup_map,
322 instruction_data: &instruction.instruction_data,
323 })
324 }
325
326 pub fn get_instruction_context_at_nesting_level(
328 &self,
329 nesting_level: usize,
330 ) -> Result<InstructionContext, InstructionError> {
331 let index_in_trace = *self
332 .instruction_stack
333 .get(nesting_level)
334 .ok_or(InstructionError::CallDepth)?;
335 let instruction_context = self.get_instruction_context_at_index_in_trace(index_in_trace)?;
336 debug_assert_eq!(instruction_context.nesting_level, nesting_level);
337 Ok(instruction_context)
338 }
339
340 pub fn get_instruction_stack_capacity(&self) -> usize {
342 self.instruction_stack_capacity
343 }
344
345 pub fn get_instruction_stack_height(&self) -> usize {
348 self.instruction_stack.len()
349 }
350
351 pub fn get_current_instruction_context(&self) -> Result<InstructionContext, InstructionError> {
353 let level = self
354 .get_instruction_stack_height()
355 .checked_sub(1)
356 .ok_or(InstructionError::CallDepth)?;
357 self.get_instruction_context_at_nesting_level(level)
358 }
359
360 pub fn get_next_instruction_context(&self) -> Result<InstructionContext, InstructionError> {
364 let index_in_trace = self
365 .instruction_trace
366 .len()
367 .checked_sub(1)
368 .ok_or(InstructionError::CallDepth)?;
369 self.get_instruction_context_at_index_in_trace(index_in_trace)
370 }
371
372 pub fn configure_next_instruction(
376 &mut self,
377 program_index: IndexOfAccount,
378 instruction_accounts: Vec<InstructionAccount>,
379 deduplication_map: Vec<u8>,
380 instruction_data: &[u8],
381 ) -> Result<(), InstructionError> {
382 debug_assert_eq!(deduplication_map.len(), MAX_ACCOUNTS_PER_TRANSACTION);
383 let instruction = self
384 .instruction_trace
385 .last_mut()
386 .ok_or(InstructionError::CallDepth)?;
387 instruction.program_account_index_in_tx = program_index;
388 instruction.instruction_accounts = instruction_accounts;
389 instruction.instruction_data = instruction_data.to_vec();
390 instruction.dedup_map = deduplication_map;
391 Ok(())
392 }
393
394 pub fn configure_next_instruction_for_tests(
396 &mut self,
397 program_index: IndexOfAccount,
398 instruction_accounts: Vec<InstructionAccount>,
399 instruction_data: &[u8],
400 ) -> Result<(), InstructionError> {
401 let mut dedup_map = vec![u8::MAX; MAX_ACCOUNTS_PER_TRANSACTION];
402 for (idx, account) in instruction_accounts.iter().enumerate() {
403 let index_in_instruction = dedup_map
404 .get_mut(account.index_in_transaction as usize)
405 .unwrap();
406 if *index_in_instruction == u8::MAX {
407 *index_in_instruction = idx as u8;
408 }
409 }
410 self.configure_next_instruction(
411 program_index,
412 instruction_accounts,
413 dedup_map,
414 instruction_data,
415 )
416 }
417
418 #[cfg(not(target_os = "solana"))]
420 pub fn push(&mut self) -> Result<(), InstructionError> {
421 let nesting_level = self.get_instruction_stack_height();
422 if !self.instruction_stack.is_empty() && self.accounts.get_lamports_delta() != 0 {
423 return Err(InstructionError::UnbalancedInstruction);
424 }
425 {
426 let instruction = self
427 .instruction_trace
428 .last_mut()
429 .ok_or(InstructionError::CallDepth)?;
430 instruction.nesting_level = nesting_level;
431 }
432 let index_in_trace = self.get_instruction_trace_length();
433 if index_in_trace >= self.instruction_trace_capacity {
434 return Err(InstructionError::MaxInstructionTraceLengthExceeded);
435 }
436 self.instruction_trace.push(InstructionFrame::default());
437 if nesting_level >= self.instruction_stack_capacity {
438 return Err(InstructionError::CallDepth);
439 }
440 self.instruction_stack.push(index_in_trace);
441 if let Some(index_in_transaction) = self.find_index_of_account(&instructions::id()) {
442 let mut mut_account_ref = self.accounts.try_borrow_mut(index_in_transaction)?;
443 if mut_account_ref.owner() != &solana_sdk_ids::sysvar::id() {
444 return Err(InstructionError::InvalidAccountOwner);
445 }
446 instructions::store_current_index_checked(
447 mut_account_ref.data_as_mut_slice(),
448 self.top_level_instruction_index as u16,
449 )?;
450 }
451 Ok(())
452 }
453
454 #[cfg(not(target_os = "solana"))]
456 pub fn pop(&mut self) -> Result<(), InstructionError> {
457 if self.instruction_stack.is_empty() {
458 return Err(InstructionError::CallDepth);
459 }
460 let detected_an_unbalanced_instruction =
462 self.get_current_instruction_context()
463 .and_then(|instruction_context| {
464 self.accounts
466 .try_borrow_mut(
467 instruction_context.get_index_of_program_account_in_transaction()?,
468 )
469 .map_err(|err| {
470 if err == InstructionError::AccountBorrowFailed {
471 InstructionError::AccountBorrowOutstanding
472 } else {
473 err
474 }
475 })?;
476 Ok(self.accounts.get_lamports_delta() != 0)
477 });
478 self.instruction_stack.pop();
480 if self.instruction_stack.is_empty() {
481 self.top_level_instruction_index = self.top_level_instruction_index.saturating_add(1);
482 }
483 if detected_an_unbalanced_instruction? {
484 Err(InstructionError::UnbalancedInstruction)
485 } else {
486 Ok(())
487 }
488 }
489
490 pub fn get_return_data(&self) -> (&Pubkey, &[u8]) {
492 (&self.return_data.program_id, &self.return_data.data)
493 }
494
495 pub fn set_return_data(
497 &mut self,
498 program_id: Pubkey,
499 data: Vec<u8>,
500 ) -> Result<(), InstructionError> {
501 self.return_data = TransactionReturnData { program_id, data };
502 Ok(())
503 }
504
505 pub fn accounts_resize_delta(&self) -> i64 {
507 self.accounts.resize_delta.get()
508 }
509
510 pub fn access_violation_handler(
512 &self,
513 stricter_abi_and_runtime_constraints: bool,
514 account_data_direct_mapping: bool,
515 ) -> AccessViolationHandler {
516 let accounts = Rc::clone(&self.accounts);
517 Box::new(
518 move |region: &mut MemoryRegion,
519 address_space_reserved_for_account: u64,
520 access_type: AccessType,
521 vm_addr: u64,
522 len: u64| {
523 if access_type == AccessType::Load {
524 return;
525 }
526 let Some(index_in_transaction) = region.access_violation_handler_payload else {
527 return;
529 };
530 let requested_length =
531 vm_addr.saturating_add(len).saturating_sub(region.vm_addr) as usize;
532 if requested_length > address_space_reserved_for_account as usize {
533 return;
535 }
536
537 let Ok(mut account) = accounts.try_borrow_mut(index_in_transaction) else {
541 debug_assert!(false);
542 return;
543 };
544 if accounts.touch(index_in_transaction).is_err() {
545 debug_assert!(false);
546 return;
547 }
548
549 let remaining_allowed_growth = MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION
550 .saturating_sub(accounts.resize_delta.get())
551 .max(0) as usize;
552
553 if requested_length > region.len as usize {
554 let old_len = account.data().len();
558 let new_len = (address_space_reserved_for_account as usize)
559 .min(MAX_ACCOUNT_DATA_LEN as usize)
560 .min(old_len.saturating_add(remaining_allowed_growth));
561 debug_assert!(accounts.can_data_be_resized(old_len, new_len).is_ok());
563 if accounts
564 .update_accounts_resize_delta(old_len, new_len)
565 .is_err()
566 {
567 return;
568 }
569 account.resize(new_len, 0);
570 region.len = new_len as u64;
571 }
572
573 if stricter_abi_and_runtime_constraints && account_data_direct_mapping {
575 region.host_addr = account.data_as_mut_slice().as_mut_ptr() as u64;
576 region.writable = true;
577 }
578 },
579 )
580 }
581}
582
583#[cfg_attr(
585 feature = "serde",
586 derive(serde_derive::Deserialize, serde_derive::Serialize)
587)]
588#[derive(Clone, Debug, Default, PartialEq, Eq)]
589pub struct TransactionReturnData {
590 pub program_id: Pubkey,
591 pub data: Vec<u8>,
592}
593
594#[derive(Debug, Clone, Default)]
596pub struct InstructionFrame {
597 nesting_level: usize,
598 program_account_index_in_tx: IndexOfAccount,
599 instruction_accounts: Vec<InstructionAccount>,
600 dedup_map: Vec<u8>,
604 instruction_data: Vec<u8>,
605}
606
607#[derive(Debug, Clone)]
609pub struct InstructionContext<'a> {
610 transaction_context: &'a TransactionContext,
611 nesting_level: usize,
613 program_account_index_in_tx: IndexOfAccount,
614 instruction_accounts: &'a [InstructionAccount],
615 dedup_map: &'a [u8],
616 instruction_data: &'a [u8],
617}
618
619impl<'a> InstructionContext<'a> {
620 pub fn get_stack_height(&self) -> usize {
624 self.nesting_level.saturating_add(1)
625 }
626
627 pub fn get_number_of_instruction_accounts(&self) -> IndexOfAccount {
629 self.instruction_accounts.len() as IndexOfAccount
630 }
631
632 pub fn check_number_of_instruction_accounts(
634 &self,
635 expected_at_least: IndexOfAccount,
636 ) -> Result<(), InstructionError> {
637 if self.get_number_of_instruction_accounts() < expected_at_least {
638 Err(InstructionError::NotEnoughAccountKeys)
639 } else {
640 Ok(())
641 }
642 }
643
644 pub fn get_instruction_data(&self) -> &[u8] {
646 self.instruction_data
647 }
648
649 pub fn find_index_of_instruction_account(
651 &self,
652 transaction_context: &TransactionContext,
653 pubkey: &Pubkey,
654 ) -> Option<IndexOfAccount> {
655 self.instruction_accounts
656 .iter()
657 .position(|instruction_account| {
658 transaction_context
659 .account_keys
660 .get(instruction_account.index_in_transaction as usize)
661 == Some(pubkey)
662 })
663 .map(|index| index as IndexOfAccount)
664 }
665
666 pub fn get_index_of_program_account_in_transaction(
668 &self,
669 ) -> Result<IndexOfAccount, InstructionError> {
670 if self.program_account_index_in_tx == u16::MAX {
671 Err(InstructionError::NotEnoughAccountKeys)
672 } else {
673 Ok(self.program_account_index_in_tx)
674 }
675 }
676
677 pub fn get_index_of_instruction_account_in_transaction(
679 &self,
680 instruction_account_index: IndexOfAccount,
681 ) -> Result<IndexOfAccount, InstructionError> {
682 Ok(self
683 .instruction_accounts
684 .get(instruction_account_index as usize)
685 .ok_or(InstructionError::NotEnoughAccountKeys)?
686 .index_in_transaction as IndexOfAccount)
687 }
688
689 pub fn get_index_of_account_in_instruction(
691 &self,
692 index_in_transaction: IndexOfAccount,
693 ) -> Result<IndexOfAccount, InstructionError> {
694 self.dedup_map
695 .get(index_in_transaction as usize)
696 .and_then(|idx| {
697 if *idx as usize >= self.instruction_accounts.len() {
698 None
699 } else {
700 Some(*idx as IndexOfAccount)
701 }
702 })
703 .ok_or(InstructionError::MissingAccount)
704 }
705
706 pub fn is_instruction_account_duplicate(
709 &self,
710 instruction_account_index: IndexOfAccount,
711 ) -> Result<Option<IndexOfAccount>, InstructionError> {
712 let index_in_transaction =
713 self.get_index_of_instruction_account_in_transaction(instruction_account_index)?;
714 let first_instruction_account_index =
715 self.get_index_of_account_in_instruction(index_in_transaction)?;
716
717 Ok(
718 if first_instruction_account_index == instruction_account_index {
719 None
720 } else {
721 Some(first_instruction_account_index)
722 },
723 )
724 }
725
726 pub fn get_program_key(&self) -> Result<&'a Pubkey, InstructionError> {
728 self.get_index_of_program_account_in_transaction()
729 .and_then(|index_in_transaction| {
730 self.transaction_context
731 .get_key_of_account_at_index(index_in_transaction)
732 })
733 }
734
735 pub fn get_program_owner(&self) -> Result<Pubkey, InstructionError> {
737 self.get_index_of_program_account_in_transaction()
738 .and_then(|index_in_transaction| {
739 self.transaction_context
740 .accounts
741 .try_borrow(index_in_transaction)
742 })
743 .map(|acc| *acc.owner())
744 }
745
746 pub fn try_borrow_instruction_account(
748 &self,
749 index_in_instruction: IndexOfAccount,
750 ) -> Result<BorrowedAccount, InstructionError> {
751 let instruction_account = *self
752 .instruction_accounts
753 .get(index_in_instruction as usize)
754 .ok_or(InstructionError::NotEnoughAccountKeys)?;
755
756 let account = self
757 .transaction_context
758 .accounts
759 .try_borrow_mut(instruction_account.index_in_transaction)?;
760
761 Ok(BorrowedAccount {
762 transaction_context: self.transaction_context,
763 instruction_account,
764 account,
765 index_in_transaction_of_instruction_program: self.program_account_index_in_tx,
766 })
767 }
768
769 pub fn is_instruction_account_signer(
771 &self,
772 instruction_account_index: IndexOfAccount,
773 ) -> Result<bool, InstructionError> {
774 Ok(self
775 .instruction_accounts
776 .get(instruction_account_index as usize)
777 .ok_or(InstructionError::MissingAccount)?
778 .is_signer())
779 }
780
781 pub fn is_instruction_account_writable(
783 &self,
784 instruction_account_index: IndexOfAccount,
785 ) -> Result<bool, InstructionError> {
786 Ok(self
787 .instruction_accounts
788 .get(instruction_account_index as usize)
789 .ok_or(InstructionError::MissingAccount)?
790 .is_writable())
791 }
792
793 pub fn get_signers(&self) -> Result<HashSet<Pubkey>, InstructionError> {
795 let mut result = HashSet::new();
796 for instruction_account in self.instruction_accounts.iter() {
797 if instruction_account.is_signer() {
798 result.insert(
799 *self
800 .transaction_context
801 .get_key_of_account_at_index(instruction_account.index_in_transaction)?,
802 );
803 }
804 }
805 Ok(result)
806 }
807
808 pub fn instruction_accounts(&self) -> &[InstructionAccount] {
809 self.instruction_accounts
810 }
811
812 pub fn get_key_of_instruction_account(
813 &self,
814 index_in_instruction: IndexOfAccount,
815 ) -> Result<&'a Pubkey, InstructionError> {
816 self.get_index_of_instruction_account_in_transaction(index_in_instruction)
817 .and_then(|idx| self.transaction_context.get_key_of_account_at_index(idx))
818 }
819}
820
821#[derive(Debug)]
823pub struct BorrowedAccount<'a> {
824 transaction_context: &'a TransactionContext,
825 account: RefMut<'a, AccountSharedData>,
826 instruction_account: InstructionAccount,
827 index_in_transaction_of_instruction_program: IndexOfAccount,
828}
829
830impl BorrowedAccount<'_> {
831 #[inline]
833 pub fn get_index_in_transaction(&self) -> IndexOfAccount {
834 self.instruction_account.index_in_transaction
835 }
836
837 #[inline]
839 pub fn get_key(&self) -> &Pubkey {
840 self.transaction_context
841 .get_key_of_account_at_index(self.instruction_account.index_in_transaction)
842 .unwrap()
843 }
844
845 #[inline]
847 pub fn get_owner(&self) -> &Pubkey {
848 self.account.owner()
849 }
850
851 #[cfg(not(target_os = "solana"))]
853 pub fn set_owner(&mut self, pubkey: &[u8]) -> Result<(), InstructionError> {
854 if !self.is_owned_by_current_program() {
856 return Err(InstructionError::ModifiedProgramId);
857 }
858 if !self.is_writable() {
860 return Err(InstructionError::ModifiedProgramId);
861 }
862 if !is_zeroed(self.get_data()) {
864 return Err(InstructionError::ModifiedProgramId);
865 }
866 if self.get_owner().to_bytes() == pubkey {
868 return Ok(());
869 }
870 self.touch()?;
871 self.account.copy_into_owner_from_slice(pubkey);
872 Ok(())
873 }
874
875 #[inline]
877 pub fn get_lamports(&self) -> u64 {
878 self.account.lamports()
879 }
880
881 #[cfg(not(target_os = "solana"))]
883 pub fn set_lamports(&mut self, lamports: u64) -> Result<(), InstructionError> {
884 if !self.is_owned_by_current_program() && lamports < self.get_lamports() {
886 return Err(InstructionError::ExternalAccountLamportSpend);
887 }
888 if !self.is_writable() {
890 return Err(InstructionError::ReadonlyLamportChange);
891 }
892 let old_lamports = self.get_lamports();
894 if old_lamports == lamports {
895 return Ok(());
896 }
897
898 let lamports_balance = (lamports as i128).saturating_sub(old_lamports as i128);
899 self.transaction_context
900 .accounts
901 .add_lamports_delta(lamports_balance)?;
902
903 self.touch()?;
904 self.account.set_lamports(lamports);
905 Ok(())
906 }
907
908 #[cfg(not(target_os = "solana"))]
910 pub fn checked_add_lamports(&mut self, lamports: u64) -> Result<(), InstructionError> {
911 self.set_lamports(
912 self.get_lamports()
913 .checked_add(lamports)
914 .ok_or(InstructionError::ArithmeticOverflow)?,
915 )
916 }
917
918 #[cfg(not(target_os = "solana"))]
920 pub fn checked_sub_lamports(&mut self, lamports: u64) -> Result<(), InstructionError> {
921 self.set_lamports(
922 self.get_lamports()
923 .checked_sub(lamports)
924 .ok_or(InstructionError::ArithmeticOverflow)?,
925 )
926 }
927
928 #[inline]
930 pub fn get_data(&self) -> &[u8] {
931 self.account.data()
932 }
933
934 #[cfg(not(target_os = "solana"))]
936 pub fn get_data_mut(&mut self) -> Result<&mut [u8], InstructionError> {
937 self.can_data_be_changed()?;
938 self.touch()?;
939 self.make_data_mut();
940 Ok(self.account.data_as_mut_slice())
941 }
942
943 #[cfg(not(target_os = "solana"))]
948 pub fn set_data_from_slice(&mut self, data: &[u8]) -> Result<(), InstructionError> {
949 self.can_data_be_resized(data.len())?;
950 self.touch()?;
951 self.update_accounts_resize_delta(data.len())?;
952 self.account.set_data_from_slice(data);
957
958 Ok(())
959 }
960
961 #[cfg(not(target_os = "solana"))]
965 pub fn set_data_length(&mut self, new_length: usize) -> Result<(), InstructionError> {
966 self.can_data_be_resized(new_length)?;
967 if self.get_data().len() == new_length {
969 return Ok(());
970 }
971 self.touch()?;
972 self.update_accounts_resize_delta(new_length)?;
973 self.account.resize(new_length, 0);
974 Ok(())
975 }
976
977 #[cfg(not(target_os = "solana"))]
979 pub fn extend_from_slice(&mut self, data: &[u8]) -> Result<(), InstructionError> {
980 let new_len = self.get_data().len().saturating_add(data.len());
981 self.can_data_be_resized(new_len)?;
982
983 if data.is_empty() {
984 return Ok(());
985 }
986
987 self.touch()?;
988 self.update_accounts_resize_delta(new_len)?;
989 self.make_data_mut();
993 self.account.extend_from_slice(data);
994 Ok(())
995 }
996
997 #[cfg(not(target_os = "solana"))]
1005 pub fn is_shared(&self) -> bool {
1006 self.account.is_shared()
1007 }
1008
1009 #[cfg(not(target_os = "solana"))]
1010 fn make_data_mut(&mut self) {
1011 if self.account.is_shared() {
1017 self.account
1018 .reserve(MAX_ACCOUNT_DATA_GROWTH_PER_INSTRUCTION);
1019 }
1020 }
1021
1022 #[cfg(all(not(target_os = "solana"), feature = "bincode"))]
1024 pub fn get_state<T: serde::de::DeserializeOwned>(&self) -> Result<T, InstructionError> {
1025 self.account
1026 .deserialize_data()
1027 .map_err(|_| InstructionError::InvalidAccountData)
1028 }
1029
1030 #[cfg(all(not(target_os = "solana"), feature = "bincode"))]
1032 pub fn set_state<T: serde::Serialize>(&mut self, state: &T) -> Result<(), InstructionError> {
1033 let data = self.get_data_mut()?;
1034 let serialized_size =
1035 bincode::serialized_size(state).map_err(|_| InstructionError::GenericError)?;
1036 if serialized_size > data.len() as u64 {
1037 return Err(InstructionError::AccountDataTooSmall);
1038 }
1039 bincode::serialize_into(&mut *data, state).map_err(|_| InstructionError::GenericError)?;
1040 Ok(())
1041 }
1042
1043 #[cfg(not(target_os = "solana"))]
1046 pub fn is_rent_exempt_at_data_length(&self, data_length: usize) -> bool {
1047 self.transaction_context
1048 .rent
1049 .is_exempt(self.get_lamports(), data_length)
1050 }
1051
1052 #[inline]
1054 #[deprecated(since = "2.1.0", note = "Use `get_owner` instead")]
1055 pub fn is_executable(&self) -> bool {
1056 self.account.executable()
1057 }
1058
1059 #[cfg(not(target_os = "solana"))]
1061 pub fn set_executable(&mut self, is_executable: bool) -> Result<(), InstructionError> {
1062 if !self
1064 .transaction_context
1065 .rent
1066 .is_exempt(self.get_lamports(), self.get_data().len())
1067 {
1068 return Err(InstructionError::ExecutableAccountNotRentExempt);
1069 }
1070 if !self.is_owned_by_current_program() {
1072 return Err(InstructionError::ExecutableModified);
1073 }
1074 if !self.is_writable() {
1076 return Err(InstructionError::ExecutableModified);
1077 }
1078 #[allow(deprecated)]
1080 if self.is_executable() == is_executable {
1081 return Ok(());
1082 }
1083 self.touch()?;
1084 self.account.set_executable(is_executable);
1085 Ok(())
1086 }
1087
1088 #[cfg(not(target_os = "solana"))]
1090 #[inline]
1091 pub fn get_rent_epoch(&self) -> u64 {
1092 self.account.rent_epoch()
1093 }
1094
1095 pub fn is_signer(&self) -> bool {
1097 self.instruction_account.is_signer()
1098 }
1099
1100 pub fn is_writable(&self) -> bool {
1102 self.instruction_account.is_writable()
1103 }
1104
1105 pub fn is_owned_by_current_program(&self) -> bool {
1107 self.transaction_context
1108 .get_key_of_account_at_index(self.index_in_transaction_of_instruction_program)
1109 .map(|program_key| program_key == self.get_owner())
1110 .unwrap_or_default()
1111 }
1112
1113 #[cfg(not(target_os = "solana"))]
1115 pub fn can_data_be_changed(&self) -> Result<(), InstructionError> {
1116 if !self.is_writable() {
1118 return Err(InstructionError::ReadonlyDataModified);
1119 }
1120 if !self.is_owned_by_current_program() {
1122 return Err(InstructionError::ExternalAccountDataModified);
1123 }
1124 Ok(())
1125 }
1126
1127 #[cfg(not(target_os = "solana"))]
1129 pub fn can_data_be_resized(&self, new_len: usize) -> Result<(), InstructionError> {
1130 let old_len = self.get_data().len();
1131 if new_len != old_len && !self.is_owned_by_current_program() {
1133 return Err(InstructionError::AccountDataSizeChanged);
1134 }
1135 self.transaction_context
1136 .accounts
1137 .can_data_be_resized(old_len, new_len)?;
1138 self.can_data_be_changed()
1139 }
1140
1141 #[cfg(not(target_os = "solana"))]
1142 fn touch(&self) -> Result<(), InstructionError> {
1143 self.transaction_context
1144 .accounts
1145 .touch(self.instruction_account.index_in_transaction)
1146 }
1147
1148 #[cfg(not(target_os = "solana"))]
1149 fn update_accounts_resize_delta(&mut self, new_len: usize) -> Result<(), InstructionError> {
1150 self.transaction_context
1151 .accounts
1152 .update_accounts_resize_delta(self.get_data().len(), new_len)
1153 }
1154}
1155
1156#[cfg(not(target_os = "solana"))]
1158pub struct ExecutionRecord {
1159 pub accounts: Vec<TransactionAccount>,
1160 pub return_data: TransactionReturnData,
1161 pub touched_account_count: u64,
1162 pub accounts_resize_delta: i64,
1163}
1164
1165#[cfg(not(target_os = "solana"))]
1167impl From<TransactionContext> for ExecutionRecord {
1168 fn from(context: TransactionContext) -> Self {
1169 let TransactionAccounts {
1170 accounts,
1171 touched_flags,
1172 resize_delta,
1173 ..
1174 } = Rc::try_unwrap(context.accounts)
1175 .expect("transaction_context.accounts has unexpected outstanding refs");
1176 let accounts = Vec::from(Pin::into_inner(context.account_keys))
1177 .into_iter()
1178 .zip(accounts.into_iter().map(RefCell::into_inner))
1179 .collect();
1180 let touched_account_count = touched_flags
1181 .borrow()
1182 .iter()
1183 .fold(0usize, |accumulator, was_touched| {
1184 accumulator.saturating_add(*was_touched as usize)
1185 }) as u64;
1186 Self {
1187 accounts,
1188 return_data: context.return_data,
1189 touched_account_count,
1190 accounts_resize_delta: Cell::into_inner(resize_delta),
1191 }
1192 }
1193}
1194
1195#[cfg(not(target_os = "solana"))]
1196fn is_zeroed(buf: &[u8]) -> bool {
1197 const ZEROS_LEN: usize = 1024;
1198 const ZEROS: [u8; ZEROS_LEN] = [0; ZEROS_LEN];
1199 let mut chunks = buf.chunks_exact(ZEROS_LEN);
1200
1201 #[allow(clippy::indexing_slicing)]
1202 {
1203 chunks.all(|chunk| chunk == &ZEROS[..])
1204 && chunks.remainder() == &ZEROS[..chunks.remainder().len()]
1205 }
1206}
1207
1208#[cfg(test)]
1209mod tests {
1210 use super::*;
1211
1212 #[test]
1213 fn test_instructions_sysvar_store_index_checked() {
1214 let build_transaction_context = |account: AccountSharedData| {
1215 TransactionContext::new(
1216 vec![
1217 (Pubkey::new_unique(), AccountSharedData::default()),
1218 (instructions::id(), account),
1219 ],
1220 Rent::default(),
1221 2,
1222 2,
1223 )
1224 };
1225
1226 let correct_space = 2;
1227 let rent_exempt_lamports = Rent::default().minimum_balance(correct_space);
1228
1229 let account =
1231 AccountSharedData::new(rent_exempt_lamports, correct_space, &Pubkey::new_unique());
1232 assert_eq!(
1233 build_transaction_context(account).push(),
1234 Err(InstructionError::InvalidAccountOwner),
1235 );
1236
1237 let account =
1239 AccountSharedData::new(rent_exempt_lamports, 0, &solana_sdk_ids::sysvar::id());
1240 assert_eq!(
1241 build_transaction_context(account).push(),
1242 Err(InstructionError::AccountDataTooSmall),
1243 );
1244
1245 let account = AccountSharedData::new(
1247 rent_exempt_lamports,
1248 correct_space,
1249 &solana_sdk_ids::sysvar::id(),
1250 );
1251 assert_eq!(build_transaction_context(account).push(), Ok(()),);
1252 }
1253
1254 #[test]
1255 fn test_invalid_native_loader_index() {
1256 let mut transaction_context = TransactionContext::new(
1257 vec![(
1258 Pubkey::new_unique(),
1259 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1260 )],
1261 Rent::default(),
1262 20,
1263 20,
1264 );
1265
1266 transaction_context
1267 .configure_next_instruction_for_tests(
1268 u16::MAX,
1269 vec![InstructionAccount::new(0, false, false)],
1270 &[],
1271 )
1272 .unwrap();
1273 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1274
1275 let result = instruction_context.get_index_of_program_account_in_transaction();
1276 assert_eq!(result, Err(InstructionError::NotEnoughAccountKeys));
1277
1278 let result = instruction_context.get_program_key();
1279 assert_eq!(result, Err(InstructionError::NotEnoughAccountKeys));
1280
1281 let result = instruction_context.get_program_owner();
1282 assert_eq!(result.err(), Some(InstructionError::NotEnoughAccountKeys));
1283 }
1284}