light_instruction_decoder/
types.rs

1//! Type definitions for enhanced logging
2//!
3//! This module contains all the data types used for instruction decoding
4//! and transaction logging. These types are independent of any test framework
5//! (LiteSVM, etc.) and can be used in standalone tools.
6
7use std::collections::HashMap;
8
9use solana_instruction::AccountMeta;
10use solana_pubkey::Pubkey;
11use solana_signature::Signature;
12
13use crate::{DecodedInstruction, DecoderRegistry, EnhancedLoggingConfig};
14
15/// Pre and post transaction account state snapshot
16#[derive(Debug, Clone, Default)]
17pub struct AccountStateSnapshot {
18    pub lamports_before: u64,
19    pub lamports_after: u64,
20    pub data_len_before: usize,
21    pub data_len_after: usize,
22}
23
24/// Enhanced transaction log containing all formatting information
25#[derive(Debug, Clone)]
26pub struct EnhancedTransactionLog {
27    pub signature: Signature,
28    pub slot: u64,
29    pub status: TransactionStatus,
30    pub fee: u64,
31    pub compute_used: u64,
32    pub compute_total: u64,
33    pub instructions: Vec<EnhancedInstructionLog>,
34    pub account_changes: Vec<AccountChange>,
35    pub program_logs_pretty: String,
36    pub light_events: Vec<LightProtocolEvent>,
37    /// Pre and post transaction account state snapshots (keyed by pubkey)
38    pub account_states: Option<HashMap<Pubkey, AccountStateSnapshot>>,
39}
40
41impl EnhancedTransactionLog {
42    /// Create a new empty transaction log with basic info
43    pub fn new(signature: Signature, slot: u64) -> Self {
44        Self {
45            signature,
46            slot,
47            status: TransactionStatus::Unknown,
48            fee: 0,
49            compute_used: 0,
50            compute_total: 1_400_000,
51            instructions: Vec::new(),
52            account_changes: Vec::new(),
53            program_logs_pretty: String::new(),
54            light_events: Vec::new(),
55            account_states: None,
56        }
57    }
58}
59
60/// Transaction execution status
61#[derive(Debug, Clone)]
62pub enum TransactionStatus {
63    Success,
64    Failed(String),
65    Unknown,
66}
67
68impl TransactionStatus {
69    pub fn text(&self) -> String {
70        match self {
71            TransactionStatus::Success => "Success".to_string(),
72            TransactionStatus::Failed(err) => format!("Failed: {}", err),
73            TransactionStatus::Unknown => "Unknown".to_string(),
74        }
75    }
76}
77
78/// Enhanced instruction log with hierarchy and parsing
79#[derive(Debug, Clone)]
80pub struct EnhancedInstructionLog {
81    pub index: usize,
82    pub program_id: Pubkey,
83    pub program_name: String,
84    pub instruction_name: Option<String>,
85    pub accounts: Vec<AccountMeta>,
86    pub data: Vec<u8>,
87    /// Decoded instruction from custom decoder (if available)
88    pub decoded_instruction: Option<DecodedInstruction>,
89    pub inner_instructions: Vec<EnhancedInstructionLog>,
90    pub compute_consumed: Option<u64>,
91    pub success: bool,
92    pub depth: usize,
93}
94
95impl EnhancedInstructionLog {
96    /// Create a new instruction log
97    pub fn new(index: usize, program_id: Pubkey, program_name: String) -> Self {
98        Self {
99            index,
100            program_id,
101            program_name,
102            instruction_name: None,
103            accounts: Vec::new(),
104            data: Vec::new(),
105            decoded_instruction: None,
106            inner_instructions: Vec::new(),
107            compute_consumed: None,
108            success: true,
109            depth: 0,
110        }
111    }
112
113    /// Decode this instruction using the provided config's decoder registry
114    pub fn decode(&mut self, config: &EnhancedLoggingConfig) {
115        if !config.decode_light_instructions {
116            return;
117        }
118
119        // Try the decoder registry (includes custom decoders)
120        if let Some(registry) = config.decoder_registry() {
121            if let Some((decoded, decoder)) =
122                registry.decode(&self.program_id, &self.data, &self.accounts)
123            {
124                self.instruction_name = Some(decoded.name.clone());
125                self.decoded_instruction = Some(decoded);
126                self.program_name = decoder.program_name().to_string();
127            }
128        }
129    }
130
131    /// Find parent instruction at target depth for nesting
132    pub fn find_parent_for_instruction(
133        instructions: &mut [EnhancedInstructionLog],
134        target_depth: usize,
135    ) -> Option<&mut EnhancedInstructionLog> {
136        for instruction in instructions.iter_mut().rev() {
137            if instruction.depth == target_depth {
138                return Some(instruction);
139            }
140            if let Some(parent) =
141                Self::find_parent_for_instruction(&mut instruction.inner_instructions, target_depth)
142            {
143                return Some(parent);
144            }
145        }
146        None
147    }
148}
149
150/// Account state changes during transaction
151#[derive(Debug, Clone)]
152pub struct AccountChange {
153    pub pubkey: Pubkey,
154    pub account_type: String,
155    pub access: AccountAccess,
156    pub account_index: usize,
157    pub lamports_before: u64,
158    pub lamports_after: u64,
159    pub data_len_before: usize,
160    pub data_len_after: usize,
161    pub owner: Pubkey,
162    pub executable: bool,
163    pub rent_epoch: u64,
164}
165
166/// Account access pattern during transaction
167#[derive(Debug, Clone, Copy, PartialEq, Eq)]
168pub enum AccountAccess {
169    Readonly,
170    Writable,
171    Signer,
172    SignerWritable,
173}
174
175impl AccountAccess {
176    pub fn symbol(&self, index: usize) -> String {
177        format!("#{}", index)
178    }
179
180    pub fn text(&self) -> &'static str {
181        match self {
182            AccountAccess::Readonly => "readonly",
183            AccountAccess::Writable => "writable",
184            AccountAccess::Signer => "signer",
185            AccountAccess::SignerWritable => "signer+writable",
186        }
187    }
188}
189
190/// Light Protocol specific events
191#[derive(Debug, Clone)]
192pub struct LightProtocolEvent {
193    pub event_type: String,
194    pub compressed_accounts: Vec<CompressedAccountInfo>,
195    pub merkle_tree_changes: Vec<MerkleTreeChange>,
196    pub nullifiers: Vec<String>,
197}
198
199/// Compressed account information
200#[derive(Debug, Clone)]
201pub struct CompressedAccountInfo {
202    pub hash: String,
203    pub owner: Pubkey,
204    pub lamports: u64,
205    pub data: Option<Vec<u8>>,
206    pub address: Option<String>,
207}
208
209/// Merkle tree state change
210#[derive(Debug, Clone)]
211pub struct MerkleTreeChange {
212    pub tree_pubkey: Pubkey,
213    pub tree_type: String,
214    pub sequence_number: u64,
215    pub leaf_index: u64,
216}
217
218/// Get human-readable program name from pubkey
219///
220/// First consults the decoder registry if provided, then falls back to hardcoded mappings.
221pub fn get_program_name(program_id: &Pubkey, registry: Option<&DecoderRegistry>) -> String {
222    // First try to get the name from the decoder registry
223    if let Some(reg) = registry {
224        if let Some(decoder) = reg.get_decoder(program_id) {
225            return decoder.program_name().to_string();
226        }
227    }
228
229    // Fall back to hardcoded mappings for programs without decoders
230    match program_id.to_string().as_str() {
231        "11111111111111111111111111111111" => "System Program".to_string(),
232        "ComputeBudget111111111111111111111111111111" => "Compute Budget".to_string(),
233        "SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7" => "Light System Program".to_string(),
234        "compr6CUsB5m2jS4Y3831ztGSTnDpnKJTKS95d64XVq" => "Account Compression".to_string(),
235        "cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m" => "Light Token Program".to_string(),
236        _ => format!("Unknown Program ({})", program_id),
237    }
238}