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(all(
6    not(target_os = "solana"),
7    feature = "debug-signature",
8    debug_assertions
9))]
10use solana_signature::Signature;
11#[cfg(not(target_os = "solana"))]
12use {solana_account::WritableAccount, solana_rent::Rent, std::mem::MaybeUninit};
13use {
14    solana_account::{AccountSharedData, ReadableAccount},
15    solana_instruction::error::InstructionError,
16    solana_pubkey::Pubkey,
17    std::{
18        cell::{Ref, RefCell, RefMut},
19        collections::HashSet,
20        pin::Pin,
21        rc::Rc,
22    },
23};
24
25// Inlined to avoid solana_system_interface dep
26#[cfg(not(target_os = "solana"))]
27const MAX_PERMITTED_DATA_LENGTH: u64 = 10 * 1024 * 1024;
28#[cfg(test)]
29static_assertions::const_assert_eq!(
30    MAX_PERMITTED_DATA_LENGTH,
31    solana_system_interface::MAX_PERMITTED_DATA_LENGTH
32);
33
34// Inlined to avoid solana_system_interface dep
35#[cfg(not(target_os = "solana"))]
36const MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION: i64 =
37    MAX_PERMITTED_DATA_LENGTH as i64 * 2;
38#[cfg(test)]
39static_assertions::const_assert_eq!(
40    MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION,
41    solana_system_interface::MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION
42);
43
44// Inlined to avoid solana_account_info dep
45#[cfg(not(target_os = "solana"))]
46const MAX_PERMITTED_DATA_INCREASE: usize = 1_024 * 10;
47#[cfg(test)]
48static_assertions::const_assert_eq!(
49    MAX_PERMITTED_DATA_INCREASE,
50    solana_account_info::MAX_PERMITTED_DATA_INCREASE
51);
52
53/// Index of an account inside of the TransactionContext or an InstructionContext.
54pub type IndexOfAccount = u16;
55
56/// Contains account meta data which varies between instruction.
57///
58/// It also contains indices to other structures for faster lookup.
59#[derive(Clone, Debug, Eq, PartialEq)]
60pub struct InstructionAccount {
61    /// Points to the account and its key in the `TransactionContext`
62    pub index_in_transaction: IndexOfAccount,
63    /// Points to the first occurrence in the parent `InstructionContext`
64    ///
65    /// This excludes the program accounts.
66    pub index_in_caller: IndexOfAccount,
67    /// Points to the first occurrence in the current `InstructionContext`
68    ///
69    /// This excludes the program accounts.
70    pub index_in_callee: IndexOfAccount,
71    /// Is this account supposed to sign
72    pub is_signer: bool,
73    /// Is this account allowed to become writable
74    pub is_writable: bool,
75}
76
77/// An account key and the matching account
78pub type TransactionAccount = (Pubkey, AccountSharedData);
79
80#[derive(Clone, Debug, PartialEq)]
81pub struct TransactionAccounts {
82    accounts: Vec<RefCell<AccountSharedData>>,
83    touched_flags: RefCell<Box<[bool]>>,
84}
85
86impl TransactionAccounts {
87    #[cfg(not(target_os = "solana"))]
88    fn new(accounts: Vec<RefCell<AccountSharedData>>) -> TransactionAccounts {
89        TransactionAccounts {
90            touched_flags: RefCell::new(vec![false; accounts.len()].into_boxed_slice()),
91            accounts,
92        }
93    }
94
95    fn len(&self) -> usize {
96        self.accounts.len()
97    }
98
99    pub fn get(&self, index: IndexOfAccount) -> Option<&RefCell<AccountSharedData>> {
100        self.accounts.get(index as usize)
101    }
102
103    #[cfg(not(target_os = "solana"))]
104    pub fn touch(&self, index: IndexOfAccount) -> Result<(), InstructionError> {
105        *self
106            .touched_flags
107            .borrow_mut()
108            .get_mut(index as usize)
109            .ok_or(InstructionError::NotEnoughAccountKeys)? = true;
110        Ok(())
111    }
112
113    #[cfg(not(target_os = "solana"))]
114    pub fn touched_count(&self) -> usize {
115        self.touched_flags
116            .borrow()
117            .iter()
118            .fold(0usize, |accumulator, was_touched| {
119                accumulator.saturating_add(*was_touched as usize)
120            })
121    }
122
123    pub fn try_borrow(
124        &self,
125        index: IndexOfAccount,
126    ) -> Result<Ref<'_, AccountSharedData>, InstructionError> {
127        self.accounts
128            .get(index as usize)
129            .ok_or(InstructionError::MissingAccount)?
130            .try_borrow()
131            .map_err(|_| InstructionError::AccountBorrowFailed)
132    }
133
134    pub fn try_borrow_mut(
135        &self,
136        index: IndexOfAccount,
137    ) -> Result<RefMut<'_, AccountSharedData>, InstructionError> {
138        self.accounts
139            .get(index as usize)
140            .ok_or(InstructionError::MissingAccount)?
141            .try_borrow_mut()
142            .map_err(|_| InstructionError::AccountBorrowFailed)
143    }
144
145    pub fn into_accounts(self) -> Vec<AccountSharedData> {
146        self.accounts
147            .into_iter()
148            .map(|account| account.into_inner())
149            .collect()
150    }
151}
152
153/// Loaded transaction shared between runtime and programs.
154///
155/// This context is valid for the entire duration of a transaction being processed.
156#[derive(Debug, Clone, PartialEq)]
157pub struct TransactionContext {
158    account_keys: Pin<Box<[Pubkey]>>,
159    accounts: Rc<TransactionAccounts>,
160    instruction_stack_capacity: usize,
161    instruction_trace_capacity: usize,
162    instruction_stack: Vec<usize>,
163    instruction_trace: Vec<InstructionContext>,
164    return_data: TransactionReturnData,
165    accounts_resize_delta: RefCell<i64>,
166    #[cfg(not(target_os = "solana"))]
167    remove_accounts_executable_flag_checks: bool,
168    #[cfg(not(target_os = "solana"))]
169    rent: Rent,
170    /// Useful for debugging to filter by or to look it up on the explorer
171    #[cfg(all(
172        not(target_os = "solana"),
173        feature = "debug-signature",
174        debug_assertions
175    ))]
176    signature: Signature,
177}
178
179impl TransactionContext {
180    /// Constructs a new TransactionContext
181    #[cfg(not(target_os = "solana"))]
182    pub fn new(
183        transaction_accounts: Vec<TransactionAccount>,
184        rent: Rent,
185        instruction_stack_capacity: usize,
186        instruction_trace_capacity: usize,
187    ) -> Self {
188        let (account_keys, accounts): (Vec<_>, Vec<_>) = transaction_accounts
189            .into_iter()
190            .map(|(key, account)| (key, RefCell::new(account)))
191            .unzip();
192        Self {
193            account_keys: Pin::new(account_keys.into_boxed_slice()),
194            accounts: Rc::new(TransactionAccounts::new(accounts)),
195            instruction_stack_capacity,
196            instruction_trace_capacity,
197            instruction_stack: Vec::with_capacity(instruction_stack_capacity),
198            instruction_trace: vec![InstructionContext::default()],
199            return_data: TransactionReturnData::default(),
200            accounts_resize_delta: RefCell::new(0),
201            remove_accounts_executable_flag_checks: true,
202            rent,
203            #[cfg(all(
204                not(target_os = "solana"),
205                feature = "debug-signature",
206                debug_assertions
207            ))]
208            signature: Signature::default(),
209        }
210    }
211
212    #[cfg(not(target_os = "solana"))]
213    pub fn set_remove_accounts_executable_flag_checks(&mut self, enabled: bool) {
214        self.remove_accounts_executable_flag_checks = enabled;
215    }
216
217    /// Used in mock_process_instruction
218    #[cfg(not(target_os = "solana"))]
219    pub fn deconstruct_without_keys(self) -> Result<Vec<AccountSharedData>, InstructionError> {
220        if !self.instruction_stack.is_empty() {
221            return Err(InstructionError::CallDepth);
222        }
223
224        Ok(Rc::try_unwrap(self.accounts)
225            .expect("transaction_context.accounts has unexpected outstanding refs")
226            .into_accounts())
227    }
228
229    #[cfg(not(target_os = "solana"))]
230    pub fn accounts(&self) -> &Rc<TransactionAccounts> {
231        &self.accounts
232    }
233
234    /// Stores the signature of the current transaction
235    #[cfg(all(
236        not(target_os = "solana"),
237        feature = "debug-signature",
238        debug_assertions
239    ))]
240    pub fn set_signature(&mut self, signature: &Signature) {
241        self.signature = *signature;
242    }
243
244    /// Returns the signature of the current transaction
245    #[cfg(all(
246        not(target_os = "solana"),
247        feature = "debug-signature",
248        debug_assertions
249    ))]
250    pub fn get_signature(&self) -> &Signature {
251        &self.signature
252    }
253
254    /// Returns the total number of accounts loaded in this Transaction
255    pub fn get_number_of_accounts(&self) -> IndexOfAccount {
256        self.accounts.len() as IndexOfAccount
257    }
258
259    /// Searches for an account by its key
260    pub fn get_key_of_account_at_index(
261        &self,
262        index_in_transaction: IndexOfAccount,
263    ) -> Result<&Pubkey, InstructionError> {
264        self.account_keys
265            .get(index_in_transaction as usize)
266            .ok_or(InstructionError::NotEnoughAccountKeys)
267    }
268
269    /// Searches for an account by its key
270    #[cfg(not(target_os = "solana"))]
271    pub fn get_account_at_index(
272        &self,
273        index_in_transaction: IndexOfAccount,
274    ) -> Result<&RefCell<AccountSharedData>, InstructionError> {
275        self.accounts
276            .get(index_in_transaction)
277            .ok_or(InstructionError::NotEnoughAccountKeys)
278    }
279
280    /// Searches for an account by its key
281    pub fn find_index_of_account(&self, pubkey: &Pubkey) -> Option<IndexOfAccount> {
282        self.account_keys
283            .iter()
284            .position(|key| key == pubkey)
285            .map(|index| index as IndexOfAccount)
286    }
287
288    /// Searches for a program account by its key
289    pub fn find_index_of_program_account(&self, pubkey: &Pubkey) -> Option<IndexOfAccount> {
290        self.account_keys
291            .iter()
292            .rposition(|key| key == pubkey)
293            .map(|index| index as IndexOfAccount)
294    }
295
296    /// Gets the max length of the InstructionContext trace
297    pub fn get_instruction_trace_capacity(&self) -> usize {
298        self.instruction_trace_capacity
299    }
300
301    /// Returns the instruction trace length.
302    ///
303    /// Not counting the last empty InstructionContext which is always pre-reserved for the next instruction.
304    /// See also `get_next_instruction_context()`.
305    pub fn get_instruction_trace_length(&self) -> usize {
306        self.instruction_trace.len().saturating_sub(1)
307    }
308
309    /// Gets an InstructionContext by its index in the trace
310    pub fn get_instruction_context_at_index_in_trace(
311        &self,
312        index_in_trace: usize,
313    ) -> Result<&InstructionContext, InstructionError> {
314        self.instruction_trace
315            .get(index_in_trace)
316            .ok_or(InstructionError::CallDepth)
317    }
318
319    /// Gets an InstructionContext by its nesting level in the stack
320    pub fn get_instruction_context_at_nesting_level(
321        &self,
322        nesting_level: usize,
323    ) -> Result<&InstructionContext, InstructionError> {
324        let index_in_trace = *self
325            .instruction_stack
326            .get(nesting_level)
327            .ok_or(InstructionError::CallDepth)?;
328        let instruction_context = self.get_instruction_context_at_index_in_trace(index_in_trace)?;
329        debug_assert_eq!(instruction_context.nesting_level, nesting_level);
330        Ok(instruction_context)
331    }
332
333    /// Gets the max height of the InstructionContext stack
334    pub fn get_instruction_stack_capacity(&self) -> usize {
335        self.instruction_stack_capacity
336    }
337
338    /// Gets instruction stack height, top-level instructions are height
339    /// `solana_sdk::instruction::TRANSACTION_LEVEL_STACK_HEIGHT`
340    pub fn get_instruction_context_stack_height(&self) -> usize {
341        self.instruction_stack.len()
342    }
343
344    /// Returns the current InstructionContext
345    pub fn get_current_instruction_context(&self) -> Result<&InstructionContext, InstructionError> {
346        let level = self
347            .get_instruction_context_stack_height()
348            .checked_sub(1)
349            .ok_or(InstructionError::CallDepth)?;
350        self.get_instruction_context_at_nesting_level(level)
351    }
352
353    /// Returns the InstructionContext to configure for the next invocation.
354    ///
355    /// The last InstructionContext is always empty and pre-reserved for the next instruction.
356    pub fn get_next_instruction_context(
357        &mut self,
358    ) -> Result<&mut InstructionContext, InstructionError> {
359        self.instruction_trace
360            .last_mut()
361            .ok_or(InstructionError::CallDepth)
362    }
363
364    /// Pushes the next InstructionContext
365    #[cfg(not(target_os = "solana"))]
366    pub fn push(&mut self) -> Result<(), InstructionError> {
367        let nesting_level = self.get_instruction_context_stack_height();
368        let caller_instruction_context = self
369            .instruction_trace
370            .last()
371            .ok_or(InstructionError::CallDepth)?;
372        let callee_instruction_accounts_lamport_sum =
373            self.instruction_accounts_lamport_sum(caller_instruction_context)?;
374        if !self.instruction_stack.is_empty() {
375            let caller_instruction_context = self.get_current_instruction_context()?;
376            let original_caller_instruction_accounts_lamport_sum =
377                caller_instruction_context.instruction_accounts_lamport_sum;
378            let current_caller_instruction_accounts_lamport_sum =
379                self.instruction_accounts_lamport_sum(caller_instruction_context)?;
380            if original_caller_instruction_accounts_lamport_sum
381                != current_caller_instruction_accounts_lamport_sum
382            {
383                return Err(InstructionError::UnbalancedInstruction);
384            }
385        }
386        {
387            let instruction_context = self.get_next_instruction_context()?;
388            instruction_context.nesting_level = nesting_level;
389            instruction_context.instruction_accounts_lamport_sum =
390                callee_instruction_accounts_lamport_sum;
391        }
392        let index_in_trace = self.get_instruction_trace_length();
393        if index_in_trace >= self.instruction_trace_capacity {
394            return Err(InstructionError::MaxInstructionTraceLengthExceeded);
395        }
396        self.instruction_trace.push(InstructionContext::default());
397        if nesting_level >= self.instruction_stack_capacity {
398            return Err(InstructionError::CallDepth);
399        }
400        self.instruction_stack.push(index_in_trace);
401        Ok(())
402    }
403
404    /// Pops the current InstructionContext
405    #[cfg(not(target_os = "solana"))]
406    pub fn pop(&mut self) -> Result<(), InstructionError> {
407        if self.instruction_stack.is_empty() {
408            return Err(InstructionError::CallDepth);
409        }
410        // Verify (before we pop) that the total sum of all lamports in this instruction did not change
411        let detected_an_unbalanced_instruction =
412            self.get_current_instruction_context()
413                .and_then(|instruction_context| {
414                    // Verify all executable accounts have no outstanding refs
415                    for account_index in instruction_context.program_accounts.iter() {
416                        self.get_account_at_index(*account_index)?
417                            .try_borrow_mut()
418                            .map_err(|_| InstructionError::AccountBorrowOutstanding)?;
419                    }
420                    self.instruction_accounts_lamport_sum(instruction_context)
421                        .map(|instruction_accounts_lamport_sum| {
422                            instruction_context.instruction_accounts_lamport_sum
423                                != instruction_accounts_lamport_sum
424                        })
425                });
426        // Always pop, even if we `detected_an_unbalanced_instruction`
427        self.instruction_stack.pop();
428        if detected_an_unbalanced_instruction? {
429            Err(InstructionError::UnbalancedInstruction)
430        } else {
431            Ok(())
432        }
433    }
434
435    /// Gets the return data of the current InstructionContext or any above
436    pub fn get_return_data(&self) -> (&Pubkey, &[u8]) {
437        (&self.return_data.program_id, &self.return_data.data)
438    }
439
440    /// Set the return data of the current InstructionContext
441    pub fn set_return_data(
442        &mut self,
443        program_id: Pubkey,
444        data: Vec<u8>,
445    ) -> Result<(), InstructionError> {
446        self.return_data = TransactionReturnData { program_id, data };
447        Ok(())
448    }
449
450    /// Calculates the sum of all lamports within an instruction
451    #[cfg(not(target_os = "solana"))]
452    fn instruction_accounts_lamport_sum(
453        &self,
454        instruction_context: &InstructionContext,
455    ) -> Result<u128, InstructionError> {
456        let mut instruction_accounts_lamport_sum: u128 = 0;
457        for instruction_account_index in 0..instruction_context.get_number_of_instruction_accounts()
458        {
459            if instruction_context
460                .is_instruction_account_duplicate(instruction_account_index)?
461                .is_some()
462            {
463                continue; // Skip duplicate account
464            }
465            let index_in_transaction = instruction_context
466                .get_index_of_instruction_account_in_transaction(instruction_account_index)?;
467            instruction_accounts_lamport_sum = (self
468                .get_account_at_index(index_in_transaction)?
469                .try_borrow()
470                .map_err(|_| InstructionError::AccountBorrowOutstanding)?
471                .lamports() as u128)
472                .checked_add(instruction_accounts_lamport_sum)
473                .ok_or(InstructionError::ArithmeticOverflow)?;
474        }
475        Ok(instruction_accounts_lamport_sum)
476    }
477
478    /// Returns the accounts resize delta
479    pub fn accounts_resize_delta(&self) -> Result<i64, InstructionError> {
480        self.accounts_resize_delta
481            .try_borrow()
482            .map_err(|_| InstructionError::GenericError)
483            .map(|value_ref| *value_ref)
484    }
485}
486
487/// Return data at the end of a transaction
488#[cfg_attr(
489    feature = "serde",
490    derive(serde_derive::Deserialize, serde_derive::Serialize)
491)]
492#[derive(Clone, Debug, Default, PartialEq, Eq)]
493pub struct TransactionReturnData {
494    pub program_id: Pubkey,
495    pub data: Vec<u8>,
496}
497
498/// Loaded instruction shared between runtime and programs.
499///
500/// This context is valid for the entire duration of a (possibly cross program) instruction being processed.
501#[derive(Debug, Clone, Default, Eq, PartialEq)]
502pub struct InstructionContext {
503    nesting_level: usize,
504    instruction_accounts_lamport_sum: u128,
505    program_accounts: Vec<IndexOfAccount>,
506    instruction_accounts: Vec<InstructionAccount>,
507    instruction_data: Vec<u8>,
508}
509
510impl InstructionContext {
511    /// Used together with TransactionContext::get_next_instruction_context()
512    #[cfg(not(target_os = "solana"))]
513    pub fn configure(
514        &mut self,
515        program_accounts: &[IndexOfAccount],
516        instruction_accounts: &[InstructionAccount],
517        instruction_data: &[u8],
518    ) {
519        self.program_accounts = program_accounts.to_vec();
520        self.instruction_accounts = instruction_accounts.to_vec();
521        self.instruction_data = instruction_data.to_vec();
522    }
523
524    /// How many Instructions were on the stack after this one was pushed
525    ///
526    /// That is the number of nested parent Instructions plus one (itself).
527    pub fn get_stack_height(&self) -> usize {
528        self.nesting_level.saturating_add(1)
529    }
530
531    /// Number of program accounts
532    pub fn get_number_of_program_accounts(&self) -> IndexOfAccount {
533        self.program_accounts.len() as IndexOfAccount
534    }
535
536    /// Number of accounts in this Instruction (without program accounts)
537    pub fn get_number_of_instruction_accounts(&self) -> IndexOfAccount {
538        self.instruction_accounts.len() as IndexOfAccount
539    }
540
541    /// Assert that enough accounts were supplied to this Instruction
542    pub fn check_number_of_instruction_accounts(
543        &self,
544        expected_at_least: IndexOfAccount,
545    ) -> Result<(), InstructionError> {
546        if self.get_number_of_instruction_accounts() < expected_at_least {
547            Err(InstructionError::NotEnoughAccountKeys)
548        } else {
549            Ok(())
550        }
551    }
552
553    /// Data parameter for the programs `process_instruction` handler
554    pub fn get_instruction_data(&self) -> &[u8] {
555        &self.instruction_data
556    }
557
558    /// Searches for a program account by its key
559    pub fn find_index_of_program_account(
560        &self,
561        transaction_context: &TransactionContext,
562        pubkey: &Pubkey,
563    ) -> Option<IndexOfAccount> {
564        self.program_accounts
565            .iter()
566            .position(|index_in_transaction| {
567                transaction_context
568                    .account_keys
569                    .get(*index_in_transaction as usize)
570                    == Some(pubkey)
571            })
572            .map(|index| index as IndexOfAccount)
573    }
574
575    /// Searches for an instruction account by its key
576    pub fn find_index_of_instruction_account(
577        &self,
578        transaction_context: &TransactionContext,
579        pubkey: &Pubkey,
580    ) -> Option<IndexOfAccount> {
581        self.instruction_accounts
582            .iter()
583            .position(|instruction_account| {
584                transaction_context
585                    .account_keys
586                    .get(instruction_account.index_in_transaction as usize)
587                    == Some(pubkey)
588            })
589            .map(|index| index as IndexOfAccount)
590    }
591
592    /// Translates the given instruction wide program_account_index into a transaction wide index
593    pub fn get_index_of_program_account_in_transaction(
594        &self,
595        program_account_index: IndexOfAccount,
596    ) -> Result<IndexOfAccount, InstructionError> {
597        Ok(*self
598            .program_accounts
599            .get(program_account_index as usize)
600            .ok_or(InstructionError::NotEnoughAccountKeys)?)
601    }
602
603    /// Translates the given instruction wide instruction_account_index into a transaction wide index
604    pub fn get_index_of_instruction_account_in_transaction(
605        &self,
606        instruction_account_index: IndexOfAccount,
607    ) -> Result<IndexOfAccount, InstructionError> {
608        Ok(self
609            .instruction_accounts
610            .get(instruction_account_index as usize)
611            .ok_or(InstructionError::NotEnoughAccountKeys)?
612            .index_in_transaction as IndexOfAccount)
613    }
614
615    /// Returns `Some(instruction_account_index)` if this is a duplicate
616    /// and `None` if it is the first account with this key
617    pub fn is_instruction_account_duplicate(
618        &self,
619        instruction_account_index: IndexOfAccount,
620    ) -> Result<Option<IndexOfAccount>, InstructionError> {
621        let index_in_callee = self
622            .instruction_accounts
623            .get(instruction_account_index as usize)
624            .ok_or(InstructionError::NotEnoughAccountKeys)?
625            .index_in_callee;
626        Ok(if index_in_callee == instruction_account_index {
627            None
628        } else {
629            Some(index_in_callee)
630        })
631    }
632
633    /// Gets the key of the last program account of this Instruction
634    pub fn get_last_program_key<'a, 'b: 'a>(
635        &'a self,
636        transaction_context: &'b TransactionContext,
637    ) -> Result<&'b Pubkey, InstructionError> {
638        self.get_index_of_program_account_in_transaction(
639            self.get_number_of_program_accounts().saturating_sub(1),
640        )
641        .and_then(|index_in_transaction| {
642            transaction_context.get_key_of_account_at_index(index_in_transaction)
643        })
644    }
645
646    fn try_borrow_account<'a, 'b: 'a>(
647        &'a self,
648        transaction_context: &'b TransactionContext,
649        index_in_transaction: IndexOfAccount,
650        index_in_instruction: IndexOfAccount,
651    ) -> Result<BorrowedAccount<'a>, InstructionError> {
652        let account = transaction_context
653            .accounts
654            .get(index_in_transaction)
655            .ok_or(InstructionError::MissingAccount)?
656            .try_borrow_mut()
657            .map_err(|_| InstructionError::AccountBorrowFailed)?;
658        Ok(BorrowedAccount {
659            transaction_context,
660            instruction_context: self,
661            index_in_transaction,
662            index_in_instruction,
663            account,
664        })
665    }
666
667    /// Gets the last program account of this Instruction
668    pub fn try_borrow_last_program_account<'a, 'b: 'a>(
669        &'a self,
670        transaction_context: &'b TransactionContext,
671    ) -> Result<BorrowedAccount<'a>, InstructionError> {
672        let result = self.try_borrow_program_account(
673            transaction_context,
674            self.get_number_of_program_accounts().saturating_sub(1),
675        );
676        debug_assert!(result.is_ok());
677        result
678    }
679
680    /// Tries to borrow a program account from this Instruction
681    pub fn try_borrow_program_account<'a, 'b: 'a>(
682        &'a self,
683        transaction_context: &'b TransactionContext,
684        program_account_index: IndexOfAccount,
685    ) -> Result<BorrowedAccount<'a>, InstructionError> {
686        let index_in_transaction =
687            self.get_index_of_program_account_in_transaction(program_account_index)?;
688        self.try_borrow_account(
689            transaction_context,
690            index_in_transaction,
691            program_account_index,
692        )
693    }
694
695    /// Gets an instruction account of this Instruction
696    pub fn try_borrow_instruction_account<'a, 'b: 'a>(
697        &'a self,
698        transaction_context: &'b TransactionContext,
699        instruction_account_index: IndexOfAccount,
700    ) -> Result<BorrowedAccount<'a>, InstructionError> {
701        let index_in_transaction =
702            self.get_index_of_instruction_account_in_transaction(instruction_account_index)?;
703        self.try_borrow_account(
704            transaction_context,
705            index_in_transaction,
706            self.get_number_of_program_accounts()
707                .saturating_add(instruction_account_index),
708        )
709    }
710
711    /// Returns whether an instruction account is a signer
712    pub fn is_instruction_account_signer(
713        &self,
714        instruction_account_index: IndexOfAccount,
715    ) -> Result<bool, InstructionError> {
716        Ok(self
717            .instruction_accounts
718            .get(instruction_account_index as usize)
719            .ok_or(InstructionError::MissingAccount)?
720            .is_signer)
721    }
722
723    /// Returns whether an instruction account is writable
724    pub fn is_instruction_account_writable(
725        &self,
726        instruction_account_index: IndexOfAccount,
727    ) -> Result<bool, InstructionError> {
728        Ok(self
729            .instruction_accounts
730            .get(instruction_account_index as usize)
731            .ok_or(InstructionError::MissingAccount)?
732            .is_writable)
733    }
734
735    /// Calculates the set of all keys of signer instruction accounts in this Instruction
736    pub fn get_signers(
737        &self,
738        transaction_context: &TransactionContext,
739    ) -> Result<HashSet<Pubkey>, InstructionError> {
740        let mut result = HashSet::new();
741        for instruction_account in self.instruction_accounts.iter() {
742            if instruction_account.is_signer {
743                result.insert(
744                    *transaction_context
745                        .get_key_of_account_at_index(instruction_account.index_in_transaction)?,
746                );
747            }
748        }
749        Ok(result)
750    }
751}
752
753/// Shared account borrowed from the TransactionContext and an InstructionContext.
754#[derive(Debug)]
755pub struct BorrowedAccount<'a> {
756    transaction_context: &'a TransactionContext,
757    instruction_context: &'a InstructionContext,
758    index_in_transaction: IndexOfAccount,
759    index_in_instruction: IndexOfAccount,
760    account: RefMut<'a, AccountSharedData>,
761}
762
763impl BorrowedAccount<'_> {
764    /// Returns the transaction context
765    pub fn transaction_context(&self) -> &TransactionContext {
766        self.transaction_context
767    }
768
769    /// Returns the index of this account (transaction wide)
770    #[inline]
771    pub fn get_index_in_transaction(&self) -> IndexOfAccount {
772        self.index_in_transaction
773    }
774
775    /// Returns the public key of this account (transaction wide)
776    #[inline]
777    pub fn get_key(&self) -> &Pubkey {
778        self.transaction_context
779            .get_key_of_account_at_index(self.index_in_transaction)
780            .unwrap()
781    }
782
783    /// Returns the owner of this account (transaction wide)
784    #[inline]
785    pub fn get_owner(&self) -> &Pubkey {
786        self.account.owner()
787    }
788
789    /// Assignes the owner of this account (transaction wide)
790    #[cfg(not(target_os = "solana"))]
791    pub fn set_owner(&mut self, pubkey: &[u8]) -> Result<(), InstructionError> {
792        // Only the owner can assign a new owner
793        if !self.is_owned_by_current_program() {
794            return Err(InstructionError::ModifiedProgramId);
795        }
796        // and only if the account is writable
797        if !self.is_writable() {
798            return Err(InstructionError::ModifiedProgramId);
799        }
800        // and only if the account is not executable
801        if self.is_executable_internal() {
802            return Err(InstructionError::ModifiedProgramId);
803        }
804        // and only if the data is zero-initialized or empty
805        if !is_zeroed(self.get_data()) {
806            return Err(InstructionError::ModifiedProgramId);
807        }
808        // don't touch the account if the owner does not change
809        if self.get_owner().to_bytes() == pubkey {
810            return Ok(());
811        }
812        self.touch()?;
813        self.account.copy_into_owner_from_slice(pubkey);
814        Ok(())
815    }
816
817    /// Returns the number of lamports of this account (transaction wide)
818    #[inline]
819    pub fn get_lamports(&self) -> u64 {
820        self.account.lamports()
821    }
822
823    /// Overwrites the number of lamports of this account (transaction wide)
824    #[cfg(not(target_os = "solana"))]
825    pub fn set_lamports(&mut self, lamports: u64) -> Result<(), InstructionError> {
826        // An account not owned by the program cannot have its balance decrease
827        if !self.is_owned_by_current_program() && lamports < self.get_lamports() {
828            return Err(InstructionError::ExternalAccountLamportSpend);
829        }
830        // The balance of read-only may not change
831        if !self.is_writable() {
832            return Err(InstructionError::ReadonlyLamportChange);
833        }
834        // The balance of executable accounts may not change
835        if self.is_executable_internal() {
836            return Err(InstructionError::ExecutableLamportChange);
837        }
838        // don't touch the account if the lamports do not change
839        if self.get_lamports() == lamports {
840            return Ok(());
841        }
842        self.touch()?;
843        self.account.set_lamports(lamports);
844        Ok(())
845    }
846
847    /// Adds lamports to this account (transaction wide)
848    #[cfg(not(target_os = "solana"))]
849    pub fn checked_add_lamports(&mut self, lamports: u64) -> Result<(), InstructionError> {
850        self.set_lamports(
851            self.get_lamports()
852                .checked_add(lamports)
853                .ok_or(InstructionError::ArithmeticOverflow)?,
854        )
855    }
856
857    /// Subtracts lamports from this account (transaction wide)
858    #[cfg(not(target_os = "solana"))]
859    pub fn checked_sub_lamports(&mut self, lamports: u64) -> Result<(), InstructionError> {
860        self.set_lamports(
861            self.get_lamports()
862                .checked_sub(lamports)
863                .ok_or(InstructionError::ArithmeticOverflow)?,
864        )
865    }
866
867    /// Returns a read-only slice of the account data (transaction wide)
868    #[inline]
869    pub fn get_data(&self) -> &[u8] {
870        self.account.data()
871    }
872
873    /// Returns a writable slice of the account data (transaction wide)
874    #[cfg(not(target_os = "solana"))]
875    pub fn get_data_mut(&mut self) -> Result<&mut [u8], InstructionError> {
876        self.can_data_be_changed()?;
877        self.touch()?;
878        self.make_data_mut();
879        Ok(self.account.data_as_mut_slice())
880    }
881
882    /// Returns the spare capacity of the vector backing the account data.
883    ///
884    /// This method should only ever be used during CPI, where after a shrinking
885    /// realloc we want to zero the spare capacity.
886    #[cfg(not(target_os = "solana"))]
887    pub fn spare_data_capacity_mut(&mut self) -> Result<&mut [MaybeUninit<u8>], InstructionError> {
888        debug_assert!(!self.account.is_shared());
889        Ok(self.account.spare_data_capacity_mut())
890    }
891
892    /// Overwrites the account data and size (transaction wide).
893    ///
894    /// You should always prefer set_data_from_slice(). Calling this method is
895    /// currently safe but requires some special casing during CPI when direct
896    /// account mapping is enabled.
897    #[cfg(all(
898        not(target_os = "solana"),
899        any(test, feature = "dev-context-only-utils")
900    ))]
901    pub fn set_data(&mut self, data: Vec<u8>) -> Result<(), InstructionError> {
902        self.can_data_be_resized(data.len())?;
903        self.can_data_be_changed()?;
904        self.touch()?;
905
906        self.update_accounts_resize_delta(data.len())?;
907        self.account.set_data(data);
908        Ok(())
909    }
910
911    /// Overwrites the account data and size (transaction wide).
912    ///
913    /// Call this when you have a slice of data you do not own and want to
914    /// replace the account data with it.
915    #[cfg(not(target_os = "solana"))]
916    pub fn set_data_from_slice(&mut self, data: &[u8]) -> Result<(), InstructionError> {
917        self.can_data_be_resized(data.len())?;
918        self.can_data_be_changed()?;
919        self.touch()?;
920        self.update_accounts_resize_delta(data.len())?;
921        // Note that we intentionally don't call self.make_data_mut() here.  make_data_mut() will
922        // allocate + memcpy the current data if self.account is shared. We don't need the memcpy
923        // here tho because account.set_data_from_slice(data) is going to replace the content
924        // anyway.
925        self.account.set_data_from_slice(data);
926
927        Ok(())
928    }
929
930    /// Resizes the account data (transaction wide)
931    ///
932    /// Fills it with zeros at the end if is extended or truncates at the end otherwise.
933    #[cfg(not(target_os = "solana"))]
934    pub fn set_data_length(&mut self, new_length: usize) -> Result<(), InstructionError> {
935        self.can_data_be_resized(new_length)?;
936        self.can_data_be_changed()?;
937        // don't touch the account if the length does not change
938        if self.get_data().len() == new_length {
939            return Ok(());
940        }
941        self.touch()?;
942        self.update_accounts_resize_delta(new_length)?;
943        self.account.resize(new_length, 0);
944        Ok(())
945    }
946
947    /// Appends all elements in a slice to the account
948    #[cfg(not(target_os = "solana"))]
949    pub fn extend_from_slice(&mut self, data: &[u8]) -> Result<(), InstructionError> {
950        let new_len = self.get_data().len().saturating_add(data.len());
951        self.can_data_be_resized(new_len)?;
952        self.can_data_be_changed()?;
953
954        if data.is_empty() {
955            return Ok(());
956        }
957
958        self.touch()?;
959        self.update_accounts_resize_delta(new_len)?;
960        // Even if extend_from_slice never reduces capacity, still realloc using
961        // make_data_mut() if necessary so that we grow the account of the full
962        // max realloc length in one go, avoiding smaller reallocations.
963        self.make_data_mut();
964        self.account.extend_from_slice(data);
965        Ok(())
966    }
967
968    /// Reserves capacity for at least additional more elements to be inserted
969    /// in the given account. Does nothing if capacity is already sufficient.
970    #[cfg(not(target_os = "solana"))]
971    pub fn reserve(&mut self, additional: usize) -> Result<(), InstructionError> {
972        // Note that we don't need to call can_data_be_changed() here nor
973        // touch() the account. reserve() only changes the capacity of the
974        // memory that holds the account but it doesn't actually change content
975        // nor length of the account.
976        self.make_data_mut();
977        self.account.reserve(additional);
978
979        Ok(())
980    }
981
982    /// Returns the number of bytes the account can hold without reallocating.
983    #[cfg(not(target_os = "solana"))]
984    pub fn capacity(&self) -> usize {
985        self.account.capacity()
986    }
987
988    /// Returns whether the underlying AccountSharedData is shared.
989    ///
990    /// The data is shared if the account has been loaded from the accounts database and has never
991    /// been written to. Writing to an account unshares it.
992    ///
993    /// During account serialization, if an account is shared it'll get mapped as CoW, else it'll
994    /// get mapped directly as writable.
995    #[cfg(not(target_os = "solana"))]
996    pub fn is_shared(&self) -> bool {
997        self.account.is_shared()
998    }
999
1000    #[cfg(not(target_os = "solana"))]
1001    fn make_data_mut(&mut self) {
1002        // if the account is still shared, it means this is the first time we're
1003        // about to write into it. Make the account mutable by copying it in a
1004        // buffer with MAX_PERMITTED_DATA_INCREASE capacity so that if the
1005        // transaction reallocs, we don't have to copy the whole account data a
1006        // second time to fullfill the realloc.
1007        //
1008        // NOTE: The account memory region CoW code in bpf_loader::create_vm() implements the same
1009        // logic and must be kept in sync.
1010        if self.account.is_shared() {
1011            self.account.reserve(MAX_PERMITTED_DATA_INCREASE);
1012        }
1013    }
1014
1015    /// Deserializes the account data into a state
1016    #[cfg(all(not(target_os = "solana"), feature = "bincode"))]
1017    pub fn get_state<T: serde::de::DeserializeOwned>(&self) -> Result<T, InstructionError> {
1018        self.account
1019            .deserialize_data()
1020            .map_err(|_| InstructionError::InvalidAccountData)
1021    }
1022
1023    /// Serializes a state into the account data
1024    #[cfg(all(not(target_os = "solana"), feature = "bincode"))]
1025    pub fn set_state<T: serde::Serialize>(&mut self, state: &T) -> Result<(), InstructionError> {
1026        let data = self.get_data_mut()?;
1027        let serialized_size =
1028            bincode::serialized_size(state).map_err(|_| InstructionError::GenericError)?;
1029        if serialized_size > data.len() as u64 {
1030            return Err(InstructionError::AccountDataTooSmall);
1031        }
1032        bincode::serialize_into(&mut *data, state).map_err(|_| InstructionError::GenericError)?;
1033        Ok(())
1034    }
1035
1036    // Returns whether or the lamports currently in the account is sufficient for rent exemption should the
1037    // data be resized to the given size
1038    #[cfg(not(target_os = "solana"))]
1039    pub fn is_rent_exempt_at_data_length(&self, data_length: usize) -> bool {
1040        self.transaction_context
1041            .rent
1042            .is_exempt(self.get_lamports(), data_length)
1043    }
1044
1045    /// Returns whether this account is executable (transaction wide)
1046    #[inline]
1047    #[deprecated(since = "2.1.0", note = "Use `get_owner` instead")]
1048    pub fn is_executable(&self) -> bool {
1049        self.account.executable()
1050    }
1051
1052    /// Feature gating to remove `is_executable` flag related checks
1053    #[cfg(not(target_os = "solana"))]
1054    #[inline]
1055    fn is_executable_internal(&self) -> bool {
1056        !self
1057            .transaction_context
1058            .remove_accounts_executable_flag_checks
1059            && self.account.executable()
1060    }
1061
1062    /// Configures whether this account is executable (transaction wide)
1063    #[cfg(not(target_os = "solana"))]
1064    pub fn set_executable(&mut self, is_executable: bool) -> Result<(), InstructionError> {
1065        // To become executable an account must be rent exempt
1066        if !self
1067            .transaction_context
1068            .rent
1069            .is_exempt(self.get_lamports(), self.get_data().len())
1070        {
1071            return Err(InstructionError::ExecutableAccountNotRentExempt);
1072        }
1073        // Only the owner can set the executable flag
1074        if !self.is_owned_by_current_program() {
1075            return Err(InstructionError::ExecutableModified);
1076        }
1077        // and only if the account is writable
1078        if !self.is_writable() {
1079            return Err(InstructionError::ExecutableModified);
1080        }
1081        // one can not clear the executable flag
1082        if self.is_executable_internal() && !is_executable {
1083            return Err(InstructionError::ExecutableModified);
1084        }
1085        // don't touch the account if the executable flag does not change
1086        #[allow(deprecated)]
1087        if self.is_executable() == is_executable {
1088            return Ok(());
1089        }
1090        self.touch()?;
1091        self.account.set_executable(is_executable);
1092        Ok(())
1093    }
1094
1095    /// Returns the rent epoch of this account (transaction wide)
1096    #[cfg(not(target_os = "solana"))]
1097    #[inline]
1098    pub fn get_rent_epoch(&self) -> u64 {
1099        self.account.rent_epoch()
1100    }
1101
1102    /// Returns whether this account is a signer (instruction wide)
1103    pub fn is_signer(&self) -> bool {
1104        if self.index_in_instruction < self.instruction_context.get_number_of_program_accounts() {
1105            return false;
1106        }
1107        self.instruction_context
1108            .is_instruction_account_signer(
1109                self.index_in_instruction
1110                    .saturating_sub(self.instruction_context.get_number_of_program_accounts()),
1111            )
1112            .unwrap_or_default()
1113    }
1114
1115    /// Returns whether this account is writable (instruction wide)
1116    pub fn is_writable(&self) -> bool {
1117        if self.index_in_instruction < self.instruction_context.get_number_of_program_accounts() {
1118            return false;
1119        }
1120        self.instruction_context
1121            .is_instruction_account_writable(
1122                self.index_in_instruction
1123                    .saturating_sub(self.instruction_context.get_number_of_program_accounts()),
1124            )
1125            .unwrap_or_default()
1126    }
1127
1128    /// Returns true if the owner of this account is the current `InstructionContext`s last program (instruction wide)
1129    pub fn is_owned_by_current_program(&self) -> bool {
1130        self.instruction_context
1131            .get_last_program_key(self.transaction_context)
1132            .map(|key| key == self.get_owner())
1133            .unwrap_or_default()
1134    }
1135
1136    /// Returns an error if the account data can not be mutated by the current program
1137    #[cfg(not(target_os = "solana"))]
1138    pub fn can_data_be_changed(&self) -> Result<(), InstructionError> {
1139        // Only non-executable accounts data can be changed
1140        if self.is_executable_internal() {
1141            return Err(InstructionError::ExecutableDataModified);
1142        }
1143        // and only if the account is writable
1144        if !self.is_writable() {
1145            return Err(InstructionError::ReadonlyDataModified);
1146        }
1147        // and only if we are the owner
1148        if !self.is_owned_by_current_program() {
1149            return Err(InstructionError::ExternalAccountDataModified);
1150        }
1151        Ok(())
1152    }
1153
1154    /// Returns an error if the account data can not be resized to the given length
1155    #[cfg(not(target_os = "solana"))]
1156    pub fn can_data_be_resized(&self, new_length: usize) -> Result<(), InstructionError> {
1157        let old_length = self.get_data().len();
1158        // Only the owner can change the length of the data
1159        if new_length != old_length && !self.is_owned_by_current_program() {
1160            return Err(InstructionError::AccountDataSizeChanged);
1161        }
1162        // The new length can not exceed the maximum permitted length
1163        if new_length > MAX_PERMITTED_DATA_LENGTH as usize {
1164            return Err(InstructionError::InvalidRealloc);
1165        }
1166        // The resize can not exceed the per-transaction maximum
1167        let length_delta = (new_length as i64).saturating_sub(old_length as i64);
1168        if self
1169            .transaction_context
1170            .accounts_resize_delta()?
1171            .saturating_add(length_delta)
1172            > MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION
1173        {
1174            return Err(InstructionError::MaxAccountsDataAllocationsExceeded);
1175        }
1176        Ok(())
1177    }
1178
1179    #[cfg(not(target_os = "solana"))]
1180    fn touch(&self) -> Result<(), InstructionError> {
1181        self.transaction_context
1182            .accounts()
1183            .touch(self.index_in_transaction)
1184    }
1185
1186    #[cfg(not(target_os = "solana"))]
1187    fn update_accounts_resize_delta(&mut self, new_len: usize) -> Result<(), InstructionError> {
1188        let mut accounts_resize_delta = self
1189            .transaction_context
1190            .accounts_resize_delta
1191            .try_borrow_mut()
1192            .map_err(|_| InstructionError::GenericError)?;
1193        *accounts_resize_delta = accounts_resize_delta
1194            .saturating_add((new_len as i64).saturating_sub(self.get_data().len() as i64));
1195        Ok(())
1196    }
1197}
1198
1199/// Everything that needs to be recorded from a TransactionContext after execution
1200#[cfg(not(target_os = "solana"))]
1201pub struct ExecutionRecord {
1202    pub accounts: Vec<TransactionAccount>,
1203    pub return_data: TransactionReturnData,
1204    pub touched_account_count: u64,
1205    pub accounts_resize_delta: i64,
1206}
1207
1208/// Used by the bank in the runtime to write back the processed accounts and recorded instructions
1209#[cfg(not(target_os = "solana"))]
1210impl From<TransactionContext> for ExecutionRecord {
1211    fn from(context: TransactionContext) -> Self {
1212        let accounts = Rc::try_unwrap(context.accounts)
1213            .expect("transaction_context.accounts has unexpected outstanding refs");
1214        let touched_account_count = accounts.touched_count() as u64;
1215        let accounts = accounts.into_accounts();
1216        Self {
1217            accounts: Vec::from(Pin::into_inner(context.account_keys))
1218                .into_iter()
1219                .zip(accounts)
1220                .collect(),
1221            return_data: context.return_data,
1222            touched_account_count,
1223            accounts_resize_delta: RefCell::into_inner(context.accounts_resize_delta),
1224        }
1225    }
1226}
1227
1228#[cfg(not(target_os = "solana"))]
1229fn is_zeroed(buf: &[u8]) -> bool {
1230    const ZEROS_LEN: usize = 1024;
1231    const ZEROS: [u8; ZEROS_LEN] = [0; ZEROS_LEN];
1232    let mut chunks = buf.chunks_exact(ZEROS_LEN);
1233
1234    #[allow(clippy::indexing_slicing)]
1235    {
1236        chunks.all(|chunk| chunk == &ZEROS[..])
1237            && chunks.remainder() == &ZEROS[..chunks.remainder().len()]
1238    }
1239}