1#![cfg_attr(
2 not(feature = "agave-unstable-api"),
3 deprecated(
4 since = "3.1.0",
5 note = "This crate has been marked for formal inclusion in the Agave Unstable API. From \
6 v4.0.0 onward, the `agave-unstable-api` crate feature must be specified to \
7 acknowledge use of an interface that may break without warning."
8 )
9)]
10#![deny(clippy::indexing_slicing)]
12#![cfg_attr(docsrs, feature(doc_auto_cfg))]
13
14use {
15 crate::transaction_accounts::{AccountRefMut, KeyedAccountSharedData, TransactionAccounts},
16 solana_account::{AccountSharedData, ReadableAccount},
17 solana_instruction::error::InstructionError,
18 solana_instructions_sysvar as instructions,
19 solana_pubkey::Pubkey,
20 solana_sbpf::memory_region::{AccessType, AccessViolationHandler, MemoryRegion},
21 std::{borrow::Cow, cell::Cell, collections::HashSet, rc::Rc},
22};
23#[cfg(not(target_os = "solana"))]
24use {solana_account::WritableAccount, solana_rent::Rent};
25
26pub mod transaction_accounts;
27pub mod vm_slice;
28
29pub const MAX_ACCOUNTS_PER_TRANSACTION: usize = 256;
30pub const MAX_ACCOUNTS_PER_INSTRUCTION: usize = 255;
33pub const MAX_INSTRUCTION_DATA_LEN: usize = 10 * 1024;
34pub const MAX_ACCOUNT_DATA_LEN: u64 = 10 * 1024 * 1024;
35pub const MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION: i64 = MAX_ACCOUNT_DATA_LEN as i64 * 2;
39pub const MAX_ACCOUNT_DATA_GROWTH_PER_INSTRUCTION: usize = 10 * 1_024;
40pub const MAX_INSTRUCTION_TRACE_LENGTH: usize = 64;
42
43#[cfg(test)]
44static_assertions::const_assert_eq!(
45 MAX_ACCOUNTS_PER_INSTRUCTION,
46 solana_program_entrypoint::NON_DUP_MARKER as usize,
47);
48#[cfg(test)]
49static_assertions::const_assert_eq!(
50 MAX_ACCOUNT_DATA_LEN,
51 solana_system_interface::MAX_PERMITTED_DATA_LENGTH,
52);
53#[cfg(test)]
54static_assertions::const_assert_eq!(
55 MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION,
56 solana_system_interface::MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION,
57);
58#[cfg(test)]
59static_assertions::const_assert_eq!(
60 MAX_ACCOUNT_DATA_GROWTH_PER_INSTRUCTION,
61 solana_account_info::MAX_PERMITTED_DATA_INCREASE,
62);
63
64pub type IndexOfAccount = u16;
66
67#[repr(C)]
74#[derive(Clone, Copy, Debug)]
75pub struct InstructionAccount {
76 pub index_in_transaction: IndexOfAccount,
78 is_signer: u8,
80 is_writable: u8,
82}
83
84impl InstructionAccount {
85 pub fn new(
86 index_in_transaction: IndexOfAccount,
87 is_signer: bool,
88 is_writable: bool,
89 ) -> InstructionAccount {
90 InstructionAccount {
91 index_in_transaction,
92 is_signer: is_signer as u8,
93 is_writable: is_writable as u8,
94 }
95 }
96
97 pub fn is_signer(&self) -> bool {
98 self.is_signer != 0
99 }
100
101 pub fn is_writable(&self) -> bool {
102 self.is_writable != 0
103 }
104
105 pub fn set_is_signer(&mut self, value: bool) {
106 self.is_signer = value as u8;
107 }
108
109 pub fn set_is_writable(&mut self, value: bool) {
110 self.is_writable = value as u8;
111 }
112}
113
114#[derive(Debug)]
118pub struct TransactionContext<'ix_data> {
119 accounts: Rc<TransactionAccounts>,
120 instruction_stack_capacity: usize,
121 instruction_trace_capacity: usize,
122 instruction_stack: Vec<usize>,
123 instruction_trace: Vec<InstructionFrame<'ix_data>>,
124 top_level_instruction_index: usize,
125 return_data: TransactionReturnData,
126 #[cfg(not(target_os = "solana"))]
127 rent: Rent,
128}
129
130impl<'ix_data> TransactionContext<'ix_data> {
131 #[cfg(not(target_os = "solana"))]
133 pub fn new(
134 transaction_accounts: Vec<KeyedAccountSharedData>,
135 rent: Rent,
136 instruction_stack_capacity: usize,
137 instruction_trace_capacity: usize,
138 ) -> Self {
139 Self {
140 accounts: Rc::new(TransactionAccounts::new(transaction_accounts)),
141 instruction_stack_capacity,
142 instruction_trace_capacity,
143 instruction_stack: Vec::with_capacity(instruction_stack_capacity),
144 instruction_trace: vec![InstructionFrame::default()],
145 top_level_instruction_index: 0,
146 return_data: TransactionReturnData::default(),
147 rent,
148 }
149 }
150
151 #[cfg(feature = "dev-context-only-utils")]
152 pub fn set_top_level_instruction_index(&mut self, top_level_instruction_index: usize) {
153 self.top_level_instruction_index = top_level_instruction_index;
154 }
155
156 #[cfg(not(target_os = "solana"))]
158 pub fn deconstruct_without_keys(self) -> Result<Vec<AccountSharedData>, InstructionError> {
159 if !self.instruction_stack.is_empty() {
160 return Err(InstructionError::CallDepth);
161 }
162
163 let accounts = Rc::try_unwrap(self.accounts)
164 .expect("transaction_context.accounts has unexpected outstanding refs")
165 .deconstruct_into_account_shared_data();
166
167 Ok(accounts)
168 }
169
170 #[cfg(not(target_os = "solana"))]
171 pub fn accounts(&self) -> &Rc<TransactionAccounts> {
172 &self.accounts
173 }
174
175 pub fn get_number_of_accounts(&self) -> IndexOfAccount {
177 self.accounts.len() as IndexOfAccount
178 }
179
180 pub fn get_key_of_account_at_index(
182 &self,
183 index_in_transaction: IndexOfAccount,
184 ) -> Result<&Pubkey, InstructionError> {
185 self.accounts
186 .account_key(index_in_transaction)
187 .ok_or(InstructionError::MissingAccount)
188 }
189
190 pub fn find_index_of_account(&self, pubkey: &Pubkey) -> Option<IndexOfAccount> {
192 self.accounts
193 .account_keys_iter()
194 .position(|key| key == pubkey)
195 .map(|index| index as IndexOfAccount)
196 }
197
198 pub fn get_instruction_trace_capacity(&self) -> usize {
200 self.instruction_trace_capacity
201 }
202
203 pub fn get_instruction_trace_length(&self) -> usize {
208 self.instruction_trace.len().saturating_sub(1)
209 }
210
211 pub fn get_instruction_context_at_index_in_trace(
213 &self,
214 index_in_trace: usize,
215 ) -> Result<InstructionContext<'_, '_>, InstructionError> {
216 let instruction = self
217 .instruction_trace
218 .get(index_in_trace)
219 .ok_or(InstructionError::CallDepth)?;
220 Ok(InstructionContext {
221 transaction_context: self,
222 index_in_trace,
223 nesting_level: instruction.nesting_level,
224 program_account_index_in_tx: instruction.program_account_index_in_tx,
225 instruction_accounts: &instruction.instruction_accounts,
226 dedup_map: &instruction.dedup_map,
227 instruction_data: &instruction.instruction_data,
228 })
229 }
230
231 pub fn get_instruction_context_at_nesting_level(
233 &self,
234 nesting_level: usize,
235 ) -> Result<InstructionContext<'_, '_>, InstructionError> {
236 let index_in_trace = *self
237 .instruction_stack
238 .get(nesting_level)
239 .ok_or(InstructionError::CallDepth)?;
240 let instruction_context = self.get_instruction_context_at_index_in_trace(index_in_trace)?;
241 debug_assert_eq!(instruction_context.nesting_level, nesting_level);
242 Ok(instruction_context)
243 }
244
245 pub fn get_instruction_stack_capacity(&self) -> usize {
247 self.instruction_stack_capacity
248 }
249
250 pub fn get_instruction_stack_height(&self) -> usize {
253 self.instruction_stack.len()
254 }
255
256 pub fn get_current_instruction_context(
258 &self,
259 ) -> Result<InstructionContext<'_, '_>, InstructionError> {
260 let level = self
261 .get_instruction_stack_height()
262 .checked_sub(1)
263 .ok_or(InstructionError::CallDepth)?;
264 self.get_instruction_context_at_nesting_level(level)
265 }
266
267 pub fn get_next_instruction_context(
271 &self,
272 ) -> Result<InstructionContext<'_, '_>, InstructionError> {
273 let index_in_trace = self
274 .instruction_trace
275 .len()
276 .checked_sub(1)
277 .ok_or(InstructionError::CallDepth)?;
278 self.get_instruction_context_at_index_in_trace(index_in_trace)
279 }
280
281 pub fn configure_next_instruction(
285 &mut self,
286 program_index: IndexOfAccount,
287 instruction_accounts: Vec<InstructionAccount>,
288 deduplication_map: Vec<u16>,
289 instruction_data: Cow<'ix_data, [u8]>,
290 ) -> Result<(), InstructionError> {
291 debug_assert_eq!(deduplication_map.len(), MAX_ACCOUNTS_PER_TRANSACTION);
292 let instruction = self
293 .instruction_trace
294 .last_mut()
295 .ok_or(InstructionError::CallDepth)?;
296 instruction.program_account_index_in_tx = program_index;
297 instruction.instruction_accounts = instruction_accounts;
298 instruction.instruction_data = instruction_data;
299 instruction.dedup_map = deduplication_map;
300 Ok(())
301 }
302
303 pub fn configure_next_instruction_for_tests(
305 &mut self,
306 program_index: IndexOfAccount,
307 instruction_accounts: Vec<InstructionAccount>,
308 instruction_data: Vec<u8>,
309 ) -> Result<(), InstructionError> {
310 debug_assert!(instruction_accounts.len() <= u16::MAX as usize);
311 let mut dedup_map = vec![u16::MAX; MAX_ACCOUNTS_PER_TRANSACTION];
312 for (idx, account) in instruction_accounts.iter().enumerate() {
313 let index_in_instruction = dedup_map
314 .get_mut(account.index_in_transaction as usize)
315 .unwrap();
316 if *index_in_instruction == u16::MAX {
317 *index_in_instruction = idx as u16;
318 }
319 }
320 self.configure_next_instruction(
321 program_index,
322 instruction_accounts,
323 dedup_map,
324 Cow::Owned(instruction_data),
325 )
326 }
327
328 #[cfg(not(target_os = "solana"))]
330 pub fn push(&mut self) -> Result<(), InstructionError> {
331 let nesting_level = self.get_instruction_stack_height();
332 if !self.instruction_stack.is_empty() && self.accounts.get_lamports_delta() != 0 {
333 return Err(InstructionError::UnbalancedInstruction);
334 }
335 {
336 let instruction = self
337 .instruction_trace
338 .last_mut()
339 .ok_or(InstructionError::CallDepth)?;
340 instruction.nesting_level = nesting_level;
341 }
342 let index_in_trace = self.get_instruction_trace_length();
343 if index_in_trace >= self.instruction_trace_capacity {
344 return Err(InstructionError::MaxInstructionTraceLengthExceeded);
345 }
346 self.instruction_trace.push(InstructionFrame::default());
347 if nesting_level >= self.instruction_stack_capacity {
348 return Err(InstructionError::CallDepth);
349 }
350 self.instruction_stack.push(index_in_trace);
351 if let Some(index_in_transaction) = self.find_index_of_account(&instructions::id()) {
352 let mut mut_account_ref = self.accounts.try_borrow_mut(index_in_transaction)?;
353 if mut_account_ref.owner() != &solana_sdk_ids::sysvar::id() {
354 return Err(InstructionError::InvalidAccountOwner);
355 }
356 instructions::store_current_index_checked(
357 mut_account_ref.data_as_mut_slice(),
358 self.top_level_instruction_index as u16,
359 )?;
360 }
361 Ok(())
362 }
363
364 #[cfg(not(target_os = "solana"))]
366 pub fn pop(&mut self) -> Result<(), InstructionError> {
367 if self.instruction_stack.is_empty() {
368 return Err(InstructionError::CallDepth);
369 }
370 let detected_an_unbalanced_instruction =
372 self.get_current_instruction_context()
373 .and_then(|instruction_context| {
374 self.accounts
376 .try_borrow_mut(
377 instruction_context.get_index_of_program_account_in_transaction()?,
378 )
379 .map_err(|err| {
380 if err == InstructionError::AccountBorrowFailed {
381 InstructionError::AccountBorrowOutstanding
382 } else {
383 err
384 }
385 })?;
386 Ok(self.accounts.get_lamports_delta() != 0)
387 });
388 self.instruction_stack.pop();
390 if self.instruction_stack.is_empty() {
391 self.top_level_instruction_index = self.top_level_instruction_index.saturating_add(1);
392 }
393 if detected_an_unbalanced_instruction? {
394 Err(InstructionError::UnbalancedInstruction)
395 } else {
396 Ok(())
397 }
398 }
399
400 pub fn get_return_data(&self) -> (&Pubkey, &[u8]) {
402 (&self.return_data.program_id, &self.return_data.data)
403 }
404
405 pub fn set_return_data(
407 &mut self,
408 program_id: Pubkey,
409 data: Vec<u8>,
410 ) -> Result<(), InstructionError> {
411 self.return_data = TransactionReturnData { program_id, data };
412 Ok(())
413 }
414
415 pub fn access_violation_handler(
417 &self,
418 stricter_abi_and_runtime_constraints: bool,
419 account_data_direct_mapping: bool,
420 ) -> AccessViolationHandler {
421 let accounts = Rc::clone(&self.accounts);
422 Box::new(
423 move |region: &mut MemoryRegion,
424 address_space_reserved_for_account: u64,
425 access_type: AccessType,
426 vm_addr: u64,
427 len: u64| {
428 if access_type == AccessType::Load {
429 return;
430 }
431 let Some(index_in_transaction) = region.access_violation_handler_payload else {
432 return;
434 };
435 let requested_length =
436 vm_addr.saturating_add(len).saturating_sub(region.vm_addr) as usize;
437 if requested_length > address_space_reserved_for_account as usize {
438 return;
440 }
441
442 let Ok(mut account) = accounts.try_borrow_mut(index_in_transaction) else {
446 debug_assert!(false);
447 return;
448 };
449 if accounts.touch(index_in_transaction).is_err() {
450 debug_assert!(false);
451 return;
452 }
453
454 let remaining_allowed_growth = MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION
455 .saturating_sub(accounts.resize_delta())
456 .max(0) as usize;
457
458 if requested_length > region.len as usize {
459 let old_len = account.data().len();
463 let new_len = (address_space_reserved_for_account as usize)
464 .min(MAX_ACCOUNT_DATA_LEN as usize)
465 .min(old_len.saturating_add(remaining_allowed_growth));
466 debug_assert!(accounts.can_data_be_resized(old_len, new_len).is_ok());
468 if accounts
469 .update_accounts_resize_delta(old_len, new_len)
470 .is_err()
471 {
472 return;
473 }
474 account.resize(new_len, 0);
475 region.len = new_len as u64;
476 }
477
478 if stricter_abi_and_runtime_constraints && account_data_direct_mapping {
480 region.host_addr = account.data_as_mut_slice().as_mut_ptr() as u64;
481 region.writable = true;
482 }
483 },
484 )
485 }
486
487 pub fn take_instruction_trace(&mut self) -> Vec<InstructionFrame<'_>> {
489 self.instruction_trace.pop();
492 std::mem::take(&mut self.instruction_trace)
493 }
494}
495
496#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
498#[derive(Clone, Debug, Default, PartialEq, Eq)]
499pub struct TransactionReturnData {
500 pub program_id: Pubkey,
501 pub data: Vec<u8>,
502}
503
504#[derive(Debug, Clone, Default)]
506pub struct InstructionFrame<'ix_data> {
507 pub nesting_level: usize,
508 pub program_account_index_in_tx: IndexOfAccount,
509 pub instruction_accounts: Vec<InstructionAccount>,
510 dedup_map: Vec<u16>,
514 pub instruction_data: Cow<'ix_data, [u8]>,
515}
516
517#[derive(Debug, Clone)]
519pub struct InstructionContext<'a, 'ix_data> {
520 transaction_context: &'a TransactionContext<'ix_data>,
521 index_in_trace: usize,
523 nesting_level: usize,
524 program_account_index_in_tx: IndexOfAccount,
525 instruction_accounts: &'a [InstructionAccount],
526 dedup_map: &'a [u16],
527 instruction_data: &'ix_data [u8],
528}
529
530impl<'a> InstructionContext<'a, '_> {
531 pub fn get_index_in_trace(&self) -> usize {
533 self.index_in_trace
534 }
535
536 pub fn get_stack_height(&self) -> usize {
540 self.nesting_level.saturating_add(1)
541 }
542
543 pub fn get_number_of_instruction_accounts(&self) -> IndexOfAccount {
545 self.instruction_accounts.len() as IndexOfAccount
546 }
547
548 pub fn check_number_of_instruction_accounts(
550 &self,
551 expected_at_least: IndexOfAccount,
552 ) -> Result<(), InstructionError> {
553 if self.get_number_of_instruction_accounts() < expected_at_least {
554 Err(InstructionError::MissingAccount)
555 } else {
556 Ok(())
557 }
558 }
559
560 pub fn get_instruction_data(&self) -> &[u8] {
562 self.instruction_data
563 }
564
565 pub fn get_index_of_program_account_in_transaction(
567 &self,
568 ) -> Result<IndexOfAccount, InstructionError> {
569 if self.program_account_index_in_tx == u16::MAX {
570 Err(InstructionError::MissingAccount)
571 } else {
572 Ok(self.program_account_index_in_tx)
573 }
574 }
575
576 pub fn get_index_of_instruction_account_in_transaction(
578 &self,
579 instruction_account_index: IndexOfAccount,
580 ) -> Result<IndexOfAccount, InstructionError> {
581 Ok(self
582 .instruction_accounts
583 .get(instruction_account_index as usize)
584 .ok_or(InstructionError::MissingAccount)?
585 .index_in_transaction as IndexOfAccount)
586 }
587
588 pub fn get_index_of_account_in_instruction(
590 &self,
591 index_in_transaction: IndexOfAccount,
592 ) -> Result<IndexOfAccount, InstructionError> {
593 self.dedup_map
594 .get(index_in_transaction as usize)
595 .and_then(|idx| {
596 if *idx as usize >= self.instruction_accounts.len() {
597 None
598 } else {
599 Some(*idx as IndexOfAccount)
600 }
601 })
602 .ok_or(InstructionError::MissingAccount)
603 }
604
605 pub fn is_instruction_account_duplicate(
608 &self,
609 instruction_account_index: IndexOfAccount,
610 ) -> Result<Option<IndexOfAccount>, InstructionError> {
611 let index_in_transaction =
612 self.get_index_of_instruction_account_in_transaction(instruction_account_index)?;
613 let first_instruction_account_index =
614 self.get_index_of_account_in_instruction(index_in_transaction)?;
615
616 Ok(
617 if first_instruction_account_index == instruction_account_index {
618 None
619 } else {
620 Some(first_instruction_account_index)
621 },
622 )
623 }
624
625 pub fn get_program_key(&self) -> Result<&'a Pubkey, InstructionError> {
627 self.get_index_of_program_account_in_transaction()
628 .and_then(|index_in_transaction| {
629 self.transaction_context
630 .get_key_of_account_at_index(index_in_transaction)
631 })
632 }
633
634 pub fn get_program_owner(&self) -> Result<Pubkey, InstructionError> {
636 self.get_index_of_program_account_in_transaction()
637 .and_then(|index_in_transaction| {
638 self.transaction_context
639 .accounts
640 .try_borrow(index_in_transaction)
641 })
642 .map(|acc| *acc.owner())
643 }
644
645 pub fn try_borrow_instruction_account(
647 &self,
648 index_in_instruction: IndexOfAccount,
649 ) -> Result<BorrowedInstructionAccount<'_, '_>, InstructionError> {
650 let instruction_account = *self
651 .instruction_accounts
652 .get(index_in_instruction as usize)
653 .ok_or(InstructionError::MissingAccount)?;
654
655 let account = self
656 .transaction_context
657 .accounts
658 .try_borrow_mut(instruction_account.index_in_transaction)?;
659
660 Ok(BorrowedInstructionAccount {
661 transaction_context: self.transaction_context,
662 instruction_account,
663 account,
664 index_in_transaction_of_instruction_program: self.program_account_index_in_tx,
665 })
666 }
667
668 pub fn is_instruction_account_signer(
670 &self,
671 instruction_account_index: IndexOfAccount,
672 ) -> Result<bool, InstructionError> {
673 Ok(self
674 .instruction_accounts
675 .get(instruction_account_index as usize)
676 .ok_or(InstructionError::MissingAccount)?
677 .is_signer())
678 }
679
680 pub fn is_instruction_account_writable(
682 &self,
683 instruction_account_index: IndexOfAccount,
684 ) -> Result<bool, InstructionError> {
685 Ok(self
686 .instruction_accounts
687 .get(instruction_account_index as usize)
688 .ok_or(InstructionError::MissingAccount)?
689 .is_writable())
690 }
691
692 pub fn get_signers(&self) -> Result<HashSet<Pubkey>, InstructionError> {
694 let mut result = HashSet::new();
695 for instruction_account in self.instruction_accounts.iter() {
696 if instruction_account.is_signer() {
697 result.insert(
698 *self
699 .transaction_context
700 .get_key_of_account_at_index(instruction_account.index_in_transaction)?,
701 );
702 }
703 }
704 Ok(result)
705 }
706
707 pub fn instruction_accounts(&self) -> &[InstructionAccount] {
708 self.instruction_accounts
709 }
710
711 pub fn get_key_of_instruction_account(
712 &self,
713 index_in_instruction: IndexOfAccount,
714 ) -> Result<&'a Pubkey, InstructionError> {
715 self.get_index_of_instruction_account_in_transaction(index_in_instruction)
716 .and_then(|idx| self.transaction_context.get_key_of_account_at_index(idx))
717 }
718}
719
720#[derive(Debug)]
722pub struct BorrowedInstructionAccount<'a, 'ix_data> {
723 transaction_context: &'a TransactionContext<'ix_data>,
724 account: AccountRefMut<'a>,
725 instruction_account: InstructionAccount,
726 index_in_transaction_of_instruction_program: IndexOfAccount,
727}
728
729impl BorrowedInstructionAccount<'_, '_> {
730 #[inline]
732 pub fn get_index_in_transaction(&self) -> IndexOfAccount {
733 self.instruction_account.index_in_transaction
734 }
735
736 #[inline]
738 pub fn get_key(&self) -> &Pubkey {
739 self.transaction_context
740 .get_key_of_account_at_index(self.instruction_account.index_in_transaction)
741 .unwrap()
742 }
743
744 #[inline]
746 pub fn get_owner(&self) -> &Pubkey {
747 self.account.owner()
748 }
749
750 #[cfg(not(target_os = "solana"))]
752 pub fn set_owner(&mut self, pubkey: &[u8]) -> Result<(), InstructionError> {
753 if !self.is_owned_by_current_program() {
755 return Err(InstructionError::ModifiedProgramId);
756 }
757 if !self.is_writable() {
759 return Err(InstructionError::ModifiedProgramId);
760 }
761 if !is_zeroed(self.get_data()) {
763 return Err(InstructionError::ModifiedProgramId);
764 }
765 if self.get_owner().to_bytes() == pubkey {
767 return Ok(());
768 }
769 self.touch()?;
770 self.account.copy_into_owner_from_slice(pubkey);
771 Ok(())
772 }
773
774 #[inline]
776 pub fn get_lamports(&self) -> u64 {
777 self.account.lamports()
778 }
779
780 #[cfg(not(target_os = "solana"))]
782 pub fn set_lamports(&mut self, lamports: u64) -> Result<(), InstructionError> {
783 if !self.is_owned_by_current_program() && lamports < self.get_lamports() {
785 return Err(InstructionError::ExternalAccountLamportSpend);
786 }
787 if !self.is_writable() {
789 return Err(InstructionError::ReadonlyLamportChange);
790 }
791 let old_lamports = self.get_lamports();
793 if old_lamports == lamports {
794 return Ok(());
795 }
796
797 let lamports_balance = (lamports as i128).saturating_sub(old_lamports as i128);
798 self.transaction_context
799 .accounts
800 .add_lamports_delta(lamports_balance)?;
801
802 self.touch()?;
803 self.account.set_lamports(lamports);
804 Ok(())
805 }
806
807 #[cfg(not(target_os = "solana"))]
809 pub fn checked_add_lamports(&mut self, lamports: u64) -> Result<(), InstructionError> {
810 self.set_lamports(
811 self.get_lamports()
812 .checked_add(lamports)
813 .ok_or(InstructionError::ArithmeticOverflow)?,
814 )
815 }
816
817 #[cfg(not(target_os = "solana"))]
819 pub fn checked_sub_lamports(&mut self, lamports: u64) -> Result<(), InstructionError> {
820 self.set_lamports(
821 self.get_lamports()
822 .checked_sub(lamports)
823 .ok_or(InstructionError::ArithmeticOverflow)?,
824 )
825 }
826
827 #[inline]
829 pub fn get_data(&self) -> &[u8] {
830 self.account.data()
831 }
832
833 #[cfg(not(target_os = "solana"))]
835 pub fn get_data_mut(&mut self) -> Result<&mut [u8], InstructionError> {
836 self.can_data_be_changed()?;
837 self.touch()?;
838 self.make_data_mut();
839 Ok(self.account.data_as_mut_slice())
840 }
841
842 #[cfg(not(target_os = "solana"))]
847 pub fn set_data_from_slice(&mut self, data: &[u8]) -> Result<(), InstructionError> {
848 self.can_data_be_resized(data.len())?;
849 self.touch()?;
850 self.update_accounts_resize_delta(data.len())?;
851 self.account.set_data_from_slice(data);
856
857 Ok(())
858 }
859
860 #[cfg(not(target_os = "solana"))]
864 pub fn set_data_length(&mut self, new_length: usize) -> Result<(), InstructionError> {
865 self.can_data_be_resized(new_length)?;
866 if self.get_data().len() == new_length {
868 return Ok(());
869 }
870 self.touch()?;
871 self.update_accounts_resize_delta(new_length)?;
872 self.account.resize(new_length, 0);
873 Ok(())
874 }
875
876 #[cfg(not(target_os = "solana"))]
878 pub fn extend_from_slice(&mut self, data: &[u8]) -> Result<(), InstructionError> {
879 let new_len = self.get_data().len().saturating_add(data.len());
880 self.can_data_be_resized(new_len)?;
881
882 if data.is_empty() {
883 return Ok(());
884 }
885
886 self.touch()?;
887 self.update_accounts_resize_delta(new_len)?;
888 self.make_data_mut();
892 self.account.extend_from_slice(data);
893 Ok(())
894 }
895
896 #[cfg(not(target_os = "solana"))]
904 pub fn is_shared(&self) -> bool {
905 self.account.is_shared()
906 }
907
908 #[cfg(not(target_os = "solana"))]
909 fn make_data_mut(&mut self) {
910 if self.account.is_shared() {
916 self.account
917 .reserve(MAX_ACCOUNT_DATA_GROWTH_PER_INSTRUCTION);
918 }
919 }
920
921 #[cfg(all(not(target_os = "solana"), feature = "bincode"))]
923 pub fn get_state<T: serde::de::DeserializeOwned>(&self) -> Result<T, InstructionError> {
924 bincode::deserialize(self.account.data()).map_err(|_| InstructionError::InvalidAccountData)
925 }
926
927 #[cfg(all(not(target_os = "solana"), feature = "bincode"))]
929 pub fn set_state<T: serde::Serialize>(&mut self, state: &T) -> Result<(), InstructionError> {
930 let data = self.get_data_mut()?;
931 let serialized_size =
932 bincode::serialized_size(state).map_err(|_| InstructionError::GenericError)?;
933 if serialized_size > data.len() as u64 {
934 return Err(InstructionError::AccountDataTooSmall);
935 }
936 bincode::serialize_into(&mut *data, state).map_err(|_| InstructionError::GenericError)?;
937 Ok(())
938 }
939
940 #[cfg(not(target_os = "solana"))]
943 pub fn is_rent_exempt_at_data_length(&self, data_length: usize) -> bool {
944 self.transaction_context
945 .rent
946 .is_exempt(self.get_lamports(), data_length)
947 }
948
949 #[inline]
951 #[deprecated(since = "2.1.0", note = "Use `get_owner` instead")]
952 pub fn is_executable(&self) -> bool {
953 #[allow(deprecated)]
954 self.account.executable()
955 }
956
957 #[cfg(not(target_os = "solana"))]
959 pub fn set_executable(&mut self, is_executable: bool) -> Result<(), InstructionError> {
960 if !self
962 .transaction_context
963 .rent
964 .is_exempt(self.get_lamports(), self.get_data().len())
965 {
966 return Err(InstructionError::ExecutableAccountNotRentExempt);
967 }
968 if !self.is_owned_by_current_program() {
970 return Err(InstructionError::ExecutableModified);
971 }
972 if !self.is_writable() {
974 return Err(InstructionError::ExecutableModified);
975 }
976 #[allow(deprecated)]
978 if self.is_executable() == is_executable {
979 return Ok(());
980 }
981 self.touch()?;
982 self.account.set_executable(is_executable);
983 Ok(())
984 }
985
986 #[cfg(not(target_os = "solana"))]
988 #[inline]
989 pub fn get_rent_epoch(&self) -> u64 {
990 self.account.rent_epoch()
991 }
992
993 pub fn is_signer(&self) -> bool {
995 self.instruction_account.is_signer()
996 }
997
998 pub fn is_writable(&self) -> bool {
1000 self.instruction_account.is_writable()
1001 }
1002
1003 pub fn is_owned_by_current_program(&self) -> bool {
1005 self.transaction_context
1006 .get_key_of_account_at_index(self.index_in_transaction_of_instruction_program)
1007 .map(|program_key| program_key == self.get_owner())
1008 .unwrap_or_default()
1009 }
1010
1011 #[cfg(not(target_os = "solana"))]
1013 pub fn can_data_be_changed(&self) -> Result<(), InstructionError> {
1014 if !self.is_writable() {
1016 return Err(InstructionError::ReadonlyDataModified);
1017 }
1018 if !self.is_owned_by_current_program() {
1020 return Err(InstructionError::ExternalAccountDataModified);
1021 }
1022 Ok(())
1023 }
1024
1025 #[cfg(not(target_os = "solana"))]
1027 pub fn can_data_be_resized(&self, new_len: usize) -> Result<(), InstructionError> {
1028 let old_len = self.get_data().len();
1029 if new_len != old_len && !self.is_owned_by_current_program() {
1031 return Err(InstructionError::AccountDataSizeChanged);
1032 }
1033 self.transaction_context
1034 .accounts
1035 .can_data_be_resized(old_len, new_len)?;
1036 self.can_data_be_changed()
1037 }
1038
1039 #[cfg(not(target_os = "solana"))]
1040 fn touch(&self) -> Result<(), InstructionError> {
1041 self.transaction_context
1042 .accounts
1043 .touch(self.instruction_account.index_in_transaction)
1044 }
1045
1046 #[cfg(not(target_os = "solana"))]
1047 fn update_accounts_resize_delta(&mut self, new_len: usize) -> Result<(), InstructionError> {
1048 self.transaction_context
1049 .accounts
1050 .update_accounts_resize_delta(self.get_data().len(), new_len)
1051 }
1052}
1053
1054#[cfg(not(target_os = "solana"))]
1056pub struct ExecutionRecord {
1057 pub accounts: Vec<KeyedAccountSharedData>,
1058 pub return_data: TransactionReturnData,
1059 pub touched_account_count: u64,
1060 pub accounts_resize_delta: i64,
1061}
1062
1063#[cfg(not(target_os = "solana"))]
1065impl From<TransactionContext<'_>> for ExecutionRecord {
1066 fn from(context: TransactionContext) -> Self {
1067 let (accounts, touched_flags, resize_delta) = Rc::try_unwrap(context.accounts)
1068 .expect("transaction_context.accounts has unexpected outstanding refs")
1069 .take();
1070 let touched_account_count = touched_flags
1071 .iter()
1072 .fold(0usize, |accumulator, was_touched| {
1073 accumulator.saturating_add(was_touched.get() as usize)
1074 }) as u64;
1075 Self {
1076 accounts,
1077 return_data: context.return_data,
1078 touched_account_count,
1079 accounts_resize_delta: Cell::into_inner(resize_delta),
1080 }
1081 }
1082}
1083
1084#[cfg(not(target_os = "solana"))]
1085fn is_zeroed(buf: &[u8]) -> bool {
1086 const ZEROS_LEN: usize = 1024;
1087 const ZEROS: [u8; ZEROS_LEN] = [0; ZEROS_LEN];
1088 let mut chunks = buf.chunks_exact(ZEROS_LEN);
1089
1090 #[allow(clippy::indexing_slicing)]
1091 {
1092 chunks.all(|chunk| chunk == &ZEROS[..])
1093 && chunks.remainder() == &ZEROS[..chunks.remainder().len()]
1094 }
1095}
1096
1097#[cfg(test)]
1098mod tests {
1099 use super::*;
1100
1101 #[test]
1102 fn test_instructions_sysvar_store_index_checked() {
1103 let build_transaction_context = |account: AccountSharedData| {
1104 TransactionContext::new(
1105 vec![
1106 (Pubkey::new_unique(), AccountSharedData::default()),
1107 (instructions::id(), account),
1108 ],
1109 Rent::default(),
1110 2,
1111 2,
1112 )
1113 };
1114
1115 let correct_space = 2;
1116 let rent_exempt_lamports = Rent::default().minimum_balance(correct_space);
1117
1118 let account =
1120 AccountSharedData::new(rent_exempt_lamports, correct_space, &Pubkey::new_unique());
1121 assert_eq!(
1122 build_transaction_context(account).push(),
1123 Err(InstructionError::InvalidAccountOwner),
1124 );
1125
1126 let account =
1128 AccountSharedData::new(rent_exempt_lamports, 0, &solana_sdk_ids::sysvar::id());
1129 assert_eq!(
1130 build_transaction_context(account).push(),
1131 Err(InstructionError::AccountDataTooSmall),
1132 );
1133
1134 let account = AccountSharedData::new(
1136 rent_exempt_lamports,
1137 correct_space,
1138 &solana_sdk_ids::sysvar::id(),
1139 );
1140 assert_eq!(build_transaction_context(account).push(), Ok(()),);
1141 }
1142
1143 #[test]
1144 fn test_invalid_native_loader_index() {
1145 let mut transaction_context = TransactionContext::new(
1146 vec![(
1147 Pubkey::new_unique(),
1148 AccountSharedData::new(1, 1, &Pubkey::new_unique()),
1149 )],
1150 Rent::default(),
1151 20,
1152 20,
1153 );
1154
1155 transaction_context
1156 .configure_next_instruction_for_tests(
1157 u16::MAX,
1158 vec![InstructionAccount::new(0, false, false)],
1159 vec![],
1160 )
1161 .unwrap();
1162 let instruction_context = transaction_context.get_next_instruction_context().unwrap();
1163
1164 let result = instruction_context.get_index_of_program_account_in_transaction();
1165 assert_eq!(result, Err(InstructionError::MissingAccount));
1166
1167 let result = instruction_context.get_program_key();
1168 assert_eq!(result, Err(InstructionError::MissingAccount));
1169
1170 let result = instruction_context.get_program_owner();
1171 assert_eq!(result.err(), Some(InstructionError::MissingAccount));
1172 }
1173}