Skip to main content

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