miraland_sdk/
transaction_context.rs

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