Skip to main content

hpsvm/
types.rs

1use solana_account::{Account, AccountSharedData};
2use solana_address::Address;
3use solana_instruction::{Instruction, account_meta::AccountMeta, error::InstructionError};
4use solana_message::inner_instruction::InnerInstructionsList;
5use solana_program_error::ProgramError;
6use solana_signature::Signature;
7use solana_transaction_context::TransactionReturnData;
8use solana_transaction_error::{TransactionError, TransactionResult as Result};
9
10use crate::{error::HPSVMError, format_logs::format_logs};
11
12#[expect(missing_docs)]
13#[derive(Debug, Default, Clone, PartialEq, Eq)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15pub struct TransactionMetadata {
16    #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde_with_str"))]
17    pub signature: Signature,
18    pub logs: Vec<String>,
19    pub inner_instructions: InnerInstructionsList,
20    pub compute_units_consumed: u64,
21    pub return_data: TransactionReturnData,
22    pub fee: u64,
23    #[cfg_attr(feature = "serde", serde(default))]
24    pub diagnostics: ExecutionDiagnostics,
25}
26
27impl TransactionMetadata {
28    #[expect(missing_docs)]
29    pub fn pretty_logs(&self) -> String {
30        format_logs(&self.logs)
31    }
32}
33
34/// Structured execution details captured alongside transaction metadata.
35#[expect(missing_docs)]
36#[derive(Debug, Default, Clone, PartialEq, Eq)]
37#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
38#[non_exhaustive]
39pub struct ExecutionDiagnostics {
40    pub pre_balances: Vec<u64>,
41    pub post_balances: Vec<u64>,
42    pub account_diffs: Vec<AccountDiff>,
43    pub account_source_failures: Vec<AccountSourceFailure>,
44    pub pre_token_balances: Vec<TokenBalance>,
45    pub post_token_balances: Vec<TokenBalance>,
46    pub execution_trace: ExecutionTrace,
47}
48
49/// External account source failures observed while preparing execution.
50#[expect(missing_docs)]
51#[derive(Debug, Clone, PartialEq, Eq)]
52#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
53#[non_exhaustive]
54pub struct AccountSourceFailure {
55    pub pubkey: Address,
56    pub error: String,
57}
58
59/// Pre/post account state for a writable account touched by execution.
60#[expect(missing_docs)]
61#[derive(Debug, Clone, PartialEq, Eq)]
62#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
63#[non_exhaustive]
64pub struct AccountDiff {
65    pub address: Address,
66    pub pre: Option<Account>,
67    pub post: Option<Account>,
68}
69
70/// SPL token balance metadata for a token account present in execution diagnostics.
71#[expect(missing_docs)]
72#[derive(Debug, Clone, PartialEq, Eq)]
73#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
74#[non_exhaustive]
75pub struct TokenBalance {
76    pub account_index: usize,
77    pub address: Address,
78    pub mint: Address,
79    pub owner: Address,
80    pub amount: u64,
81    pub decimals: Option<u8>,
82}
83
84/// Instruction trace frames captured directly from the Solana transaction context.
85#[expect(missing_docs)]
86#[derive(Debug, Default, Clone, PartialEq, Eq)]
87#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
88#[non_exhaustive]
89pub struct ExecutionTrace {
90    pub instructions: Vec<ExecutedInstruction>,
91}
92
93/// One executed top-level or CPI instruction.
94#[expect(missing_docs)]
95#[derive(Debug, Clone, PartialEq, Eq)]
96#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
97#[non_exhaustive]
98pub struct ExecutedInstruction {
99    pub stack_height: u8,
100    pub program_id: Address,
101    pub accounts: Vec<AccountMeta>,
102    pub data: Vec<u8>,
103}
104
105impl ExecutedInstruction {
106    /// Returns this trace frame as a normal Solana instruction.
107    #[must_use]
108    pub fn instruction(&self) -> Instruction {
109        Instruction {
110            program_id: self.program_id,
111            accounts: self.accounts.clone(),
112            data: self.data.clone(),
113        }
114    }
115}
116
117/// Result of [`crate::HPSVM::transact`].
118///
119/// Each outcome is tied to the VM instance and state version that produced it.
120/// [`crate::HPSVM::commit_transaction`] only accepts outcomes from that same
121/// instance before any intervening state or config mutation. Otherwise commit
122/// returns `ResanitizationNeeded`.
123///
124/// The provenance fields stay internal and are skipped from serialization, so
125/// serialized outcomes are observational only and cannot be committed on a
126/// foreign or later-mutated VM.
127#[must_use = "call HPSVM::commit_transaction to apply this outcome to the VM"]
128#[derive(Debug, PartialEq, Eq)]
129#[cfg_attr(feature = "serde", derive(serde::Serialize))]
130pub struct ExecutionOutcome {
131    pub(crate) meta: TransactionMetadata,
132    pub(crate) post_accounts: Vec<(Address, AccountSharedData)>,
133    pub(crate) status: Result<()>,
134    pub(crate) included: bool,
135    #[cfg_attr(feature = "serde", serde(skip_serializing))]
136    pub(crate) origin_vm_instance_id: u64,
137    #[cfg_attr(feature = "serde", serde(skip_serializing))]
138    pub(crate) origin_state_version: u64,
139    #[cfg_attr(feature = "serde", serde(skip_serializing, skip_deserializing))]
140    pub(crate) fee_payer: Option<Address>,
141}
142
143impl ExecutionOutcome {
144    /// Returns the transaction metadata captured during execution.
145    pub fn meta(&self) -> &TransactionMetadata {
146        &self.meta
147    }
148
149    /// Returns the writable post-execution account snapshot captured by `transact`.
150    pub fn post_accounts(&self) -> &[(Address, AccountSharedData)] {
151        &self.post_accounts
152    }
153
154    /// Returns the execution status that `commit_transaction` will commit if
155    /// the outcome provenance still matches the target VM.
156    pub fn status(&self) -> &Result<()> {
157        &self.status
158    }
159
160    /// Returns whether this outcome is eligible for commit-time side effects.
161    pub fn included(&self) -> bool {
162        self.included
163    }
164}
165
166#[expect(missing_docs)]
167#[derive(Debug, Default, Clone, PartialEq, Eq)]
168#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
169pub struct SimulatedTransactionInfo {
170    #[expect(missing_docs)]
171    pub meta: TransactionMetadata,
172    #[expect(missing_docs)]
173    pub post_accounts: Vec<(Address, AccountSharedData)>,
174}
175
176#[expect(missing_docs)]
177#[derive(Debug, Clone, PartialEq, Eq)]
178#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
179pub struct FailedTransactionMetadata {
180    #[expect(missing_docs)]
181    pub err: TransactionError,
182    #[expect(missing_docs)]
183    pub meta: TransactionMetadata,
184}
185
186impl From<ProgramError> for FailedTransactionMetadata {
187    fn from(value: ProgramError) -> Self {
188        Self {
189            err: TransactionError::InstructionError(
190                0,
191                InstructionError::Custom(u64::from(value) as u32),
192            ),
193            meta: Default::default(),
194        }
195    }
196}
197
198#[expect(missing_docs)]
199pub type TransactionResult = std::result::Result<TransactionMetadata, FailedTransactionMetadata>;
200
201pub(crate) struct ExecutionResult {
202    pub(crate) post_accounts: Vec<(Address, AccountSharedData)>,
203    pub(crate) tx_result: Result<()>,
204    pub(crate) signature: Signature,
205    pub(crate) compute_units_consumed: u64,
206    pub(crate) inner_instructions: InnerInstructionsList,
207    pub(crate) return_data: TransactionReturnData,
208    pub(crate) execution_trace: ExecutionTrace,
209    /// Whether the transaction can be included in a block
210    pub(crate) included: bool,
211    pub(crate) fee: u64,
212    pub(crate) fee_payer: Option<Address>,
213    pub(crate) account_source_failures: Vec<AccountSourceFailure>,
214    pub(crate) fatal_error: Option<HPSVMError>,
215}
216
217impl Default for ExecutionResult {
218    fn default() -> Self {
219        Self {
220            post_accounts: Default::default(),
221            tx_result: Err(TransactionError::UnsupportedVersion),
222            signature: Default::default(),
223            compute_units_consumed: Default::default(),
224            inner_instructions: Default::default(),
225            return_data: Default::default(),
226            execution_trace: Default::default(),
227            included: false,
228            fee: 0,
229            fee_payer: None,
230            account_source_failures: Default::default(),
231            fatal_error: None,
232        }
233    }
234}