gmsol_decode/decoder/decoder_impl/
solana_decoder.rs

1use std::collections::{HashMap, HashSet};
2
3use anchor_lang::prelude::event::EVENT_IX_TAG_LE;
4use solana_sdk::{pubkey::Pubkey, signature::Signature};
5use solana_transaction_status::{
6    EncodedTransactionWithStatusMeta, UiInstruction, UiLoadedAddresses,
7};
8
9use crate::{Decode, DecodeError, Decoder, Visitor};
10
11pub use solana_transaction_status;
12
13/// Transaction Decoder.
14pub struct TransactionDecoder<'a> {
15    slot: u64,
16    signature: Signature,
17    transaction: &'a EncodedTransactionWithStatusMeta,
18    cpi_event_filter: CPIEventFilter,
19}
20
21impl<'a> TransactionDecoder<'a> {
22    /// Create a new transaction decoder.
23    pub fn new(
24        slot: u64,
25        signature: Signature,
26        transaction: &'a EncodedTransactionWithStatusMeta,
27    ) -> Self {
28        Self {
29            slot,
30            signature,
31            transaction,
32            cpi_event_filter: CPIEventFilter {
33                map: Default::default(),
34            },
35        }
36    }
37
38    /// Add a Program ID to the CPI Event filter.
39    pub fn add_cpi_event_program_id(
40        &mut self,
41        program_id: &Pubkey,
42    ) -> Result<&mut Self, DecodeError> {
43        self.cpi_event_filter.add(program_id)?;
44        Ok(self)
45    }
46
47    /// Add a Event authority and its Program ID to the CPI Event filter.
48    pub fn add_cpi_event_authority_and_program_id(
49        &mut self,
50        event_authority: Pubkey,
51        program_id: Pubkey,
52    ) -> Result<&mut Self, DecodeError> {
53        self.cpi_event_filter
54            .add_event_authority_and_program_id(event_authority, program_id)?;
55        Ok(self)
56    }
57
58    /// Set CPI events filter.
59    pub fn set_cpi_event_filter(&mut self, filter: CPIEventFilter) -> &mut Self {
60        self.cpi_event_filter = filter;
61        self
62    }
63
64    /// Get signature.
65    pub fn signature(&self) -> Signature {
66        self.signature
67    }
68
69    /// Get slot.
70    pub fn slot(&self) -> u64 {
71        self.slot
72    }
73
74    /// Get transaction.
75    pub fn transaction(&self) -> &EncodedTransactionWithStatusMeta {
76        self.transaction
77    }
78
79    /// Extract CPI events.
80    pub fn extract_cpi_events(&self) -> Result<CPIEvents, DecodeError> {
81        let tx = self.transaction;
82        let slot_index = (self.slot, None);
83        let Some(decoded) = tx.transaction.decode() else {
84            return Err(DecodeError::custom("failed to decode transaction"));
85        };
86        let Some(meta) = &tx.meta else {
87            return Err(DecodeError::custom("missing meta"));
88        };
89        let accounts = decoded.message.static_account_keys();
90        let loaded_addresses = Option::from(meta.loaded_addresses.clone())
91            .map(|mut loaded: UiLoadedAddresses| {
92                loaded.writable.append(&mut loaded.readonly);
93                loaded.writable
94            })
95            .unwrap_or_default();
96        let mut accounts = accounts.to_vec();
97        for address in loaded_addresses {
98            accounts.push(address.parse().map_err(DecodeError::custom)?);
99        }
100        tracing::debug!("accounts: {accounts:#?}");
101        let mut event_authority_indices = HashMap::<_, HashSet<u8>>::default();
102        let map = &self.cpi_event_filter.map;
103        for res in accounts
104            .iter()
105            .enumerate()
106            .filter(|(_, key)| map.contains_key(key))
107            .map(|(idx, key)| u8::try_from(idx).map(|idx| (map.get(key).unwrap(), idx)))
108        {
109            let (pubkey, idx) = res.map_err(|_| DecodeError::custom("invalid account keys"))?;
110            event_authority_indices
111                .entry(pubkey)
112                .or_default()
113                .insert(idx);
114        }
115        tracing::debug!("event_authorities: {event_authority_indices:#?}");
116        let Some(ixs) = Option::<&Vec<_>>::from(meta.inner_instructions.as_ref()) else {
117            return Err(DecodeError::custom("missing inner instructions"));
118        };
119        let mut events = Vec::default();
120        for ix in ixs.iter().flat_map(|ixs| &ixs.instructions) {
121            let UiInstruction::Compiled(ix) = ix else {
122                tracing::warn!("only compiled instruction is currently supported");
123                continue;
124            };
125            // NOTE: we are currently assuming that the Event CPI has only the event authority in the account list.
126            if ix.accounts.len() != 1 {
127                continue;
128            }
129            if let Some(program_id) = accounts.get(ix.program_id_index as usize) {
130                let Some(indexes) = event_authority_indices.get(program_id) else {
131                    continue;
132                };
133                let data = bs58::decode(&ix.data)
134                    .into_vec()
135                    .map_err(|err| {
136                        DecodeError::custom(format!("decode ix data error, err={err}. Note that currently only Base58 is supported"))
137                    })?;
138                if indexes.contains(&ix.accounts[0]) && data.starts_with(&EVENT_IX_TAG_LE) {
139                    events.push(CPIEvent::new(*program_id, data));
140                }
141            }
142        }
143        Ok(CPIEvents {
144            signature: self.signature,
145            slot_index,
146            events,
147        })
148    }
149}
150
151impl Decoder for TransactionDecoder<'_> {
152    fn decode_account<V>(&self, _visitor: V) -> Result<V::Value, DecodeError>
153    where
154        V: Visitor,
155    {
156        Err(DecodeError::custom(
157            "Expecting `Account` but found `Transaction`",
158        ))
159    }
160
161    fn decode_transaction<V>(&self, _visitor: V) -> Result<V::Value, DecodeError>
162    where
163        V: Visitor,
164    {
165        Err(DecodeError::custom(
166            "decode transaction is currently not supported",
167        ))
168    }
169
170    fn decode_anchor_cpi_events<V>(&self, visitor: V) -> Result<V::Value, DecodeError>
171    where
172        V: Visitor,
173    {
174        visitor.visit_anchor_cpi_events(self.extract_cpi_events()?.access())
175    }
176
177    fn decode_owned_data<V>(&self, _visitor: V) -> Result<V::Value, DecodeError>
178    where
179        V: Visitor,
180    {
181        Err(DecodeError::custom(
182            "cannot access ownedd data directly of a transaction",
183        ))
184    }
185
186    fn decode_bytes<V>(&self, _visitor: V) -> Result<V::Value, DecodeError>
187    where
188        V: Visitor,
189    {
190        Err(DecodeError::custom(
191            "cannot access bytes directly of a transaction",
192        ))
193    }
194}
195
196/// CPI Event filter.
197#[derive(Debug, Clone, Default)]
198pub struct CPIEventFilter {
199    /// A mapping from event authority to its program id.
200    map: HashMap<Pubkey, Pubkey>,
201}
202
203impl CPIEventFilter {
204    /// Subscribe to CPI Event from the given program.
205    pub fn add(&mut self, program_id: &Pubkey) -> Result<&mut Self, DecodeError> {
206        let event_authority = find_event_authority_address(program_id);
207        self.add_event_authority_and_program_id(event_authority, *program_id)
208    }
209
210    /// Add event authority and its program id directly.
211    pub fn add_event_authority_and_program_id(
212        &mut self,
213        event_authority: Pubkey,
214        program_id: Pubkey,
215    ) -> Result<&mut Self, DecodeError> {
216        if let Some(previous) = self.map.insert(event_authority, program_id) {
217            // This should be rare, but if a collision does occur, an error will be thrown.
218            if previous != program_id {
219                return Err(DecodeError::custom(format!(
220                    "event authority collision, previous={previous}, current={program_id}"
221                )));
222            }
223        }
224        Ok(self)
225    }
226
227    /// Get event authorities.
228    pub fn event_authorities(&self) -> impl Iterator<Item = &Pubkey> {
229        self.map.keys()
230    }
231
232    /// Get programs.
233    pub fn programs(&self) -> impl Iterator<Item = &Pubkey> {
234        self.map.values()
235    }
236}
237
238/// CPI Event decoder.
239pub struct CPIEvent {
240    program_id: Pubkey,
241    data: Vec<u8>,
242}
243
244impl CPIEvent {
245    /// Create a new [`CPIEvent`] decoder.
246    pub fn new(program_id: Pubkey, data: Vec<u8>) -> Self {
247        Self { program_id, data }
248    }
249}
250
251impl Decoder for &CPIEvent {
252    fn decode_account<V>(&self, _visitor: V) -> Result<V::Value, DecodeError>
253    where
254        V: Visitor,
255    {
256        Err(DecodeError::InvalidType(
257            "Expecting `Account` but found `CPIEvent`".to_string(),
258        ))
259    }
260
261    fn decode_transaction<V>(&self, _visitor: V) -> Result<V::Value, DecodeError>
262    where
263        V: Visitor,
264    {
265        Err(DecodeError::InvalidType(
266            "Expecting `Transaction` but found `CPIEvent`".to_string(),
267        ))
268    }
269
270    fn decode_anchor_cpi_events<V>(&self, _visitor: V) -> Result<V::Value, DecodeError>
271    where
272        V: Visitor,
273    {
274        Err(DecodeError::InvalidType(
275            "Expecting `AnchorCPIEvents` but found `CPIEvent`".to_string(),
276        ))
277    }
278
279    fn decode_owned_data<V>(&self, visitor: V) -> Result<V::Value, DecodeError>
280    where
281        V: Visitor,
282    {
283        visitor.visit_owned_data(&self.program_id, &self.data)
284    }
285
286    fn decode_bytes<V>(&self, visitor: V) -> Result<V::Value, DecodeError>
287    where
288        V: Visitor,
289    {
290        visitor.visit_bytes(&self.data)
291    }
292}
293
294/// Slot and index.
295pub type SlotAndIndex = (u64, Option<usize>);
296
297/// CPI Events.
298pub struct CPIEvents {
299    /// Signature.
300    pub signature: Signature,
301    /// Slot and index.
302    pub slot_index: SlotAndIndex,
303    /// CPI Events.
304    pub events: Vec<CPIEvent>,
305}
306
307impl CPIEvents {
308    /// Access CPI Events.
309    pub fn access(&self) -> AccessCPIEvents {
310        AccessCPIEvents {
311            signature: &self.signature,
312            slot_index: &self.slot_index,
313            events: self.events.iter(),
314        }
315    }
316}
317
318/// Access CPI Events.
319pub struct AccessCPIEvents<'a> {
320    signature: &'a Signature,
321    slot_index: &'a SlotAndIndex,
322    events: std::slice::Iter<'a, CPIEvent>,
323}
324
325impl<'a> AccessCPIEvents<'a> {
326    /// Create a new access for CPI Events.
327    pub fn new(
328        signature: &'a Signature,
329        slot_index: &'a SlotAndIndex,
330        events: &'a [CPIEvent],
331    ) -> Self {
332        Self {
333            signature,
334            slot_index,
335            events: events.iter(),
336        }
337    }
338}
339
340impl<'a> crate::AnchorCPIEventsAccess<'a> for AccessCPIEvents<'a> {
341    fn slot(&self) -> Result<u64, DecodeError> {
342        Ok(self.slot_index.0)
343    }
344
345    fn index(&self) -> Result<Option<usize>, DecodeError> {
346        Ok(self.slot_index.1)
347    }
348
349    fn signature(&self) -> Result<&Signature, DecodeError> {
350        Ok(self.signature)
351    }
352
353    fn next_event<T>(&mut self) -> Result<Option<T>, DecodeError>
354    where
355        T: Decode,
356    {
357        let Some(decoder) = self.events.next() else {
358            return Ok(None);
359        };
360        T::decode(decoder).map(Some)
361    }
362}
363
364const EVENT_AUTHORITY_SEED: &[u8] = b"__event_authority";
365
366fn find_event_authority_address(program_id: &Pubkey) -> Pubkey {
367    Pubkey::find_program_address(&[EVENT_AUTHORITY_SEED], program_id).0
368}