1#![deny(clippy::indexing_slicing)]
3#![cfg_attr(docsrs, feature(doc_auto_cfg))]
4
5#[cfg(not(target_os = "solana"))]
6use {solana_account::WritableAccount, solana_rent::Rent, std::mem::MaybeUninit};
7use {
8 solana_account::{AccountSharedData, ReadableAccount},
9 solana_instruction::error::InstructionError,
10 solana_instructions_sysvar as instructions,
11 solana_pubkey::Pubkey,
12 std::{
13 cell::{Ref, RefCell, RefMut},
14 collections::HashSet,
15 pin::Pin,
16 rc::Rc,
17 },
18};
19
20#[cfg(not(target_os = "solana"))]
22const MAX_PERMITTED_DATA_LENGTH: u64 = 10 * 1024 * 1024;
23#[cfg(test)]
24static_assertions::const_assert_eq!(
25 MAX_PERMITTED_DATA_LENGTH,
26 solana_system_interface::MAX_PERMITTED_DATA_LENGTH
27);
28
29#[cfg(not(target_os = "solana"))]
31const MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION: i64 =
32 MAX_PERMITTED_DATA_LENGTH as i64 * 2;
33#[cfg(test)]
34static_assertions::const_assert_eq!(
35 MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION,
36 solana_system_interface::MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION
37);
38
39#[cfg(not(target_os = "solana"))]
41const MAX_PERMITTED_DATA_INCREASE: usize = 1_024 * 10;
42#[cfg(test)]
43static_assertions::const_assert_eq!(
44 MAX_PERMITTED_DATA_INCREASE,
45 solana_account_info::MAX_PERMITTED_DATA_INCREASE
46);
47
48pub type IndexOfAccount = u16;
50
51#[derive(Clone, Debug, Eq, PartialEq)]
55pub struct InstructionAccount {
56 pub index_in_transaction: IndexOfAccount,
58 pub index_in_caller: IndexOfAccount,
62 pub index_in_callee: IndexOfAccount,
66 pub is_signer: bool,
68 pub is_writable: bool,
70}
71
72pub type TransactionAccount = (Pubkey, AccountSharedData);
74
75#[derive(Clone, Debug, PartialEq)]
76pub struct TransactionAccounts {
77 accounts: Vec<RefCell<AccountSharedData>>,
78 touched_flags: RefCell<Box<[bool]>>,
79 resize_delta: RefCell<i64>,
80}
81
82impl TransactionAccounts {
83 #[cfg(not(target_os = "solana"))]
84 fn new(accounts: Vec<RefCell<AccountSharedData>>) -> TransactionAccounts {
85 let touched_flags = vec![false; accounts.len()].into_boxed_slice();
86 TransactionAccounts {
87 accounts,
88 touched_flags: RefCell::new(touched_flags),
89 resize_delta: RefCell::new(0),
90 }
91 }
92
93 fn len(&self) -> usize {
94 self.accounts.len()
95 }
96
97 fn get(&self, index: IndexOfAccount) -> Option<&RefCell<AccountSharedData>> {
98 self.accounts.get(index as usize)
99 }
100
101 #[cfg(not(target_os = "solana"))]
102 pub fn touch(&self, index: IndexOfAccount) -> Result<(), InstructionError> {
103 *self
104 .touched_flags
105 .borrow_mut()
106 .get_mut(index as usize)
107 .ok_or(InstructionError::NotEnoughAccountKeys)? = true;
108 Ok(())
109 }
110
111 fn update_accounts_resize_delta(
112 &self,
113 old_len: usize,
114 new_len: usize,
115 ) -> Result<(), InstructionError> {
116 let mut accounts_resize_delta = self
117 .resize_delta
118 .try_borrow_mut()
119 .map_err(|_| InstructionError::GenericError)?;
120 *accounts_resize_delta =
121 accounts_resize_delta.saturating_add((new_len as i64).saturating_sub(old_len as i64));
122 Ok(())
123 }
124
125 fn can_data_be_resized(&self, old_len: usize, new_len: usize) -> Result<(), InstructionError> {
126 if new_len > MAX_PERMITTED_DATA_LENGTH as usize {
128 return Err(InstructionError::InvalidRealloc);
129 }
130 let length_delta = (new_len as i64).saturating_sub(old_len as i64);
132 if self
133 .resize_delta
134 .try_borrow()
135 .map_err(|_| InstructionError::GenericError)
136 .map(|value_ref| *value_ref)?
137 .saturating_add(length_delta)
138 > MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION
139 {
140 return Err(InstructionError::MaxAccountsDataAllocationsExceeded);
141 }
142 Ok(())
143 }
144
145 pub fn try_borrow(
146 &self,
147 index: IndexOfAccount,
148 ) -> Result<Ref<'_, AccountSharedData>, InstructionError> {
149 self.accounts
150 .get(index as usize)
151 .ok_or(InstructionError::MissingAccount)?
152 .try_borrow()
153 .map_err(|_| InstructionError::AccountBorrowFailed)
154 }
155}
156
157#[derive(Debug, Clone, PartialEq)]
161pub struct TransactionContext {
162 account_keys: Pin<Box<[Pubkey]>>,
163 accounts: Rc<TransactionAccounts>,
164 instruction_stack_capacity: usize,
165 instruction_trace_capacity: usize,
166 instruction_stack: Vec<usize>,
167 instruction_trace: Vec<InstructionContext>,
168 top_level_instruction_index: usize,
169 return_data: TransactionReturnData,
170 #[cfg(not(target_os = "solana"))]
171 remove_accounts_executable_flag_checks: bool,
172 #[cfg(not(target_os = "solana"))]
173 rent: Rent,
174}
175
176impl TransactionContext {
177 #[cfg(not(target_os = "solana"))]
179 pub fn new(
180 transaction_accounts: Vec<TransactionAccount>,
181 rent: Rent,
182 instruction_stack_capacity: usize,
183 instruction_trace_capacity: usize,
184 ) -> Self {
185 let (account_keys, accounts): (Vec<_>, Vec<_>) = transaction_accounts
186 .into_iter()
187 .map(|(key, account)| (key, RefCell::new(account)))
188 .unzip();
189 Self {
190 account_keys: Pin::new(account_keys.into_boxed_slice()),
191 accounts: Rc::new(TransactionAccounts::new(accounts)),
192 instruction_stack_capacity,
193 instruction_trace_capacity,
194 instruction_stack: Vec::with_capacity(instruction_stack_capacity),
195 instruction_trace: vec![InstructionContext::default()],
196 top_level_instruction_index: 0,
197 return_data: TransactionReturnData::default(),
198 remove_accounts_executable_flag_checks: true,
199 rent,
200 }
201 }
202
203 #[cfg(not(target_os = "solana"))]
204 pub fn set_remove_accounts_executable_flag_checks(&mut self, enabled: bool) {
205 self.remove_accounts_executable_flag_checks = enabled;
206 }
207
208 #[cfg(not(target_os = "solana"))]
210 pub fn deconstruct_without_keys(self) -> Result<Vec<AccountSharedData>, InstructionError> {
211 if !self.instruction_stack.is_empty() {
212 return Err(InstructionError::CallDepth);
213 }
214
215 Ok(Rc::try_unwrap(self.accounts)
216 .expect("transaction_context.accounts has unexpected outstanding refs")
217 .accounts
218 .into_iter()
219 .map(RefCell::into_inner)
220 .collect())
221 }
222
223 #[cfg(not(target_os = "solana"))]
224 pub fn accounts(&self) -> &Rc<TransactionAccounts> {
225 &self.accounts
226 }
227
228 pub fn get_number_of_accounts(&self) -> IndexOfAccount {
230 self.accounts.len() as IndexOfAccount
231 }
232
233 pub fn get_key_of_account_at_index(
235 &self,
236 index_in_transaction: IndexOfAccount,
237 ) -> Result<&Pubkey, InstructionError> {
238 self.account_keys
239 .get(index_in_transaction as usize)
240 .ok_or(InstructionError::NotEnoughAccountKeys)
241 }
242
243 #[cfg(all(
245 not(target_os = "solana"),
246 any(test, feature = "dev-context-only-utils")
247 ))]
248 pub fn get_account_at_index(
249 &self,
250 index_in_transaction: IndexOfAccount,
251 ) -> Result<&RefCell<AccountSharedData>, InstructionError> {
252 self.accounts
253 .get(index_in_transaction)
254 .ok_or(InstructionError::NotEnoughAccountKeys)
255 }
256
257 pub fn find_index_of_account(&self, pubkey: &Pubkey) -> Option<IndexOfAccount> {
259 self.account_keys
260 .iter()
261 .position(|key| key == pubkey)
262 .map(|index| index as IndexOfAccount)
263 }
264
265 pub fn find_index_of_program_account(&self, pubkey: &Pubkey) -> Option<IndexOfAccount> {
267 self.account_keys
268 .iter()
269 .rposition(|key| key == pubkey)
270 .map(|index| index as IndexOfAccount)
271 }
272
273 pub fn get_instruction_trace_capacity(&self) -> usize {
275 self.instruction_trace_capacity
276 }
277
278 pub fn get_instruction_trace_length(&self) -> usize {
283 self.instruction_trace.len().saturating_sub(1)
284 }
285
286 pub fn get_instruction_context_at_index_in_trace(
288 &self,
289 index_in_trace: usize,
290 ) -> Result<&InstructionContext, InstructionError> {
291 self.instruction_trace
292 .get(index_in_trace)
293 .ok_or(InstructionError::CallDepth)
294 }
295
296 pub fn get_instruction_context_at_nesting_level(
298 &self,
299 nesting_level: usize,
300 ) -> Result<&InstructionContext, InstructionError> {
301 let index_in_trace = *self
302 .instruction_stack
303 .get(nesting_level)
304 .ok_or(InstructionError::CallDepth)?;
305 let instruction_context = self.get_instruction_context_at_index_in_trace(index_in_trace)?;
306 debug_assert_eq!(instruction_context.nesting_level, nesting_level);
307 Ok(instruction_context)
308 }
309
310 pub fn get_instruction_stack_capacity(&self) -> usize {
312 self.instruction_stack_capacity
313 }
314
315 pub fn get_instruction_context_stack_height(&self) -> usize {
318 self.instruction_stack.len()
319 }
320
321 pub fn get_current_instruction_context(&self) -> Result<&InstructionContext, InstructionError> {
323 let level = self
324 .get_instruction_context_stack_height()
325 .checked_sub(1)
326 .ok_or(InstructionError::CallDepth)?;
327 self.get_instruction_context_at_nesting_level(level)
328 }
329
330 pub fn get_next_instruction_context(
334 &mut self,
335 ) -> Result<&mut InstructionContext, InstructionError> {
336 self.instruction_trace
337 .last_mut()
338 .ok_or(InstructionError::CallDepth)
339 }
340
341 #[cfg(not(target_os = "solana"))]
343 pub fn push(&mut self) -> Result<(), InstructionError> {
344 let nesting_level = self.get_instruction_context_stack_height();
345 let caller_instruction_context = self
346 .instruction_trace
347 .last()
348 .ok_or(InstructionError::CallDepth)?;
349 let callee_instruction_accounts_lamport_sum =
350 self.instruction_accounts_lamport_sum(caller_instruction_context)?;
351 if !self.instruction_stack.is_empty() {
352 let caller_instruction_context = self.get_current_instruction_context()?;
353 let original_caller_instruction_accounts_lamport_sum =
354 caller_instruction_context.instruction_accounts_lamport_sum;
355 let current_caller_instruction_accounts_lamport_sum =
356 self.instruction_accounts_lamport_sum(caller_instruction_context)?;
357 if original_caller_instruction_accounts_lamport_sum
358 != current_caller_instruction_accounts_lamport_sum
359 {
360 return Err(InstructionError::UnbalancedInstruction);
361 }
362 }
363 {
364 let instruction_context = self.get_next_instruction_context()?;
365 instruction_context.nesting_level = nesting_level;
366 instruction_context.instruction_accounts_lamport_sum =
367 callee_instruction_accounts_lamport_sum;
368 }
369 let index_in_trace = self.get_instruction_trace_length();
370 if index_in_trace >= self.instruction_trace_capacity {
371 return Err(InstructionError::MaxInstructionTraceLengthExceeded);
372 }
373 self.instruction_trace.push(InstructionContext::default());
374 if nesting_level >= self.instruction_stack_capacity {
375 return Err(InstructionError::CallDepth);
376 }
377 self.instruction_stack.push(index_in_trace);
378 if let Some(index_in_transaction) = self.find_index_of_account(&instructions::id()) {
379 let mut mut_account_ref = self
380 .accounts
381 .get(index_in_transaction)
382 .ok_or(InstructionError::NotEnoughAccountKeys)?
383 .try_borrow_mut()
384 .map_err(|_| InstructionError::AccountBorrowFailed)?;
385 if mut_account_ref.owner() != &solana_sdk_ids::sysvar::id() {
386 return Err(InstructionError::InvalidAccountOwner);
387 }
388 instructions::store_current_index_checked(
389 mut_account_ref.data_as_mut_slice(),
390 self.top_level_instruction_index as u16,
391 )?;
392 }
393 Ok(())
394 }
395
396 #[cfg(not(target_os = "solana"))]
398 pub fn pop(&mut self) -> Result<(), InstructionError> {
399 if self.instruction_stack.is_empty() {
400 return Err(InstructionError::CallDepth);
401 }
402 let detected_an_unbalanced_instruction =
404 self.get_current_instruction_context()
405 .and_then(|instruction_context| {
406 for index_in_transaction in instruction_context.program_accounts.iter() {
408 self.accounts
409 .get(*index_in_transaction)
410 .ok_or(InstructionError::NotEnoughAccountKeys)?
411 .try_borrow_mut()
412 .map_err(|_| InstructionError::AccountBorrowOutstanding)?;
413 }
414 self.instruction_accounts_lamport_sum(instruction_context)
415 .map(|instruction_accounts_lamport_sum| {
416 instruction_context.instruction_accounts_lamport_sum
417 != instruction_accounts_lamport_sum
418 })
419 });
420 self.instruction_stack.pop();
422 if self.instruction_stack.is_empty() {
423 self.top_level_instruction_index = self.top_level_instruction_index.saturating_add(1);
424 }
425 if detected_an_unbalanced_instruction? {
426 Err(InstructionError::UnbalancedInstruction)
427 } else {
428 Ok(())
429 }
430 }
431
432 pub fn get_return_data(&self) -> (&Pubkey, &[u8]) {
434 (&self.return_data.program_id, &self.return_data.data)
435 }
436
437 pub fn set_return_data(
439 &mut self,
440 program_id: Pubkey,
441 data: Vec<u8>,
442 ) -> Result<(), InstructionError> {
443 self.return_data = TransactionReturnData { program_id, data };
444 Ok(())
445 }
446
447 #[cfg(not(target_os = "solana"))]
449 fn instruction_accounts_lamport_sum(
450 &self,
451 instruction_context: &InstructionContext,
452 ) -> Result<u128, InstructionError> {
453 let mut instruction_accounts_lamport_sum: u128 = 0;
454 for instruction_account_index in 0..instruction_context.get_number_of_instruction_accounts()
455 {
456 if instruction_context
457 .is_instruction_account_duplicate(instruction_account_index)?
458 .is_some()
459 {
460 continue; }
462 let index_in_transaction = instruction_context
463 .get_index_of_instruction_account_in_transaction(instruction_account_index)?;
464 instruction_accounts_lamport_sum = (self
465 .accounts
466 .get(index_in_transaction)
467 .ok_or(InstructionError::NotEnoughAccountKeys)?
468 .try_borrow()
469 .map_err(|_| InstructionError::AccountBorrowOutstanding)?
470 .lamports() as u128)
471 .checked_add(instruction_accounts_lamport_sum)
472 .ok_or(InstructionError::ArithmeticOverflow)?;
473 }
474 Ok(instruction_accounts_lamport_sum)
475 }
476
477 pub fn accounts_resize_delta(&self) -> Result<i64, InstructionError> {
479 self.accounts
480 .resize_delta
481 .try_borrow()
482 .map_err(|_| InstructionError::GenericError)
483 .map(|value_ref| *value_ref)
484 }
485
486 pub fn account_data_write_access_handler(&self) -> Box<dyn Fn(u32) -> Result<u64, ()>> {
488 let accounts = Rc::clone(&self.accounts);
489 Box::new(move |index_in_transaction| {
490 let mut account = accounts
494 .accounts
495 .get(index_in_transaction as usize)
496 .ok_or(())?
497 .try_borrow_mut()
498 .map_err(|_| ())?;
499 accounts
500 .touch(index_in_transaction as IndexOfAccount)
501 .map_err(|_| ())?;
502
503 if account.is_shared() {
504 account.reserve(MAX_PERMITTED_DATA_INCREASE);
507 }
508 Ok(account.data_as_mut_slice().as_mut_ptr() as u64)
509 })
510 }
511}
512
513#[cfg_attr(
515 feature = "serde",
516 derive(serde_derive::Deserialize, serde_derive::Serialize)
517)]
518#[derive(Clone, Debug, Default, PartialEq, Eq)]
519pub struct TransactionReturnData {
520 pub program_id: Pubkey,
521 pub data: Vec<u8>,
522}
523
524#[derive(Debug, Clone, Default, Eq, PartialEq)]
528pub struct InstructionContext {
529 nesting_level: usize,
530 instruction_accounts_lamport_sum: u128,
531 program_accounts: Vec<IndexOfAccount>,
532 instruction_accounts: Vec<InstructionAccount>,
533 instruction_data: Vec<u8>,
534}
535
536impl InstructionContext {
537 #[cfg(not(target_os = "solana"))]
539 pub fn configure(
540 &mut self,
541 program_accounts: &[IndexOfAccount],
542 instruction_accounts: &[InstructionAccount],
543 instruction_data: &[u8],
544 ) {
545 self.program_accounts = program_accounts.to_vec();
546 self.instruction_accounts = instruction_accounts.to_vec();
547 self.instruction_data = instruction_data.to_vec();
548 }
549
550 pub fn get_stack_height(&self) -> usize {
554 self.nesting_level.saturating_add(1)
555 }
556
557 pub fn get_number_of_program_accounts(&self) -> IndexOfAccount {
559 self.program_accounts.len() as IndexOfAccount
560 }
561
562 pub fn get_number_of_instruction_accounts(&self) -> IndexOfAccount {
564 self.instruction_accounts.len() as IndexOfAccount
565 }
566
567 pub fn check_number_of_instruction_accounts(
569 &self,
570 expected_at_least: IndexOfAccount,
571 ) -> Result<(), InstructionError> {
572 if self.get_number_of_instruction_accounts() < expected_at_least {
573 Err(InstructionError::NotEnoughAccountKeys)
574 } else {
575 Ok(())
576 }
577 }
578
579 pub fn get_instruction_data(&self) -> &[u8] {
581 &self.instruction_data
582 }
583
584 pub fn find_index_of_program_account(
586 &self,
587 transaction_context: &TransactionContext,
588 pubkey: &Pubkey,
589 ) -> Option<IndexOfAccount> {
590 self.program_accounts
591 .iter()
592 .position(|index_in_transaction| {
593 transaction_context
594 .account_keys
595 .get(*index_in_transaction as usize)
596 == Some(pubkey)
597 })
598 .map(|index| index as IndexOfAccount)
599 }
600
601 pub fn find_index_of_instruction_account(
603 &self,
604 transaction_context: &TransactionContext,
605 pubkey: &Pubkey,
606 ) -> Option<IndexOfAccount> {
607 self.instruction_accounts
608 .iter()
609 .position(|instruction_account| {
610 transaction_context
611 .account_keys
612 .get(instruction_account.index_in_transaction as usize)
613 == Some(pubkey)
614 })
615 .map(|index| index as IndexOfAccount)
616 }
617
618 pub fn get_index_of_program_account_in_transaction(
620 &self,
621 program_account_index: IndexOfAccount,
622 ) -> Result<IndexOfAccount, InstructionError> {
623 Ok(*self
624 .program_accounts
625 .get(program_account_index as usize)
626 .ok_or(InstructionError::NotEnoughAccountKeys)?)
627 }
628
629 pub fn get_index_of_instruction_account_in_transaction(
631 &self,
632 instruction_account_index: IndexOfAccount,
633 ) -> Result<IndexOfAccount, InstructionError> {
634 Ok(self
635 .instruction_accounts
636 .get(instruction_account_index as usize)
637 .ok_or(InstructionError::NotEnoughAccountKeys)?
638 .index_in_transaction as IndexOfAccount)
639 }
640
641 pub fn is_instruction_account_duplicate(
644 &self,
645 instruction_account_index: IndexOfAccount,
646 ) -> Result<Option<IndexOfAccount>, InstructionError> {
647 let index_in_callee = self
648 .instruction_accounts
649 .get(instruction_account_index as usize)
650 .ok_or(InstructionError::NotEnoughAccountKeys)?
651 .index_in_callee;
652 Ok(if index_in_callee == instruction_account_index {
653 None
654 } else {
655 Some(index_in_callee)
656 })
657 }
658
659 pub fn get_last_program_key<'a, 'b: 'a>(
661 &'a self,
662 transaction_context: &'b TransactionContext,
663 ) -> Result<&'b Pubkey, InstructionError> {
664 self.get_index_of_program_account_in_transaction(
665 self.get_number_of_program_accounts().saturating_sub(1),
666 )
667 .and_then(|index_in_transaction| {
668 transaction_context.get_key_of_account_at_index(index_in_transaction)
669 })
670 }
671
672 fn try_borrow_account<'a, 'b: 'a>(
673 &'a self,
674 transaction_context: &'b TransactionContext,
675 index_in_transaction: IndexOfAccount,
676 index_in_instruction: IndexOfAccount,
677 ) -> Result<BorrowedAccount<'a>, InstructionError> {
678 let account = transaction_context
679 .accounts
680 .get(index_in_transaction)
681 .ok_or(InstructionError::MissingAccount)?
682 .try_borrow_mut()
683 .map_err(|_| InstructionError::AccountBorrowFailed)?;
684 Ok(BorrowedAccount {
685 transaction_context,
686 instruction_context: self,
687 index_in_transaction,
688 index_in_instruction,
689 account,
690 })
691 }
692
693 pub fn try_borrow_last_program_account<'a, 'b: 'a>(
695 &'a self,
696 transaction_context: &'b TransactionContext,
697 ) -> Result<BorrowedAccount<'a>, InstructionError> {
698 let result = self.try_borrow_program_account(
699 transaction_context,
700 self.get_number_of_program_accounts().saturating_sub(1),
701 );
702 debug_assert!(result.is_ok());
703 result
704 }
705
706 pub fn try_borrow_program_account<'a, 'b: 'a>(
708 &'a self,
709 transaction_context: &'b TransactionContext,
710 program_account_index: IndexOfAccount,
711 ) -> Result<BorrowedAccount<'a>, InstructionError> {
712 let index_in_transaction =
713 self.get_index_of_program_account_in_transaction(program_account_index)?;
714 self.try_borrow_account(
715 transaction_context,
716 index_in_transaction,
717 program_account_index,
718 )
719 }
720
721 pub fn try_borrow_instruction_account<'a, 'b: 'a>(
723 &'a self,
724 transaction_context: &'b TransactionContext,
725 instruction_account_index: IndexOfAccount,
726 ) -> Result<BorrowedAccount<'a>, InstructionError> {
727 let index_in_transaction =
728 self.get_index_of_instruction_account_in_transaction(instruction_account_index)?;
729 self.try_borrow_account(
730 transaction_context,
731 index_in_transaction,
732 self.get_number_of_program_accounts()
733 .saturating_add(instruction_account_index),
734 )
735 }
736
737 pub fn is_instruction_account_signer(
739 &self,
740 instruction_account_index: IndexOfAccount,
741 ) -> Result<bool, InstructionError> {
742 Ok(self
743 .instruction_accounts
744 .get(instruction_account_index as usize)
745 .ok_or(InstructionError::MissingAccount)?
746 .is_signer)
747 }
748
749 pub fn is_instruction_account_writable(
751 &self,
752 instruction_account_index: IndexOfAccount,
753 ) -> Result<bool, InstructionError> {
754 Ok(self
755 .instruction_accounts
756 .get(instruction_account_index as usize)
757 .ok_or(InstructionError::MissingAccount)?
758 .is_writable)
759 }
760
761 pub fn get_signers(
763 &self,
764 transaction_context: &TransactionContext,
765 ) -> Result<HashSet<Pubkey>, InstructionError> {
766 let mut result = HashSet::new();
767 for instruction_account in self.instruction_accounts.iter() {
768 if instruction_account.is_signer {
769 result.insert(
770 *transaction_context
771 .get_key_of_account_at_index(instruction_account.index_in_transaction)?,
772 );
773 }
774 }
775 Ok(result)
776 }
777}
778
779#[derive(Debug)]
781pub struct BorrowedAccount<'a> {
782 transaction_context: &'a TransactionContext,
783 instruction_context: &'a InstructionContext,
784 index_in_transaction: IndexOfAccount,
785 index_in_instruction: IndexOfAccount,
786 account: RefMut<'a, AccountSharedData>,
787}
788
789impl BorrowedAccount<'_> {
790 pub fn transaction_context(&self) -> &TransactionContext {
792 self.transaction_context
793 }
794
795 #[inline]
797 pub fn get_index_in_transaction(&self) -> IndexOfAccount {
798 self.index_in_transaction
799 }
800
801 #[inline]
803 pub fn get_key(&self) -> &Pubkey {
804 self.transaction_context
805 .get_key_of_account_at_index(self.index_in_transaction)
806 .unwrap()
807 }
808
809 #[inline]
811 pub fn get_owner(&self) -> &Pubkey {
812 self.account.owner()
813 }
814
815 #[cfg(not(target_os = "solana"))]
817 pub fn set_owner(&mut self, pubkey: &[u8]) -> Result<(), InstructionError> {
818 if !self.is_owned_by_current_program() {
820 return Err(InstructionError::ModifiedProgramId);
821 }
822 if !self.is_writable() {
824 return Err(InstructionError::ModifiedProgramId);
825 }
826 if self.is_executable_internal() {
828 return Err(InstructionError::ModifiedProgramId);
829 }
830 if !is_zeroed(self.get_data()) {
832 return Err(InstructionError::ModifiedProgramId);
833 }
834 if self.get_owner().to_bytes() == pubkey {
836 return Ok(());
837 }
838 self.touch()?;
839 self.account.copy_into_owner_from_slice(pubkey);
840 Ok(())
841 }
842
843 #[inline]
845 pub fn get_lamports(&self) -> u64 {
846 self.account.lamports()
847 }
848
849 #[cfg(not(target_os = "solana"))]
851 pub fn set_lamports(&mut self, lamports: u64) -> Result<(), InstructionError> {
852 if !self.is_owned_by_current_program() && lamports < self.get_lamports() {
854 return Err(InstructionError::ExternalAccountLamportSpend);
855 }
856 if !self.is_writable() {
858 return Err(InstructionError::ReadonlyLamportChange);
859 }
860 if self.is_executable_internal() {
862 return Err(InstructionError::ExecutableLamportChange);
863 }
864 if self.get_lamports() == lamports {
866 return Ok(());
867 }
868 self.touch()?;
869 self.account.set_lamports(lamports);
870 Ok(())
871 }
872
873 #[cfg(not(target_os = "solana"))]
875 pub fn checked_add_lamports(&mut self, lamports: u64) -> Result<(), InstructionError> {
876 self.set_lamports(
877 self.get_lamports()
878 .checked_add(lamports)
879 .ok_or(InstructionError::ArithmeticOverflow)?,
880 )
881 }
882
883 #[cfg(not(target_os = "solana"))]
885 pub fn checked_sub_lamports(&mut self, lamports: u64) -> Result<(), InstructionError> {
886 self.set_lamports(
887 self.get_lamports()
888 .checked_sub(lamports)
889 .ok_or(InstructionError::ArithmeticOverflow)?,
890 )
891 }
892
893 #[inline]
895 pub fn get_data(&self) -> &[u8] {
896 self.account.data()
897 }
898
899 #[cfg(not(target_os = "solana"))]
901 pub fn get_data_mut(&mut self) -> Result<&mut [u8], InstructionError> {
902 self.can_data_be_changed()?;
903 self.touch()?;
904 self.make_data_mut();
905 Ok(self.account.data_as_mut_slice())
906 }
907
908 #[cfg(not(target_os = "solana"))]
913 pub fn spare_data_capacity_mut(&mut self) -> Result<&mut [MaybeUninit<u8>], InstructionError> {
914 debug_assert!(!self.account.is_shared());
915 Ok(self.account.spare_data_capacity_mut())
916 }
917
918 #[cfg(all(
924 not(target_os = "solana"),
925 any(test, feature = "dev-context-only-utils")
926 ))]
927 pub fn set_data(&mut self, data: Vec<u8>) -> Result<(), InstructionError> {
928 self.can_data_be_resized(data.len())?;
929 self.touch()?;
930
931 self.update_accounts_resize_delta(data.len())?;
932 self.account.set_data(data);
933 Ok(())
934 }
935
936 #[cfg(not(target_os = "solana"))]
941 pub fn set_data_from_slice(&mut self, data: &[u8]) -> Result<(), InstructionError> {
942 self.can_data_be_resized(data.len())?;
943 self.touch()?;
944 self.update_accounts_resize_delta(data.len())?;
945 self.account.set_data_from_slice(data);
950
951 Ok(())
952 }
953
954 #[cfg(not(target_os = "solana"))]
958 pub fn set_data_length(&mut self, new_length: usize) -> Result<(), InstructionError> {
959 self.can_data_be_resized(new_length)?;
960 if self.get_data().len() == new_length {
962 return Ok(());
963 }
964 self.touch()?;
965 self.update_accounts_resize_delta(new_length)?;
966 self.account.resize(new_length, 0);
967 Ok(())
968 }
969
970 #[cfg(not(target_os = "solana"))]
972 pub fn extend_from_slice(&mut self, data: &[u8]) -> Result<(), InstructionError> {
973 let new_len = self.get_data().len().saturating_add(data.len());
974 self.can_data_be_resized(new_len)?;
975
976 if data.is_empty() {
977 return Ok(());
978 }
979
980 self.touch()?;
981 self.update_accounts_resize_delta(new_len)?;
982 self.make_data_mut();
986 self.account.extend_from_slice(data);
987 Ok(())
988 }
989
990 #[cfg(not(target_os = "solana"))]
993 pub fn reserve(&mut self, additional: usize) -> Result<(), InstructionError> {
994 self.make_data_mut();
999 self.account.reserve(additional);
1000
1001 Ok(())
1002 }
1003
1004 #[cfg(not(target_os = "solana"))]
1006 pub fn capacity(&self) -> usize {
1007 self.account.capacity()
1008 }
1009
1010 #[cfg(not(target_os = "solana"))]
1018 pub fn is_shared(&self) -> bool {
1019 self.account.is_shared()
1020 }
1021
1022 #[cfg(not(target_os = "solana"))]
1023 fn make_data_mut(&mut self) {
1024 if self.account.is_shared() {
1033 self.account.reserve(MAX_PERMITTED_DATA_INCREASE);
1034 }
1035 }
1036
1037 #[cfg(all(not(target_os = "solana"), feature = "bincode"))]
1039 pub fn get_state<T: serde::de::DeserializeOwned>(&self) -> Result<T, InstructionError> {
1040 self.account
1041 .deserialize_data()
1042 .map_err(|_| InstructionError::InvalidAccountData)
1043 }
1044
1045 #[cfg(all(not(target_os = "solana"), feature = "bincode"))]
1047 pub fn set_state<T: serde::Serialize>(&mut self, state: &T) -> Result<(), InstructionError> {
1048 let data = self.get_data_mut()?;
1049 let serialized_size =
1050 bincode::serialized_size(state).map_err(|_| InstructionError::GenericError)?;
1051 if serialized_size > data.len() as u64 {
1052 return Err(InstructionError::AccountDataTooSmall);
1053 }
1054 bincode::serialize_into(&mut *data, state).map_err(|_| InstructionError::GenericError)?;
1055 Ok(())
1056 }
1057
1058 #[cfg(not(target_os = "solana"))]
1061 pub fn is_rent_exempt_at_data_length(&self, data_length: usize) -> bool {
1062 self.transaction_context
1063 .rent
1064 .is_exempt(self.get_lamports(), data_length)
1065 }
1066
1067 #[inline]
1069 #[deprecated(since = "2.1.0", note = "Use `get_owner` instead")]
1070 pub fn is_executable(&self) -> bool {
1071 self.account.executable()
1072 }
1073
1074 #[cfg(not(target_os = "solana"))]
1076 #[inline]
1077 fn is_executable_internal(&self) -> bool {
1078 !self
1079 .transaction_context
1080 .remove_accounts_executable_flag_checks
1081 && self.account.executable()
1082 }
1083
1084 #[cfg(not(target_os = "solana"))]
1086 pub fn set_executable(&mut self, is_executable: bool) -> Result<(), InstructionError> {
1087 if !self
1089 .transaction_context
1090 .rent
1091 .is_exempt(self.get_lamports(), self.get_data().len())
1092 {
1093 return Err(InstructionError::ExecutableAccountNotRentExempt);
1094 }
1095 if !self.is_owned_by_current_program() {
1097 return Err(InstructionError::ExecutableModified);
1098 }
1099 if !self.is_writable() {
1101 return Err(InstructionError::ExecutableModified);
1102 }
1103 if self.is_executable_internal() && !is_executable {
1105 return Err(InstructionError::ExecutableModified);
1106 }
1107 #[allow(deprecated)]
1109 if self.is_executable() == is_executable {
1110 return Ok(());
1111 }
1112 self.touch()?;
1113 self.account.set_executable(is_executable);
1114 Ok(())
1115 }
1116
1117 #[cfg(not(target_os = "solana"))]
1119 #[inline]
1120 pub fn get_rent_epoch(&self) -> u64 {
1121 self.account.rent_epoch()
1122 }
1123
1124 pub fn is_signer(&self) -> bool {
1126 if self.index_in_instruction < self.instruction_context.get_number_of_program_accounts() {
1127 return false;
1128 }
1129 self.instruction_context
1130 .is_instruction_account_signer(
1131 self.index_in_instruction
1132 .saturating_sub(self.instruction_context.get_number_of_program_accounts()),
1133 )
1134 .unwrap_or_default()
1135 }
1136
1137 pub fn is_writable(&self) -> bool {
1139 if self.index_in_instruction < self.instruction_context.get_number_of_program_accounts() {
1140 return false;
1141 }
1142 self.instruction_context
1143 .is_instruction_account_writable(
1144 self.index_in_instruction
1145 .saturating_sub(self.instruction_context.get_number_of_program_accounts()),
1146 )
1147 .unwrap_or_default()
1148 }
1149
1150 pub fn is_owned_by_current_program(&self) -> bool {
1152 self.instruction_context
1153 .get_last_program_key(self.transaction_context)
1154 .map(|key| key == self.get_owner())
1155 .unwrap_or_default()
1156 }
1157
1158 #[cfg(not(target_os = "solana"))]
1160 pub fn can_data_be_changed(&self) -> Result<(), InstructionError> {
1161 if self.is_executable_internal() {
1163 return Err(InstructionError::ExecutableDataModified);
1164 }
1165 if !self.is_writable() {
1167 return Err(InstructionError::ReadonlyDataModified);
1168 }
1169 if !self.is_owned_by_current_program() {
1171 return Err(InstructionError::ExternalAccountDataModified);
1172 }
1173 Ok(())
1174 }
1175
1176 #[cfg(not(target_os = "solana"))]
1178 pub fn can_data_be_resized(&self, new_len: usize) -> Result<(), InstructionError> {
1179 let old_len = self.get_data().len();
1180 if new_len != old_len && !self.is_owned_by_current_program() {
1182 return Err(InstructionError::AccountDataSizeChanged);
1183 }
1184 self.transaction_context
1185 .accounts
1186 .can_data_be_resized(old_len, new_len)?;
1187 self.can_data_be_changed()
1188 }
1189
1190 #[cfg(not(target_os = "solana"))]
1191 fn touch(&self) -> Result<(), InstructionError> {
1192 self.transaction_context
1193 .accounts
1194 .touch(self.index_in_transaction)
1195 }
1196
1197 #[cfg(not(target_os = "solana"))]
1198 fn update_accounts_resize_delta(&mut self, new_len: usize) -> Result<(), InstructionError> {
1199 self.transaction_context
1200 .accounts
1201 .update_accounts_resize_delta(self.get_data().len(), new_len)
1202 }
1203}
1204
1205#[cfg(not(target_os = "solana"))]
1207pub struct ExecutionRecord {
1208 pub accounts: Vec<TransactionAccount>,
1209 pub return_data: TransactionReturnData,
1210 pub touched_account_count: u64,
1211 pub accounts_resize_delta: i64,
1212}
1213
1214#[cfg(not(target_os = "solana"))]
1216impl From<TransactionContext> for ExecutionRecord {
1217 fn from(context: TransactionContext) -> Self {
1218 let TransactionAccounts {
1219 accounts,
1220 touched_flags,
1221 resize_delta,
1222 } = Rc::try_unwrap(context.accounts)
1223 .expect("transaction_context.accounts has unexpected outstanding refs");
1224 let accounts = Vec::from(Pin::into_inner(context.account_keys))
1225 .into_iter()
1226 .zip(accounts.into_iter().map(RefCell::into_inner))
1227 .collect();
1228 let touched_account_count = touched_flags
1229 .borrow()
1230 .iter()
1231 .fold(0usize, |accumulator, was_touched| {
1232 accumulator.saturating_add(*was_touched as usize)
1233 }) as u64;
1234 Self {
1235 accounts,
1236 return_data: context.return_data,
1237 touched_account_count,
1238 accounts_resize_delta: RefCell::into_inner(resize_delta),
1239 }
1240 }
1241}
1242
1243#[cfg(not(target_os = "solana"))]
1244fn is_zeroed(buf: &[u8]) -> bool {
1245 const ZEROS_LEN: usize = 1024;
1246 const ZEROS: [u8; ZEROS_LEN] = [0; ZEROS_LEN];
1247 let mut chunks = buf.chunks_exact(ZEROS_LEN);
1248
1249 #[allow(clippy::indexing_slicing)]
1250 {
1251 chunks.all(|chunk| chunk == &ZEROS[..])
1252 && chunks.remainder() == &ZEROS[..chunks.remainder().len()]
1253 }
1254}
1255
1256#[cfg(test)]
1257mod tests {
1258 use super::*;
1259
1260 #[test]
1261 fn test_instructions_sysvar_store_index_checked() {
1262 let build_transaction_context = |account: AccountSharedData| {
1263 TransactionContext::new(
1264 vec![
1265 (Pubkey::new_unique(), AccountSharedData::default()),
1266 (instructions::id(), account),
1267 ],
1268 Rent::default(),
1269 2,
1270 2,
1271 )
1272 };
1273
1274 let correct_space = 2;
1275 let rent_exempt_lamports = Rent::default().minimum_balance(correct_space);
1276
1277 let account =
1279 AccountSharedData::new(rent_exempt_lamports, correct_space, &Pubkey::new_unique());
1280 assert_eq!(
1281 build_transaction_context(account).push(),
1282 Err(InstructionError::InvalidAccountOwner),
1283 );
1284
1285 let account =
1287 AccountSharedData::new(rent_exempt_lamports, 0, &solana_sdk_ids::sysvar::id());
1288 assert_eq!(
1289 build_transaction_context(account).push(),
1290 Err(InstructionError::AccountDataTooSmall),
1291 );
1292
1293 let account = AccountSharedData::new(
1295 rent_exempt_lamports,
1296 correct_space,
1297 &solana_sdk_ids::sysvar::id(),
1298 );
1299 assert_eq!(build_transaction_context(account).push(), Ok(()),);
1300 }
1301}