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