1#![allow(missing_docs)]
4
5use crate::{abi::abi_decode_calldata, provider::runtime_transport::RuntimeTransportBuilder};
6use alloy_json_abi::JsonAbi;
7use alloy_primitives::{B256, Selector, map::HashMap};
8use eyre::Context;
9use itertools::Itertools;
10use serde::{Deserialize, Serialize, de::DeserializeOwned};
11use std::{
12 fmt,
13 sync::{
14 Arc,
15 atomic::{AtomicBool, AtomicUsize, Ordering},
16 },
17 time::Duration,
18};
19
20const BASE_URL: &str = "https://api.4byte.sourcify.dev";
21const SELECTOR_LOOKUP_URL: &str = "https://api.4byte.sourcify.dev/signature-database/v1/lookup";
22const SELECTOR_IMPORT_URL: &str = "https://api.4byte.sourcify.dev/signature-database/v1/import";
23
24const REQ_TIMEOUT: Duration = Duration::from_secs(15);
26
27const MAX_TIMEDOUT_REQ: usize = 4usize;
29
30pub type OpenChainSignatures = Vec<String>;
32
33#[derive(Clone, Debug)]
35pub struct OpenChainClient {
36 inner: reqwest::Client,
37 spurious_connection: Arc<AtomicBool>,
39 timedout_requests: Arc<AtomicUsize>,
41 max_timedout_requests: usize,
43}
44
45impl OpenChainClient {
46 pub fn new() -> eyre::Result<Self> {
48 let inner = RuntimeTransportBuilder::new(BASE_URL.parse().unwrap())
49 .with_timeout(REQ_TIMEOUT)
50 .build()
51 .reqwest_client()
52 .wrap_err("failed to build OpenChain client")?;
53 Ok(Self {
54 inner,
55 spurious_connection: Default::default(),
56 timedout_requests: Default::default(),
57 max_timedout_requests: MAX_TIMEDOUT_REQ,
58 })
59 }
60
61 async fn get_text(&self, url: impl reqwest::IntoUrl + fmt::Display) -> reqwest::Result<String> {
62 trace!(%url, "GET");
63 self.inner
64 .get(url)
65 .send()
66 .await
67 .inspect_err(|err| self.on_reqwest_err(err))?
68 .text()
69 .await
70 .inspect_err(|err| self.on_reqwest_err(err))
71 }
72
73 async fn post_json<T: Serialize + std::fmt::Debug, R: DeserializeOwned>(
75 &self,
76 url: &str,
77 body: &T,
78 ) -> reqwest::Result<R> {
79 trace!(%url, body=?serde_json::to_string(body), "POST");
80 self.inner
81 .post(url)
82 .json(body)
83 .send()
84 .await
85 .inspect_err(|err| self.on_reqwest_err(err))?
86 .json()
87 .await
88 .inspect_err(|err| self.on_reqwest_err(err))
89 }
90
91 fn on_reqwest_err(&self, err: &reqwest::Error) {
92 fn is_connectivity_err(err: &reqwest::Error) -> bool {
93 if err.is_timeout() || err.is_connect() {
94 return true;
95 }
96 if let Some(status) = err.status() {
98 let code = status.as_u16();
99 if (500..600).contains(&code) {
100 return true;
101 }
102 }
103 false
104 }
105
106 if is_connectivity_err(err) {
107 warn!("spurious network detected for OpenChain");
108 let previous = self.timedout_requests.fetch_add(1, Ordering::SeqCst);
109 if previous + 1 >= self.max_timedout_requests {
110 self.set_spurious();
111 }
112 }
113 }
114
115 fn is_spurious(&self) -> bool {
117 self.spurious_connection.load(Ordering::Relaxed)
118 }
119
120 fn set_spurious(&self) {
122 self.spurious_connection.store(true, Ordering::Relaxed)
123 }
124
125 fn ensure_not_spurious(&self) -> eyre::Result<()> {
126 if self.is_spurious() {
127 eyre::bail!("Spurious connection detected");
128 }
129 Ok(())
130 }
131
132 pub async fn decode_selector(
134 &self,
135 selector: SelectorKind,
136 ) -> eyre::Result<OpenChainSignatures> {
137 Ok(self.decode_selectors(&[selector]).await?.pop().unwrap())
138 }
139
140 pub async fn decode_selectors(
142 &self,
143 selectors: &[SelectorKind],
144 ) -> eyre::Result<Vec<OpenChainSignatures>> {
145 if selectors.is_empty() {
146 return Ok(vec![]);
147 }
148
149 if enabled!(tracing::Level::TRACE) {
150 trace!(?selectors, "decoding selectors");
151 } else {
152 debug!(len = selectors.len(), "decoding selectors");
153 }
154
155 self.ensure_not_spurious()?;
157
158 let mut url: url::Url = SELECTOR_LOOKUP_URL.parse().unwrap();
160 {
161 let mut query = url.query_pairs_mut();
162 let functions = selectors.iter().filter_map(SelectorKind::as_function);
163 if functions.clone().next().is_some() {
164 query.append_pair("function", &functions.format(",").to_string());
165 }
166 let events = selectors.iter().filter_map(SelectorKind::as_event);
167 if events.clone().next().is_some() {
168 query.append_pair("event", &events.format(",").to_string());
169 }
170 let _ = query.finish();
171 }
172
173 let text = self.get_text(url).await?;
174 let SignatureResponse { ok, result } = match serde_json::from_str(&text) {
175 Ok(response) => response,
176 Err(err) => {
177 eyre::bail!("could not decode response: {err}: {text}");
178 }
179 };
180 if !ok {
181 eyre::bail!("OpenChain returned an error: {text}");
182 }
183
184 Ok(selectors
185 .iter()
186 .map(|selector| {
187 let signatures = match selector {
188 SelectorKind::Function(selector) | SelectorKind::Error(selector) => {
189 result.function.get(selector)
190 }
191 SelectorKind::Event(hash) => result.event.get(hash),
192 };
193 signatures
194 .map(Option::as_deref)
195 .unwrap_or_default()
196 .unwrap_or_default()
197 .iter()
198 .map(|sig| sig.name.clone())
199 .collect()
200 })
201 .collect())
202 }
203
204 pub async fn decode_function_selector(
206 &self,
207 selector: Selector,
208 ) -> eyre::Result<OpenChainSignatures> {
209 self.decode_selector(SelectorKind::Function(selector)).await
210 }
211
212 pub async fn decode_calldata(&self, calldata: &str) -> eyre::Result<OpenChainSignatures> {
214 let calldata = calldata.strip_prefix("0x").unwrap_or(calldata);
215 if calldata.len() < 8 {
216 eyre::bail!(
217 "Calldata too short: expected at least 8 characters (excluding 0x prefix), got {}.",
218 calldata.len()
219 );
220 }
221
222 let mut sigs = self.decode_function_selector(calldata[..8].parse()?).await?;
223 sigs.retain(|sig| abi_decode_calldata(sig, calldata, true, true).is_ok());
225 Ok(sigs)
226 }
227
228 pub async fn decode_event_topic(&self, topic: B256) -> eyre::Result<OpenChainSignatures> {
230 self.decode_selector(SelectorKind::Event(topic)).await
231 }
232
233 pub async fn pretty_calldata(
251 &self,
252 calldata: impl AsRef<str>,
253 offline: bool,
254 ) -> eyre::Result<PossibleSigs> {
255 let mut possible_info = PossibleSigs::new();
256 let calldata = calldata.as_ref().trim_start_matches("0x");
257
258 let selector =
259 calldata.get(..8).ok_or_else(|| eyre::eyre!("calldata cannot be less that 4 bytes"))?;
260
261 let sigs = if offline {
262 vec![]
263 } else {
264 let selector = selector.parse()?;
265 self.decode_function_selector(selector).await.unwrap_or_default().into_iter().collect()
266 };
267 let (_, data) = calldata.split_at(8);
268
269 if !data.len().is_multiple_of(64) {
270 eyre::bail!("\nInvalid calldata size");
271 }
272
273 let row_length = data.len() / 64;
274
275 for row in 0..row_length {
276 possible_info.data.push(data[64 * row..64 * (row + 1)].to_string());
277 }
278 if sigs.is_empty() {
279 possible_info.method = SelectorOrSig::Selector(selector.to_string());
280 } else {
281 possible_info.method = SelectorOrSig::Sig(sigs);
282 }
283 Ok(possible_info)
284 }
285
286 pub async fn import_selectors(
288 &self,
289 data: SelectorImportData,
290 ) -> eyre::Result<SelectorImportResponse> {
291 self.ensure_not_spurious()?;
292
293 let request = match data {
294 SelectorImportData::Abi(abis) => {
295 let functions_and_errors: OpenChainSignatures = abis
296 .iter()
297 .flat_map(|abi| {
298 abi.functions()
299 .map(|func| func.signature())
300 .chain(abi.errors().map(|error| error.signature()))
301 .collect::<Vec<_>>()
302 })
303 .collect();
304
305 let events = abis
306 .iter()
307 .flat_map(|abi| abi.events().map(|event| event.signature()))
308 .collect::<Vec<_>>();
309
310 SelectorImportRequest { function: functions_and_errors, event: events }
311 }
312 SelectorImportData::Raw(raw) => {
313 let function_and_error =
314 raw.function.iter().chain(raw.error.iter()).cloned().collect::<Vec<_>>();
315 SelectorImportRequest { function: function_and_error, event: raw.event }
316 }
317 };
318
319 Ok(self.post_json(SELECTOR_IMPORT_URL, &request).await?)
320 }
321}
322
323pub enum SelectorOrSig {
324 Selector(String),
325 Sig(OpenChainSignatures),
326}
327
328pub struct PossibleSigs {
329 method: SelectorOrSig,
330 data: OpenChainSignatures,
331}
332
333impl PossibleSigs {
334 fn new() -> Self {
335 Self { method: SelectorOrSig::Selector("0x00000000".to_string()), data: vec![] }
336 }
337}
338
339impl fmt::Display for PossibleSigs {
340 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
341 match &self.method {
342 SelectorOrSig::Selector(selector) => {
343 writeln!(f, "\n Method: {selector}")?;
344 }
345 SelectorOrSig::Sig(sigs) => {
346 writeln!(f, "\n Possible methods:")?;
347 for sig in sigs {
348 writeln!(f, " - {sig}")?;
349 }
350 }
351 }
352
353 writeln!(f, " ------------")?;
354 for (i, row) in self.data.iter().enumerate() {
355 let row_label_decimal = i * 32;
356 let row_label_hex = format!("{row_label_decimal:03x}");
357 writeln!(f, " [{row_label_hex}]: {row}")?;
358 }
359 Ok(())
360 }
361}
362
363#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
365pub enum SelectorKind {
366 Function(Selector),
368 Error(Selector),
370 Event(B256),
372}
373
374impl SelectorKind {
375 pub const fn as_function(&self) -> Option<Selector> {
377 match *self {
378 Self::Function(selector) | Self::Error(selector) => Some(selector),
379 _ => None,
380 }
381 }
382
383 pub const fn as_event(&self) -> Option<B256> {
385 match *self {
386 Self::Event(hash) => Some(hash),
387 _ => None,
388 }
389 }
390}
391
392pub async fn decode_selector(selector: SelectorKind) -> eyre::Result<OpenChainSignatures> {
394 OpenChainClient::new()?.decode_selector(selector).await
395}
396
397pub async fn decode_selectors(
399 selectors: &[SelectorKind],
400) -> eyre::Result<Vec<OpenChainSignatures>> {
401 OpenChainClient::new()?.decode_selectors(selectors).await
402}
403
404pub async fn decode_function_selector(selector: Selector) -> eyre::Result<OpenChainSignatures> {
406 OpenChainClient::new()?.decode_function_selector(selector).await
407}
408
409pub async fn decode_calldata(calldata: &str) -> eyre::Result<OpenChainSignatures> {
411 OpenChainClient::new()?.decode_calldata(calldata).await
412}
413
414pub async fn decode_event_topic(topic: B256) -> eyre::Result<OpenChainSignatures> {
416 OpenChainClient::new()?.decode_event_topic(topic).await
417}
418
419pub async fn pretty_calldata(
435 calldata: impl AsRef<str>,
436 offline: bool,
437) -> eyre::Result<PossibleSigs> {
438 OpenChainClient::new()?.pretty_calldata(calldata, offline).await
439}
440
441#[derive(Debug, Default, PartialEq, Eq, Serialize)]
442pub struct RawSelectorImportData {
443 pub function: OpenChainSignatures,
444 pub event: OpenChainSignatures,
445 pub error: OpenChainSignatures,
446}
447
448impl RawSelectorImportData {
449 pub const fn is_empty(&self) -> bool {
450 self.function.is_empty() && self.event.is_empty() && self.error.is_empty()
451 }
452}
453
454#[derive(Serialize)]
455#[serde(untagged)]
456pub enum SelectorImportData {
457 Abi(Vec<JsonAbi>),
458 Raw(RawSelectorImportData),
459}
460
461#[derive(Debug, Default, Serialize)]
462struct SelectorImportRequest {
463 function: OpenChainSignatures,
464 event: OpenChainSignatures,
465}
466
467#[derive(Debug, Deserialize)]
468struct SelectorImportEffect {
469 imported: HashMap<String, String>,
470 duplicated: HashMap<String, String>,
471}
472
473#[derive(Debug, Deserialize)]
474struct SelectorImportResult {
475 function: SelectorImportEffect,
476 event: SelectorImportEffect,
477}
478
479#[derive(Debug, Deserialize)]
480pub struct SelectorImportResponse {
481 result: SelectorImportResult,
482}
483
484impl SelectorImportResponse {
485 pub fn describe(&self) {
487 for (k, v) in &self.result.function.imported {
488 let _ = sh_println!("Imported: Function {k}: {v}");
489 }
490 for (k, v) in &self.result.event.imported {
491 let _ = sh_println!("Imported: Event {k}: {v}");
492 }
493 for (k, v) in &self.result.function.duplicated {
494 let _ = sh_println!("Duplicated: Function {k}: {v}");
495 }
496 for (k, v) in &self.result.event.duplicated {
497 let _ = sh_println!("Duplicated: Event {k}: {v}");
498 }
499
500 let _ = sh_println!("Selectors successfully uploaded to OpenChain");
501 }
502}
503
504pub async fn import_selectors(data: SelectorImportData) -> eyre::Result<SelectorImportResponse> {
506 OpenChainClient::new()?.import_selectors(data).await
507}
508
509#[derive(Debug, Default, PartialEq, Eq)]
510pub struct ParsedSignatures {
511 pub signatures: RawSelectorImportData,
512 pub abis: Vec<JsonAbi>,
513}
514
515#[derive(Deserialize)]
516struct Artifact {
517 abi: JsonAbi,
518}
519
520pub fn parse_signatures(tokens: Vec<String>) -> ParsedSignatures {
524 let abis = tokens
527 .iter()
528 .filter(|sig| sig.ends_with(".json"))
529 .filter_map(|filename| std::fs::read_to_string(filename).ok())
530 .filter_map(|file| serde_json::from_str(file.as_str()).ok())
531 .map(|artifact: Artifact| artifact.abi)
532 .collect();
533
534 let signatures = tokens.iter().filter(|sig| !sig.ends_with(".json")).fold(
537 RawSelectorImportData::default(),
538 |mut data, signature| {
539 let mut split = signature.split(' ');
540 #[allow(clippy::collapsible_match)]
541 match split.next() {
542 Some("function") => {
543 if let Some(sig) = split.next() {
544 data.function.push(sig.to_string())
545 }
546 }
547 Some("event") => {
548 if let Some(sig) = split.next() {
549 data.event.push(sig.to_string())
550 }
551 }
552 Some("error") => {
553 if let Some(sig) = split.next() {
554 data.error.push(sig.to_string())
555 }
556 }
557 Some(signature) => {
558 data.function.push(signature.to_string());
560 }
561 None => {}
562 }
563 data
564 },
565 );
566
567 ParsedSignatures { signatures, abis }
568}
569
570#[derive(Deserialize)]
572struct SignatureResponse {
573 ok: bool,
574 result: SignatureResult,
575}
576
577#[derive(Deserialize)]
578struct SignatureResult {
579 event: HashMap<B256, Option<Vec<Signature>>>,
580 function: HashMap<Selector, Option<Vec<Signature>>>,
581}
582
583#[derive(Deserialize)]
584struct Signature {
585 name: String,
586}
587
588#[cfg(test)]
589mod tests {
590 use super::*;
591
592 #[test]
593 fn test_parse_signatures() {
594 let result = parse_signatures(vec!["transfer(address,uint256)".to_string()]);
595 assert_eq!(
596 result,
597 ParsedSignatures {
598 signatures: RawSelectorImportData {
599 function: vec!["transfer(address,uint256)".to_string()],
600 ..Default::default()
601 },
602 ..Default::default()
603 }
604 );
605
606 let result = parse_signatures(vec![
607 "transfer(address,uint256)".to_string(),
608 "function approve(address,uint256)".to_string(),
609 ]);
610 assert_eq!(
611 result,
612 ParsedSignatures {
613 signatures: RawSelectorImportData {
614 function: vec![
615 "transfer(address,uint256)".to_string(),
616 "approve(address,uint256)".to_string()
617 ],
618 ..Default::default()
619 },
620 ..Default::default()
621 }
622 );
623
624 let result = parse_signatures(vec![
625 "transfer(address,uint256)".to_string(),
626 "event Approval(address,address,uint256)".to_string(),
627 "error ERC20InsufficientBalance(address,uint256,uint256)".to_string(),
628 ]);
629 assert_eq!(
630 result,
631 ParsedSignatures {
632 signatures: RawSelectorImportData {
633 function: vec!["transfer(address,uint256)".to_string()],
634 event: vec!["Approval(address,address,uint256)".to_string()],
635 error: vec!["ERC20InsufficientBalance(address,uint256,uint256)".to_string()]
636 },
637 ..Default::default()
638 }
639 );
640
641 let result = parse_signatures(vec!["event".to_string()]);
643 assert_eq!(
644 result,
645 ParsedSignatures { signatures: Default::default(), ..Default::default() }
646 );
647 }
648
649 #[tokio::test]
650 async fn spurious_marked_on_timeout_threshold() {
651 let client = OpenChainClient::new().expect("client must build");
653 let url = "http://127.0.0.1:9"; for i in 0..(MAX_TIMEDOUT_REQ - 1) {
657 let _ = client.get_text(url).await; assert!(!client.is_spurious(), "unexpected spurious after {} failed attempts", i + 1);
659 }
660
661 let _ = client.get_text(url).await;
663 assert!(client.is_spurious(), "expected spurious after threshold failures");
664 }
665}