gmsol_decode/decoder/decoder_impl/
solana_decoder.rs1use 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
13pub 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 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 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 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 pub fn set_cpi_event_filter(&mut self, filter: CPIEventFilter) -> &mut Self {
60 self.cpi_event_filter = filter;
61 self
62 }
63
64 pub fn signature(&self) -> Signature {
66 self.signature
67 }
68
69 pub fn slot(&self) -> u64 {
71 self.slot
72 }
73
74 pub fn transaction(&self) -> &EncodedTransactionWithStatusMeta {
76 self.transaction
77 }
78
79 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 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#[derive(Debug, Clone, Default)]
198pub struct CPIEventFilter {
199 map: HashMap<Pubkey, Pubkey>,
201}
202
203impl CPIEventFilter {
204 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 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 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 pub fn event_authorities(&self) -> impl Iterator<Item = &Pubkey> {
229 self.map.keys()
230 }
231
232 pub fn programs(&self) -> impl Iterator<Item = &Pubkey> {
234 self.map.values()
235 }
236}
237
238pub struct CPIEvent {
240 program_id: Pubkey,
241 data: Vec<u8>,
242}
243
244impl CPIEvent {
245 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
294pub type SlotAndIndex = (u64, Option<usize>);
296
297pub struct CPIEvents {
299 pub signature: Signature,
301 pub slot_index: SlotAndIndex,
303 pub events: Vec<CPIEvent>,
305}
306
307impl CPIEvents {
308 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
318pub 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 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}