ccnext_query_builder/abi/
query_builder.rs

1use std::{collections::HashMap, future::Future, pin::Pin, sync::Arc};
2
3use alloy::{consensus::Transaction as _, dyn_abi::{DecodedEvent, DynSolType, EventExt}, hex::FromHex, json_abi::JsonAbi, primitives::{map::HashSet, FixedBytes}, rpc::types::{Log, Transaction, TransactionReceipt}};
4use alloy_json_abi::{Event, Function};
5
6use crate::abi::{models::{FieldMetadata, QueryableFields}, query_builder_for_function::QueryBuilderForFunction};
7use ccnext_abi_encoding::abi::abi_encode;
8use super::{abi_encoding_mapping::get_all_fields_for_transaction, models::QueryBuilderError, query_builder_for_event::QueryBuilderForEvent, utils::compute_abi_offsets};
9
10type AsyncAbiResolverCallback = Arc<dyn Fn(String) -> Pin<Box<dyn Future<Output = Option<String>> + Send>> + Send + Sync>;
11
12pub struct QueryBuilder {
13    tx: Transaction,
14    rx: TransactionReceipt,
15    abi_provider: Option<AsyncAbiResolverCallback>,
16    _computed_offsets: Vec<FieldMetadata>,
17    mapped_offsets: HashMap<QueryableFields, FieldMetadata>,
18    selected_offsets: Vec<(usize, usize)>,
19    abi_cache: HashMap<String, JsonAbi>
20}
21fn hex_to_4_bytes(hex: &str) -> Result<[u8; 4], &'static str> {
22    let hex = hex.strip_prefix("0x").unwrap_or(hex); // Remove "0x" if present
23
24    if hex.len() != 8 {
25        return Err("Hex string must be exactly 8 characters long (excluding 0x)");
26    }
27
28    let bytes = hex::decode(hex).map_err(|_| "Invalid hex string")?;
29
30    let mut arr = [0u8; 4];
31    arr.copy_from_slice(&bytes[..4]);
32
33    Ok(arr)
34}
35
36impl QueryBuilder {
37    pub fn create_from_transaction(tx: Transaction, rx: TransactionReceipt) -> Result<QueryBuilder, QueryBuilderError> {
38
39        // encode the transaction
40        let encoded = match abi_encode(tx.clone(), rx.clone()) {
41            Ok(encoded_result) => encoded_result,
42            Err(_) => {
43                return Err(QueryBuilderError::FailedToAbiEncode);
44            },
45        };
46
47        // get the strongly typed elements.
48        let field_and_types = get_all_fields_for_transaction(tx.inner.tx_type().clone());
49
50        // only the types you need.
51        let fields: Vec<QueryableFields> = field_and_types.iter().map(|f| f.0.clone()).collect();
52
53        // only the types you need.
54        let sol_types: Vec<DynSolType> = field_and_types.iter().map(|f| f.1.clone()).collect();
55
56        // compute the offsets.
57        let computed_offsets = match compute_abi_offsets(sol_types, encoded.abi.clone()) {
58            Ok(co) => {
59                co
60            },
61            Err(_) => {
62                return Err(QueryBuilderError::FailedToComputeOffsets);
63            }
64        };
65
66        // TODO: Make sure this passes with various transactions. Doesn't intutitively feel like this should work
67        // in all cases as currently written, since `compute_abi_offsets` recursively adds children while 
68        // `get_all_fields_for_transaction` doesn't.
69        if computed_offsets.len() != field_and_types.len() {
70            return Err(QueryBuilderError::MissMatchedLengthDecoding);
71        }
72
73        // create a map of offsets.
74        let mut mapped_offsets = HashMap::<QueryableFields, FieldMetadata>::new();
75        let fields_offset = fields.iter().zip(computed_offsets.clone());
76        for (field, offset) in fields_offset {
77            mapped_offsets.insert(field.clone(), offset);
78        }
79
80        Ok(QueryBuilder {
81            tx,
82            rx,
83            abi_provider: None,
84            mapped_offsets,
85            _computed_offsets: computed_offsets.clone(),
86            selected_offsets: vec![],
87            abi_cache: HashMap::new()
88        })
89    }
90
91    pub fn set_abi_provider(&mut self, abi_provider: AsyncAbiResolverCallback) {
92        self.abi_provider = Some(abi_provider);
93    }
94
95    pub async fn function_builder(&mut self, name_or_signature: String, 
96        configurator: fn(&mut QueryBuilderForFunction) -> Result<(), QueryBuilderError>
97    ) -> Result<&mut Self, QueryBuilderError> {
98
99        if self.tx.inner.input().is_empty() {
100            return Err(QueryBuilderError::RequestingFunctionArgumentOfAnEmptyCalldataTransaction);
101        } 
102        
103        let contract_address = match self.tx.to() {
104            Some(ca) => ca,
105            None => {
106                return Err(QueryBuilderError::RequestingFunctionArgumentButNoToAddressPresent);
107            }
108        };
109
110        let data_field = match self.mapped_offsets.get(&QueryableFields::TxData) {
111            Some(t) => t,
112            None => return Err(QueryBuilderError::FailedToFindTxDataField)
113        }.clone();
114
115        let abi = self.get_abi_from_provider_cached(contract_address.to_string()).await?;
116        let matched_function: &Function;
117        // If signature, then we can get function without ambiguity
118        if name_or_signature.starts_with("0x") {
119            
120            let name_of_signature_bytes = match self::hex_to_4_bytes(name_or_signature.as_str()) {
121                Ok(t) => t,
122                Err(_) => return Err(QueryBuilderError::FunctionSignatureNameProvidedIsNotValidHex)
123            };
124
125            matched_function = match abi.functions().find(|f| f.selector().0 == name_of_signature_bytes) {
126                Some(t) => t,
127                None => return Err(QueryBuilderError::FailedToFindFunctionByNameOrSignature(name_or_signature))
128            };
129
130        } else {
131
132            // If name was passed, then we get the function with that name. For now, we error out when
133            // multiple functions are found with the same name.
134            matched_function = match abi.function(&name_or_signature) {
135                Some(functions) => {
136                    if functions.len() > 1 {
137                        return Err(QueryBuilderError::AmbigiousFunctionMatch(functions.clone()));
138                    } 
139
140                    match functions.get(0) {
141                        Some(t) => t,
142                        None => return Err(QueryBuilderError::FailedToFindFunctionByNameOrSignature(name_or_signature))
143                    }
144                },
145                None => {
146                    return Err(QueryBuilderError::FailedToFindFunctionByNameOrSignature(name_or_signature));
147                }
148            };
149        }
150
151        // now that we have a matched function :)
152        // we can create a function builder for it.
153        let mut builder = QueryBuilderForFunction::new(matched_function.clone(), self.tx.clone(), data_field.clone());
154        configurator(&mut builder)?;
155        let offsets_from_builder = builder.get_selected_offsets();
156        self.selected_offsets.extend(offsets_from_builder);
157        Ok(self)
158    }
159
160    pub fn add_static_field(&mut self, field: QueryableFields) -> Result<&mut Self, QueryBuilderError> {
161        match self.mapped_offsets.get(&field) {
162            Some(field_offset) => {
163                match field_offset.size {
164                    Some(size) => {
165                        self.selected_offsets.push((field_offset.offset, size));
166                        Ok(self)
167                    }
168                    None => {
169                        Err(QueryBuilderError::FieldIsNotStatic)
170                    }
171                }
172            },
173            None => {
174                Err(QueryBuilderError::FieldNotPresentInTx)
175            }
176        }
177    }
178
179    pub async fn multi_event_builder(&mut self, 
180        event_name_or_signature: String,
181        filter: fn(Log, DecodedEvent, usize) -> bool,
182        configurator: fn(&mut QueryBuilderForEvent) -> Result<(), QueryBuilderError>
183    ) -> Result<&mut Self, QueryBuilderError> {
184
185        let matched_events = self.find_all_events(event_name_or_signature.clone(), filter).await?;
186       
187        let logs_field = match self.mapped_offsets.get(&QueryableFields::RxLogs) {
188            Some(lf) => lf,
189            None => {
190                return Err(QueryBuilderError::FailedToFindRxLogsField);
191            }
192        };
193
194        for (log, decoded_event, log_index, event) in matched_events {
195
196            let log_field = match logs_field.children.get(log_index) {
197                Some(t) => t,
198                None => return Err(QueryBuilderError::FailedToGetEventDataOffsets(log))
199            };
200            
201            let mut event_builder = QueryBuilderForEvent::new(
202                log_field.clone(), 
203                log,
204                decoded_event,
205                event
206            );
207            configurator(&mut event_builder)?;
208            let selected_offsets_from_event_builder = event_builder.get_selected_offsets();
209            self.selected_offsets.extend(selected_offsets_from_event_builder);
210        }
211
212        Ok(self)
213    }
214
215    pub async fn event_builder(&mut self, 
216        event_name_or_signature: String, 
217        filter: fn(Log, DecodedEvent, usize) -> bool,
218        configurator: fn(&mut QueryBuilderForEvent) -> Result<(), QueryBuilderError>
219    ) -> Result<&mut Self, QueryBuilderError> {
220
221        let (log, decoded_event, log_index, event) = match self.find_event(event_name_or_signature.clone(), filter).await? {
222            Some(m) => {
223                m
224            },
225            None => {
226                return Err(QueryBuilderError::FailedToFindEventByNameOrSignature(event_name_or_signature))
227            }
228        };
229
230        let logs_field = match self.mapped_offsets.get(&QueryableFields::RxLogs) {
231            Some(lf) => lf,
232            None => {
233                return Err(QueryBuilderError::FailedToFindRxLogsField);
234            }
235        };
236        
237        let log_field = match logs_field.children.get(log_index) {
238            Some(f) => f,
239            None => {
240                return Err(QueryBuilderError::MissingLogInAbiOffsets(log_index));
241            }
242        };
243
244        let mut event_builder = QueryBuilderForEvent::new(log_field.clone(), log, decoded_event, event);
245        configurator(&mut event_builder)?;
246        let selected_offsets_from_event_builder = event_builder.get_selected_offsets();
247        self.selected_offsets.extend(selected_offsets_from_event_builder);
248        Ok(self)
249    }
250
251    pub async fn find_event(&mut self, event_name_or_signature: String, filter: fn(Log, DecodedEvent, usize) -> bool) -> Result<Option<(Log, DecodedEvent, usize, Event)>, QueryBuilderError> {
252        let events = self.find_all_events(event_name_or_signature.clone(), filter).await?;
253        if events.len() == 0 {
254            Ok(None)
255        } else if events.len() == 1 {
256            let first_element = events.get(0).unwrap();
257            Ok(Some(first_element.clone()))
258        } else {
259            Err(QueryBuilderError::AmbigiousEventMatch(event_name_or_signature))
260        }
261    }
262    
263    pub async fn find_all_events(&mut self, event_name_or_signature: String, filter: fn (Log, DecodedEvent, usize) -> bool) -> Result<Vec<(Log, DecodedEvent, usize, Event)>, QueryBuilderError> {
264
265      
266        let mut extended_logs = Vec::new();
267
268        if event_name_or_signature.starts_with("0x") {
269            let event_signature_as_fixed_bytes = match FixedBytes::<32>::from_hex(event_name_or_signature.clone()) {
270                Ok(decoded_fixed_bytes) => decoded_fixed_bytes,
271                Err(_) => return Err(QueryBuilderError::EventSignatureNameProvidedIsNotValidHex)
272            };
273
274            // filter the logs, that match the criteria.
275            let mut filtered_logs = Vec::new();
276            let mut log_index = 0;
277            let mut contract_addresses = Vec::new();
278            for log in self.rx.inner.logs() {
279
280                if let Some(event_hash) = log.topic0() {
281                    if event_hash.eq(&event_signature_as_fixed_bytes) {
282                        contract_addresses.push(log.address().to_string());
283                        filtered_logs.push((log_index, log.clone()));
284                    }
285                }
286
287                log_index += 1;
288            }
289
290            // get the contract addresses
291            let abis = self.get_abis_of_contract_addresses(contract_addresses).await?;
292            for (log_index, log) in filtered_logs {
293                let contract_address = log.address().to_string();
294                let abi = match abis.get(&contract_address) {
295                    Some(a) => a,
296                    None => return Err(QueryBuilderError::NoAbiFoundForContract(contract_address.clone()))
297                };
298
299                let event_of_signature = abi.events().find(|e| {
300                    e.selector().0 == event_signature_as_fixed_bytes.0
301                });
302
303                if let Some(event) = event_of_signature {
304
305                    // we have the event woot woot.
306                    match event.decode_log(&log.inner, true) {
307                        Ok(decoded_event) => {
308                            extended_logs.push((log, decoded_event, log_index, event.clone()));
309                        }
310                        Err(_) => {
311                            return Err(QueryBuilderError::FailedToDecodeLog(log.clone()));
312                        }
313                    }
314                } else {
315                    return Err(QueryBuilderError::FailedToFindEventByNameOrSignature(event_name_or_signature.clone()));
316                }
317            }            
318
319        } else {
320
321            // we need to get all the abi's possible in the events.
322            let abis = self.get_receipt_abis().await?;
323            let mut log_index = 0;
324            for log in self.rx.inner.logs() {
325
326                // get the ABI for this log.
327                let abi = match abis.get(&log.address().to_string()) {
328                    Some(json_abi) => {
329                        json_abi
330                    }
331                    None => {
332                        return Err(QueryBuilderError::NoAbiFoundForContract(log.address().to_string()));
333                    }
334                };
335
336                // get the event signature
337                let event_signature = match log.topic0() {
338                    Some(es) => es,
339                    None => {
340                        log_index += 1;
341                        continue;
342                    }
343                };
344
345                // find the event...
346                let event_of_signature = abi.events().find(|e| {
347                    e.selector().0 == event_signature.0
348                });
349
350                // attempt to parse the log :)
351                if let Some(event) = event_of_signature {
352
353                    // before we try to decode the log, lets first check its the event name
354                    // we care about..
355                    if event.name == event_name_or_signature {
356                        // we have the event woot woot.
357                        match event.decode_log(&log.inner, true) {
358                            Ok(decoded_event) => {
359                                extended_logs.push((log.clone(), decoded_event, log_index, event.clone()));
360                            }
361                            Err(_) => {
362                                return Err(QueryBuilderError::FailedToDecodeLog(log.clone()));
363                            }
364                        }
365                    }
366                } else {
367                    // its not that we can't find it, its more that the log is not decodable
368                    // due not being able to find the event in the ABI.
369                    return Err(QueryBuilderError::FailedToDecodeLog(log.clone()));
370                }
371
372                log_index += 1;
373            }
374        }
375
376        // now that we have only extended logs of an event that either matches by name or signature.
377        // we just need to offer the ability to filter to the user..
378        let mut matches = Vec::new();
379        for (log, decoded_event, log_index, event) in extended_logs {
380            if true == filter(log.clone(), decoded_event.clone(), log_index.clone()) {
381                matches.push((log, decoded_event, log_index, event));
382            }
383        }
384
385        Ok(matches)
386    }
387
388    pub async fn get_receipt_abis(&mut self) -> Result<HashMap<String, JsonAbi>, QueryBuilderError> {
389        let contract_addresses: Vec<String> = self.rx.inner
390            .logs()
391            .iter()
392            .map(|f| f.address().to_string())
393            .collect();
394
395        let result = self.get_abis_of_contract_addresses(contract_addresses).await?;
396        Ok(result)
397    }
398
399    pub async fn get_abis_of_contract_addresses(&mut self, contract_addresses: Vec<String>) -> Result<HashMap<String, JsonAbi>, QueryBuilderError> {
400        let unique_contract_addresses: HashSet<_> = contract_addresses.into_iter().collect();
401        let mut abi_map = HashMap::new();
402        for contract_address in unique_contract_addresses {
403            let abi: JsonAbi = self.get_abi_from_provider_cached(contract_address.clone()).await?;
404            abi_map.insert(contract_address.clone(), abi);
405        }
406        Ok(abi_map)
407    }
408
409    pub async fn get_abi_from_provider_cached(&mut self, contract_address: String) -> Result<JsonAbi, QueryBuilderError> {
410
411
412        let result = match self.abi_cache.get(&contract_address.clone()) {
413            Some(existing) => {
414                existing
415            }
416            None => {
417                let abi = self.get_abi_from_provider(contract_address.clone()).await?;
418                self.abi_cache.insert(contract_address.clone(), abi.clone());
419                &abi.clone()
420            }
421        };
422
423        Ok(result.clone())
424    }
425
426    pub async fn get_abi_from_provider(&self, contract_address: String) -> Result<JsonAbi, QueryBuilderError> {
427        let abi_provider = match &self.abi_provider {
428            Some(ap) => ap,
429            None => return Err(QueryBuilderError::AbiProviderNotInitialized)
430        };
431 
432        let abi_raw = match abi_provider(contract_address.clone()).await {
433            Some(json_string) => {
434                json_string
435            },
436            None => {
437                return Err(QueryBuilderError::NoAbiFoundForContract(contract_address.clone()));
438            } 
439        };
440
441        match JsonAbi::from_json_str(&abi_raw) {
442            Ok(json_abi) => Ok(json_abi),
443            Err(_) => Err(QueryBuilderError::FailedToParseAbi(contract_address.clone(), abi_raw))
444        }
445    }
446    
447    pub fn get_selected_offsets(&self) -> Vec<(usize, usize)> {
448        self.selected_offsets.clone()
449    }
450}