ccnext_query_builder/abi/
query_builder.rs

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