ccnext_query_builder/abi/
query_builder.rs

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