gmsol_decode/decoder/decoder_impl/
solana_decoder.rs

1use std::collections::{HashMap, HashSet};
2
3use anchor_lang::prelude::{event::EVENT_IX_TAG_LE, AccountMeta};
4use solana_sdk::{
5    instruction::CompiledInstruction, message::v0::MessageAddressTableLookup, pubkey::Pubkey,
6    signature::Signature, transaction::VersionedTransaction,
7};
8use solana_transaction_status_client_types::{
9    option_serializer::OptionSerializer, EncodedTransactionWithStatusMeta, UiInstruction,
10    UiTransactionStatusMeta,
11};
12
13use crate::{Decode, DecodeError, Decoder, Visitor};
14
15pub use solana_transaction_status_client_types as solana_transaction_status;
16
17/// Transaction Decoder.
18pub struct TransactionDecoder<'a> {
19    slot: u64,
20    signature: Signature,
21    transaction: &'a EncodedTransactionWithStatusMeta,
22    cpi_event_filter: CPIEventFilter,
23}
24
25impl<'a> TransactionDecoder<'a> {
26    /// Create a new transaction decoder.
27    pub fn new(
28        slot: u64,
29        signature: Signature,
30        transaction: &'a EncodedTransactionWithStatusMeta,
31    ) -> Self {
32        Self {
33            slot,
34            signature,
35            transaction,
36            cpi_event_filter: CPIEventFilter {
37                map: Default::default(),
38            },
39        }
40    }
41
42    /// Add a Program ID to the CPI Event filter.
43    pub fn add_cpi_event_program_id(
44        &mut self,
45        program_id: &Pubkey,
46    ) -> Result<&mut Self, DecodeError> {
47        self.cpi_event_filter.add(program_id)?;
48        Ok(self)
49    }
50
51    /// Add a Event authority and its Program ID to the CPI Event filter.
52    pub fn add_cpi_event_authority_and_program_id(
53        &mut self,
54        event_authority: Pubkey,
55        program_id: Pubkey,
56    ) -> Result<&mut Self, DecodeError> {
57        self.cpi_event_filter
58            .add_event_authority_and_program_id(event_authority, program_id)?;
59        Ok(self)
60    }
61
62    /// Set CPI events filter.
63    pub fn set_cpi_event_filter(&mut self, filter: CPIEventFilter) -> &mut Self {
64        self.cpi_event_filter = filter;
65        self
66    }
67
68    /// Get signature.
69    pub fn signature(&self) -> Signature {
70        self.signature
71    }
72
73    /// Get slot.
74    pub fn slot(&self) -> u64 {
75        self.slot
76    }
77
78    /// Get transaction.
79    pub fn transaction(&self) -> &EncodedTransactionWithStatusMeta {
80        self.transaction
81    }
82
83    /// Decode transaction.
84    pub fn decoded_transaction(&self) -> Result<DecodedTransaction, DecodeError> {
85        let tx = self.transaction;
86        let slot_index = (self.slot, None);
87        let Some(decoded) = tx.transaction.decode() else {
88            return Err(DecodeError::custom("failed to decode transaction"));
89        };
90        let Some(meta) = &tx.meta else {
91            return Err(DecodeError::custom("missing meta"));
92        };
93
94        let (dynamic_writable_accounts, dynamic_readonly_accounts) = match &meta.loaded_addresses {
95            OptionSerializer::Some(loaded) => {
96                let dynamic_writable_accounts = loaded
97                    .writable
98                    .iter()
99                    .map(|address| address.parse().map_err(DecodeError::custom))
100                    .collect::<Result<Vec<_>, _>>()?;
101                let dynamic_readonly_accounts = loaded
102                    .readonly
103                    .iter()
104                    .map(|address| address.parse().map_err(DecodeError::custom))
105                    .collect::<Result<Vec<_>, _>>()?;
106                (dynamic_writable_accounts, dynamic_readonly_accounts)
107            }
108            OptionSerializer::None | OptionSerializer::Skip => Default::default(),
109        };
110
111        Ok(DecodedTransaction {
112            signature: self.signature,
113            slot_index,
114            transaction: decoded,
115            dynamic_writable_accounts,
116            dynamic_readonly_accounts,
117            transaction_status_meta: meta,
118        })
119    }
120
121    /// Extract CPI events.
122    pub fn extract_cpi_events(&self) -> Result<CPIEvents, DecodeError> {
123        self.decoded_transaction()?
124            .extract_cpi_events(&self.cpi_event_filter)
125    }
126}
127
128impl Decoder for TransactionDecoder<'_> {
129    fn decode_account<V>(&self, _visitor: V) -> Result<V::Value, DecodeError>
130    where
131        V: Visitor,
132    {
133        Err(DecodeError::custom(
134            "Expecting `Account` but found `Transaction`",
135        ))
136    }
137
138    fn decode_transaction<V>(&self, visitor: V) -> Result<V::Value, DecodeError>
139    where
140        V: Visitor,
141    {
142        visitor.visit_transaction(self.decoded_transaction()?)
143    }
144
145    fn decode_anchor_cpi_events<V>(&self, visitor: V) -> Result<V::Value, DecodeError>
146    where
147        V: Visitor,
148    {
149        visitor.visit_anchor_cpi_events(self.extract_cpi_events()?.access())
150    }
151
152    fn decode_owned_data<V>(&self, _visitor: V) -> Result<V::Value, DecodeError>
153    where
154        V: Visitor,
155    {
156        Err(DecodeError::custom(
157            "cannot access ownedd data directly of a transaction",
158        ))
159    }
160
161    fn decode_bytes<V>(&self, _visitor: V) -> Result<V::Value, DecodeError>
162    where
163        V: Visitor,
164    {
165        Err(DecodeError::custom(
166            "cannot access bytes directly of a transaction",
167        ))
168    }
169}
170
171/// CPI Event filter.
172#[derive(Debug, Clone, Default)]
173pub struct CPIEventFilter {
174    /// A mapping from event authority to its program id.
175    map: HashMap<Pubkey, Pubkey>,
176}
177
178impl CPIEventFilter {
179    /// Subscribe to CPI Event from the given program.
180    pub fn add(&mut self, program_id: &Pubkey) -> Result<&mut Self, DecodeError> {
181        let event_authority = find_event_authority_address(program_id);
182        self.add_event_authority_and_program_id(event_authority, *program_id)
183    }
184
185    /// Add event authority and its program id directly.
186    pub fn add_event_authority_and_program_id(
187        &mut self,
188        event_authority: Pubkey,
189        program_id: Pubkey,
190    ) -> Result<&mut Self, DecodeError> {
191        if let Some(previous) = self.map.insert(event_authority, program_id) {
192            // This should be rare, but if a collision does occur, an error will be thrown.
193            if previous != program_id {
194                return Err(DecodeError::custom(format!(
195                    "event authority collision, previous={previous}, current={program_id}"
196                )));
197            }
198        }
199        Ok(self)
200    }
201
202    /// Get event authorities.
203    pub fn event_authorities(&self) -> impl Iterator<Item = &Pubkey> {
204        self.map.keys()
205    }
206
207    /// Get programs.
208    pub fn programs(&self) -> impl Iterator<Item = &Pubkey> {
209        self.map.values()
210    }
211}
212
213/// CPI Event decoder.
214pub struct CPIEvent {
215    program_id: Pubkey,
216    data: Vec<u8>,
217}
218
219impl CPIEvent {
220    /// Create a new [`CPIEvent`] decoder.
221    pub fn new(program_id: Pubkey, data: Vec<u8>) -> Self {
222        Self { program_id, data }
223    }
224}
225
226impl Decoder for &CPIEvent {
227    fn decode_account<V>(&self, _visitor: V) -> Result<V::Value, DecodeError>
228    where
229        V: Visitor,
230    {
231        Err(DecodeError::InvalidType(
232            "Expecting `Account` but found `CPIEvent`".to_string(),
233        ))
234    }
235
236    fn decode_transaction<V>(&self, _visitor: V) -> Result<V::Value, DecodeError>
237    where
238        V: Visitor,
239    {
240        Err(DecodeError::InvalidType(
241            "Expecting `Transaction` but found `CPIEvent`".to_string(),
242        ))
243    }
244
245    fn decode_anchor_cpi_events<V>(&self, _visitor: V) -> Result<V::Value, DecodeError>
246    where
247        V: Visitor,
248    {
249        Err(DecodeError::InvalidType(
250            "Expecting `AnchorCPIEvents` but found `CPIEvent`".to_string(),
251        ))
252    }
253
254    fn decode_owned_data<V>(&self, visitor: V) -> Result<V::Value, DecodeError>
255    where
256        V: Visitor,
257    {
258        visitor.visit_owned_data(&self.program_id, &self.data)
259    }
260
261    fn decode_bytes<V>(&self, visitor: V) -> Result<V::Value, DecodeError>
262    where
263        V: Visitor,
264    {
265        visitor.visit_bytes(&self.data)
266    }
267}
268
269/// Slot and index.
270pub type SlotAndIndex = (u64, Option<usize>);
271
272/// CPI Events.
273pub struct CPIEvents {
274    /// Signature.
275    pub signature: Signature,
276    /// Slot and index.
277    pub slot_index: SlotAndIndex,
278    /// CPI Events.
279    pub events: Vec<CPIEvent>,
280}
281
282impl CPIEvents {
283    /// Access CPI Events.
284    pub fn access(&self) -> AccessCPIEvents {
285        AccessCPIEvents {
286            signature: &self.signature,
287            slot_index: &self.slot_index,
288            events: self.events.iter(),
289        }
290    }
291}
292
293/// Access CPI Events.
294pub struct AccessCPIEvents<'a> {
295    signature: &'a Signature,
296    slot_index: &'a SlotAndIndex,
297    events: std::slice::Iter<'a, CPIEvent>,
298}
299
300impl<'a> AccessCPIEvents<'a> {
301    /// Create a new access for CPI Events.
302    pub fn new(
303        signature: &'a Signature,
304        slot_index: &'a SlotAndIndex,
305        events: &'a [CPIEvent],
306    ) -> Self {
307        Self {
308            signature,
309            slot_index,
310            events: events.iter(),
311        }
312    }
313}
314
315impl<'a> crate::AnchorCPIEventsAccess<'a> for AccessCPIEvents<'a> {
316    fn slot(&self) -> Result<u64, DecodeError> {
317        Ok(self.slot_index.0)
318    }
319
320    fn index(&self) -> Result<Option<usize>, DecodeError> {
321        Ok(self.slot_index.1)
322    }
323
324    fn signature(&self) -> Result<&Signature, DecodeError> {
325        Ok(self.signature)
326    }
327
328    fn next_event<T>(&mut self) -> Result<Option<T>, DecodeError>
329    where
330        T: Decode,
331    {
332        let Some(decoder) = self.events.next() else {
333            return Ok(None);
334        };
335        T::decode(decoder).map(Some)
336    }
337}
338
339const EVENT_AUTHORITY_SEED: &[u8] = b"__event_authority";
340
341fn find_event_authority_address(program_id: &Pubkey) -> Pubkey {
342    Pubkey::find_program_address(&[EVENT_AUTHORITY_SEED], program_id).0
343}
344
345/// Decoded Transaction.
346pub struct DecodedTransaction<'a> {
347    /// Signature.
348    pub signature: Signature,
349    /// Slot and index.
350    pub slot_index: SlotAndIndex,
351    /// Transaction.
352    pub transaction: VersionedTransaction,
353    /// Dynamic writable accounts.
354    pub dynamic_writable_accounts: Vec<Pubkey>,
355    /// Dynamic read-only accounts.
356    pub dynamic_readonly_accounts: Vec<Pubkey>,
357    /// Transaction status meta.
358    pub transaction_status_meta: &'a UiTransactionStatusMeta,
359}
360
361impl DecodedTransaction<'_> {
362    /// Extract Anchor CPI events.
363    pub fn extract_cpi_events(
364        &self,
365        cpi_event_filter: &CPIEventFilter,
366    ) -> Result<CPIEvents, DecodeError> {
367        let mut event_authority_indices = HashMap::<_, HashSet<u8>>::default();
368        let mut accounts = self.transaction.message.static_account_keys().to_vec();
369        accounts.extend_from_slice(&self.dynamic_writable_accounts);
370        accounts.extend_from_slice(&self.dynamic_readonly_accounts);
371        tracing::debug!("accounts: {accounts:#?}");
372        let map = &cpi_event_filter.map;
373        for res in accounts
374            .iter()
375            .enumerate()
376            .filter(|(_, key)| map.contains_key(key))
377            .map(|(idx, key)| u8::try_from(idx).map(|idx| (map.get(key).unwrap(), idx)))
378        {
379            let (pubkey, idx) = res.map_err(|_| DecodeError::custom("invalid account keys"))?;
380            event_authority_indices
381                .entry(pubkey)
382                .or_default()
383                .insert(idx);
384        }
385        tracing::debug!("event_authorities: {event_authority_indices:#?}");
386        let Some(ixs) =
387            Option::<&Vec<_>>::from(self.transaction_status_meta.inner_instructions.as_ref())
388        else {
389            return Err(DecodeError::custom("missing inner instructions"));
390        };
391        let mut events = Vec::default();
392        for ix in ixs.iter().flat_map(|ixs| &ixs.instructions) {
393            let UiInstruction::Compiled(ix) = ix else {
394                tracing::warn!("only compiled instruction is currently supported");
395                continue;
396            };
397            // NOTE: we are currently assuming that the Event CPI has only the event authority in the account list.
398            if ix.accounts.len() != 1 {
399                continue;
400            }
401            if let Some(program_id) = accounts.get(ix.program_id_index as usize) {
402                let Some(indexes) = event_authority_indices.get(program_id) else {
403                    continue;
404                };
405                let data = bs58::decode(&ix.data)
406                    .into_vec()
407                    .map_err(|err| {
408                        DecodeError::custom(format!("decode ix data error, err={err}. Note that currently only Base58 is supported"))
409                    })?;
410                if indexes.contains(&ix.accounts[0]) && data.starts_with(EVENT_IX_TAG_LE) {
411                    events.push(CPIEvent::new(*program_id, data));
412                }
413            }
414        }
415        Ok(CPIEvents {
416            signature: self.signature,
417            slot_index: self.slot_index,
418            events,
419        })
420    }
421}
422
423impl crate::TransactionAccess for DecodedTransaction<'_> {
424    fn slot(&self) -> Result<u64, DecodeError> {
425        Ok(self.slot_index.0)
426    }
427
428    fn index(&self) -> Result<Option<usize>, DecodeError> {
429        Ok(self.slot_index.1)
430    }
431
432    fn signature(&self) -> Result<&Signature, DecodeError> {
433        Ok(&self.signature)
434    }
435
436    fn num_signers(&self, is_writable: bool) -> Result<usize, DecodeError> {
437        let header = self.transaction.message.header();
438        if is_writable {
439            (header.num_required_signatures as usize)
440                .checked_sub(self.num_signers(false)?)
441                .ok_or_else(|| {
442                    DecodeError::custom(
443                        "invalid transaction message header: num_signed < num_readonly_signed",
444                    )
445                })
446        } else {
447            Ok(header.num_readonly_signed_accounts as usize)
448        }
449    }
450
451    fn num_accounts(&self) -> usize {
452        self.transaction.message.static_account_keys().len()
453            + self.dynamic_writable_accounts.len()
454            + self.dynamic_readonly_accounts.len()
455    }
456
457    fn message_signature(&self, idx: usize) -> Option<&Signature> {
458        self.transaction.signatures.get(idx)
459    }
460
461    fn account_meta(&self, idx: usize) -> Result<Option<AccountMeta>, DecodeError> {
462        let static_accounts = self.transaction.message.static_account_keys();
463        let static_end = static_accounts.len();
464        let dynamic_writable_length = self.dynamic_writable_accounts.len();
465        let dynamic_readonly_length = self.dynamic_readonly_accounts.len();
466        let dynamic_writable_end = static_end + dynamic_writable_length;
467        let dynamic_end = dynamic_writable_end + dynamic_readonly_length;
468        let meta = if idx >= dynamic_end {
469            None
470        } else if idx >= dynamic_writable_end {
471            let idx = idx - dynamic_writable_end;
472            Some(AccountMeta {
473                pubkey: self.dynamic_readonly_accounts[idx],
474                is_signer: false,
475                is_writable: false,
476            })
477        } else if idx >= static_end {
478            let idx = idx - static_end;
479            Some(AccountMeta {
480                pubkey: self.dynamic_writable_accounts[idx],
481                is_signer: false,
482                is_writable: true,
483            })
484        } else {
485            let num_readonly_signed = self.num_signers(false)?;
486            let num_readonly_unsigned = self
487                .transaction
488                .message
489                .header()
490                .num_readonly_unsigned_accounts as usize;
491            let writable_signed_end = self.num_signers(true)?;
492            let readonly_signed_end = writable_signed_end + num_readonly_signed;
493            let writable_unsigend_end = static_end.checked_sub(num_readonly_unsigned).ok_or_else(|| {
494               DecodeError::custom("invalid transaction message header: static_end < num_sigend + num_readonly_signed") 
495            })?;
496            let (is_signer, is_writable) = if idx >= writable_unsigend_end {
497                (false, false)
498            } else if idx >= readonly_signed_end {
499                (false, true)
500            } else if idx >= writable_signed_end {
501                (true, false)
502            } else {
503                (true, true)
504            };
505            Some(AccountMeta {
506                pubkey: static_accounts[idx],
507                is_signer,
508                is_writable,
509            })
510        };
511        Ok(meta)
512    }
513
514    fn num_address_table_lookups(&self) -> usize {
515        self.transaction
516            .message
517            .address_table_lookups()
518            .map(|atls| atls.len())
519            .unwrap_or_default()
520    }
521
522    fn address_table_lookup(&self, idx: usize) -> Option<&MessageAddressTableLookup> {
523        self.transaction.message.address_table_lookups()?.get(idx)
524    }
525
526    fn num_instructions(&self) -> usize {
527        self.transaction.message.instructions().len()
528    }
529
530    fn instruction(&self, idx: usize) -> Option<&CompiledInstruction> {
531        self.transaction.message.instructions().get(idx)
532    }
533
534    fn transaction_status_meta(&self) -> Option<&UiTransactionStatusMeta> {
535        Some(self.transaction_status_meta)
536    }
537}