solana_transaction_context/
lib.rs

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