solana_transaction_context/
lib.rs

1//! Data shared between program runtime and built-in programs as well as SBF programs.
2#![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// Inlined to avoid solana_system_interface dep
21#[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// Inlined to avoid solana_system_interface dep
30#[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// Inlined to avoid solana_account_info dep
40#[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
48/// Index of an account inside of the TransactionContext or an InstructionContext.
49pub type IndexOfAccount = u16;
50
51/// Contains account meta data which varies between instruction.
52///
53/// It also contains indices to other structures for faster lookup.
54#[derive(Clone, Debug, Eq, PartialEq)]
55pub struct InstructionAccount {
56    /// Points to the account and its key in the `TransactionContext`
57    pub index_in_transaction: IndexOfAccount,
58    /// Points to the first occurrence in the parent `InstructionContext`
59    ///
60    /// This excludes the program accounts.
61    pub index_in_caller: IndexOfAccount,
62    /// Points to the first occurrence in the current `InstructionContext`
63    ///
64    /// This excludes the program accounts.
65    pub index_in_callee: IndexOfAccount,
66    /// Is this account supposed to sign
67    pub is_signer: bool,
68    /// Is this account allowed to become writable
69    pub is_writable: bool,
70}
71
72/// An account key and the matching account
73pub 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        // The new length can not exceed the maximum permitted length
127        if new_len > MAX_PERMITTED_DATA_LENGTH as usize {
128            return Err(InstructionError::InvalidRealloc);
129        }
130        // The resize can not exceed the per-transaction maximum
131        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/// Loaded transaction shared between runtime and programs.
158///
159/// This context is valid for the entire duration of a transaction being processed.
160#[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    /// Constructs a new TransactionContext
178    #[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    /// Used in mock_process_instruction
209    #[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    /// Returns the total number of accounts loaded in this Transaction
229    pub fn get_number_of_accounts(&self) -> IndexOfAccount {
230        self.accounts.len() as IndexOfAccount
231    }
232
233    /// Searches for an account by its key
234    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    /// Searches for an account by its key
244    #[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    /// Searches for an account by its key
258    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    /// Searches for a program account by its key
266    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    /// Gets the max length of the InstructionContext trace
274    pub fn get_instruction_trace_capacity(&self) -> usize {
275        self.instruction_trace_capacity
276    }
277
278    /// Returns the instruction trace length.
279    ///
280    /// Not counting the last empty InstructionContext which is always pre-reserved for the next instruction.
281    /// See also `get_next_instruction_context()`.
282    pub fn get_instruction_trace_length(&self) -> usize {
283        self.instruction_trace.len().saturating_sub(1)
284    }
285
286    /// Gets an InstructionContext by its index in the trace
287    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    /// Gets an InstructionContext by its nesting level in the stack
297    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    /// Gets the max height of the InstructionContext stack
311    pub fn get_instruction_stack_capacity(&self) -> usize {
312        self.instruction_stack_capacity
313    }
314
315    /// Gets instruction stack height, top-level instructions are height
316    /// `solana_instruction::TRANSACTION_LEVEL_STACK_HEIGHT`
317    pub fn get_instruction_context_stack_height(&self) -> usize {
318        self.instruction_stack.len()
319    }
320
321    /// Returns the current InstructionContext
322    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    /// Returns the InstructionContext to configure for the next invocation.
331    ///
332    /// The last InstructionContext is always empty and pre-reserved for the next instruction.
333    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    /// Pushes the next InstructionContext
342    #[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    /// Pops the current InstructionContext
397    #[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        // Verify (before we pop) that the total sum of all lamports in this instruction did not change
403        let detected_an_unbalanced_instruction =
404            self.get_current_instruction_context()
405                .and_then(|instruction_context| {
406                    // Verify all executable accounts have no outstanding refs
407                    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        // Always pop, even if we `detected_an_unbalanced_instruction`
421        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    /// Gets the return data of the current InstructionContext or any above
433    pub fn get_return_data(&self) -> (&Pubkey, &[u8]) {
434        (&self.return_data.program_id, &self.return_data.data)
435    }
436
437    /// Set the return data of the current InstructionContext
438    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    /// Calculates the sum of all lamports within an instruction
448    #[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; // Skip duplicate account
461            }
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    /// Returns the accounts resize delta
478    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    /// Returns a new account data write access handler
487    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            // The two calls below can't really fail. If they fail because of a bug,
491            // whatever is writing will trigger an EbpfError::AccessViolation like
492            // if the region was readonly, and the transaction will fail gracefully.
493            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                // See BorrowedAccount::make_data_mut() as to why we reserve extra
505                // MAX_PERMITTED_DATA_INCREASE bytes here.
506                account.reserve(MAX_PERMITTED_DATA_INCREASE);
507            }
508            Ok(account.data_as_mut_slice().as_mut_ptr() as u64)
509        })
510    }
511}
512
513/// Return data at the end of a transaction
514#[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/// Loaded instruction shared between runtime and programs.
525///
526/// This context is valid for the entire duration of a (possibly cross program) instruction being processed.
527#[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    /// Used together with TransactionContext::get_next_instruction_context()
538    #[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    /// How many Instructions were on the stack after this one was pushed
551    ///
552    /// That is the number of nested parent Instructions plus one (itself).
553    pub fn get_stack_height(&self) -> usize {
554        self.nesting_level.saturating_add(1)
555    }
556
557    /// Number of program accounts
558    pub fn get_number_of_program_accounts(&self) -> IndexOfAccount {
559        self.program_accounts.len() as IndexOfAccount
560    }
561
562    /// Number of accounts in this Instruction (without program accounts)
563    pub fn get_number_of_instruction_accounts(&self) -> IndexOfAccount {
564        self.instruction_accounts.len() as IndexOfAccount
565    }
566
567    /// Assert that enough accounts were supplied to this Instruction
568    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    /// Data parameter for the programs `process_instruction` handler
580    pub fn get_instruction_data(&self) -> &[u8] {
581        &self.instruction_data
582    }
583
584    /// Searches for a program account by its key
585    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    /// Searches for an instruction account by its key
602    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    /// Translates the given instruction wide program_account_index into a transaction wide index
619    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    /// Translates the given instruction wide instruction_account_index into a transaction wide index
630    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    /// Returns `Some(instruction_account_index)` if this is a duplicate
642    /// and `None` if it is the first account with this key
643    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    /// Gets the key of the last program account of this Instruction
660    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    /// Gets the last program account of this Instruction
694    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    /// Tries to borrow a program account from this Instruction
707    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    /// Gets an instruction account of this Instruction
722    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    /// Returns whether an instruction account is a signer
738    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    /// Returns whether an instruction account is writable
750    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    /// Calculates the set of all keys of signer instruction accounts in this Instruction
762    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/// Shared account borrowed from the TransactionContext and an InstructionContext.
780#[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    /// Returns the transaction context
791    pub fn transaction_context(&self) -> &TransactionContext {
792        self.transaction_context
793    }
794
795    /// Returns the index of this account (transaction wide)
796    #[inline]
797    pub fn get_index_in_transaction(&self) -> IndexOfAccount {
798        self.index_in_transaction
799    }
800
801    /// Returns the public key of this account (transaction wide)
802    #[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    /// Returns the owner of this account (transaction wide)
810    #[inline]
811    pub fn get_owner(&self) -> &Pubkey {
812        self.account.owner()
813    }
814
815    /// Assignes the owner of this account (transaction wide)
816    #[cfg(not(target_os = "solana"))]
817    pub fn set_owner(&mut self, pubkey: &[u8]) -> Result<(), InstructionError> {
818        // Only the owner can assign a new owner
819        if !self.is_owned_by_current_program() {
820            return Err(InstructionError::ModifiedProgramId);
821        }
822        // and only if the account is writable
823        if !self.is_writable() {
824            return Err(InstructionError::ModifiedProgramId);
825        }
826        // and only if the account is not executable
827        if self.is_executable_internal() {
828            return Err(InstructionError::ModifiedProgramId);
829        }
830        // and only if the data is zero-initialized or empty
831        if !is_zeroed(self.get_data()) {
832            return Err(InstructionError::ModifiedProgramId);
833        }
834        // don't touch the account if the owner does not change
835        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    /// Returns the number of lamports of this account (transaction wide)
844    #[inline]
845    pub fn get_lamports(&self) -> u64 {
846        self.account.lamports()
847    }
848
849    /// Overwrites the number of lamports of this account (transaction wide)
850    #[cfg(not(target_os = "solana"))]
851    pub fn set_lamports(&mut self, lamports: u64) -> Result<(), InstructionError> {
852        // An account not owned by the program cannot have its balance decrease
853        if !self.is_owned_by_current_program() && lamports < self.get_lamports() {
854            return Err(InstructionError::ExternalAccountLamportSpend);
855        }
856        // The balance of read-only may not change
857        if !self.is_writable() {
858            return Err(InstructionError::ReadonlyLamportChange);
859        }
860        // The balance of executable accounts may not change
861        if self.is_executable_internal() {
862            return Err(InstructionError::ExecutableLamportChange);
863        }
864        // don't touch the account if the lamports do not change
865        if self.get_lamports() == lamports {
866            return Ok(());
867        }
868        self.touch()?;
869        self.account.set_lamports(lamports);
870        Ok(())
871    }
872
873    /// Adds lamports to this account (transaction wide)
874    #[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    /// Subtracts lamports from this account (transaction wide)
884    #[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    /// Returns a read-only slice of the account data (transaction wide)
894    #[inline]
895    pub fn get_data(&self) -> &[u8] {
896        self.account.data()
897    }
898
899    /// Returns a writable slice of the account data (transaction wide)
900    #[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    /// Returns the spare capacity of the vector backing the account data.
909    ///
910    /// This method should only ever be used during CPI, where after a shrinking
911    /// realloc we want to zero the spare capacity.
912    #[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    /// Overwrites the account data and size (transaction wide).
919    ///
920    /// You should always prefer set_data_from_slice(). Calling this method is
921    /// currently safe but requires some special casing during CPI when direct
922    /// account mapping is enabled.
923    #[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    /// Overwrites the account data and size (transaction wide).
937    ///
938    /// Call this when you have a slice of data you do not own and want to
939    /// replace the account data with it.
940    #[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        // Note that we intentionally don't call self.make_data_mut() here.  make_data_mut() will
946        // allocate + memcpy the current data if self.account is shared. We don't need the memcpy
947        // here tho because account.set_data_from_slice(data) is going to replace the content
948        // anyway.
949        self.account.set_data_from_slice(data);
950
951        Ok(())
952    }
953
954    /// Resizes the account data (transaction wide)
955    ///
956    /// Fills it with zeros at the end if is extended or truncates at the end otherwise.
957    #[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        // don't touch the account if the length does not change
961        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    /// Appends all elements in a slice to the account
971    #[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        // Even if extend_from_slice never reduces capacity, still realloc using
983        // make_data_mut() if necessary so that we grow the account of the full
984        // max realloc length in one go, avoiding smaller reallocations.
985        self.make_data_mut();
986        self.account.extend_from_slice(data);
987        Ok(())
988    }
989
990    /// Reserves capacity for at least additional more elements to be inserted
991    /// in the given account. Does nothing if capacity is already sufficient.
992    #[cfg(not(target_os = "solana"))]
993    pub fn reserve(&mut self, additional: usize) -> Result<(), InstructionError> {
994        // Note that we don't need to call can_data_be_changed() here nor
995        // touch() the account. reserve() only changes the capacity of the
996        // memory that holds the account but it doesn't actually change content
997        // nor length of the account.
998        self.make_data_mut();
999        self.account.reserve(additional);
1000
1001        Ok(())
1002    }
1003
1004    /// Returns the number of bytes the account can hold without reallocating.
1005    #[cfg(not(target_os = "solana"))]
1006    pub fn capacity(&self) -> usize {
1007        self.account.capacity()
1008    }
1009
1010    /// Returns whether the underlying AccountSharedData is shared.
1011    ///
1012    /// The data is shared if the account has been loaded from the accounts database and has never
1013    /// been written to. Writing to an account unshares it.
1014    ///
1015    /// During account serialization, if an account is shared it'll get mapped as CoW, else it'll
1016    /// get mapped directly as writable.
1017    #[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 the account is still shared, it means this is the first time we're
1025        // about to write into it. Make the account mutable by copying it in a
1026        // buffer with MAX_PERMITTED_DATA_INCREASE capacity so that if the
1027        // transaction reallocs, we don't have to copy the whole account data a
1028        // second time to fullfill the realloc.
1029        //
1030        // NOTE: The account memory region CoW code in bpf_loader::create_vm() implements the same
1031        // logic and must be kept in sync.
1032        if self.account.is_shared() {
1033            self.account.reserve(MAX_PERMITTED_DATA_INCREASE);
1034        }
1035    }
1036
1037    /// Deserializes the account data into a state
1038    #[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    /// Serializes a state into the account data
1046    #[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    // Returns whether or the lamports currently in the account is sufficient for rent exemption should the
1059    // data be resized to the given size
1060    #[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    /// Returns whether this account is executable (transaction wide)
1068    #[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    /// Feature gating to remove `is_executable` flag related checks
1075    #[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    /// Configures whether this account is executable (transaction wide)
1085    #[cfg(not(target_os = "solana"))]
1086    pub fn set_executable(&mut self, is_executable: bool) -> Result<(), InstructionError> {
1087        // To become executable an account must be rent exempt
1088        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        // Only the owner can set the executable flag
1096        if !self.is_owned_by_current_program() {
1097            return Err(InstructionError::ExecutableModified);
1098        }
1099        // and only if the account is writable
1100        if !self.is_writable() {
1101            return Err(InstructionError::ExecutableModified);
1102        }
1103        // one can not clear the executable flag
1104        if self.is_executable_internal() && !is_executable {
1105            return Err(InstructionError::ExecutableModified);
1106        }
1107        // don't touch the account if the executable flag does not change
1108        #[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    /// Returns the rent epoch of this account (transaction wide)
1118    #[cfg(not(target_os = "solana"))]
1119    #[inline]
1120    pub fn get_rent_epoch(&self) -> u64 {
1121        self.account.rent_epoch()
1122    }
1123
1124    /// Returns whether this account is a signer (instruction wide)
1125    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    /// Returns whether this account is writable (instruction wide)
1138    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    /// Returns true if the owner of this account is the current `InstructionContext`s last program (instruction wide)
1151    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    /// Returns an error if the account data can not be mutated by the current program
1159    #[cfg(not(target_os = "solana"))]
1160    pub fn can_data_be_changed(&self) -> Result<(), InstructionError> {
1161        // Only non-executable accounts data can be changed
1162        if self.is_executable_internal() {
1163            return Err(InstructionError::ExecutableDataModified);
1164        }
1165        // and only if the account is writable
1166        if !self.is_writable() {
1167            return Err(InstructionError::ReadonlyDataModified);
1168        }
1169        // and only if we are the owner
1170        if !self.is_owned_by_current_program() {
1171            return Err(InstructionError::ExternalAccountDataModified);
1172        }
1173        Ok(())
1174    }
1175
1176    /// Returns an error if the account data can not be resized to the given length
1177    #[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        // Only the owner can change the length of the data
1181        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/// Everything that needs to be recorded from a TransactionContext after execution
1206#[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/// Used by the bank in the runtime to write back the processed accounts and recorded instructions
1215#[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                /* max_instruction_stack_depth */ 2,
1270                /* max_instruction_trace_length */ 2,
1271            )
1272        };
1273
1274        let correct_space = 2;
1275        let rent_exempt_lamports = Rent::default().minimum_balance(correct_space);
1276
1277        // First try it with the wrong owner.
1278        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        // Now with the wrong data length.
1286        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        // Finally provide the correct account setup.
1294        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}