1#![allow(missing_docs)]
4
5use crate::{abi::abi_decode_calldata, provider::runtime_transport::RuntimeTransportBuilder};
6use alloy_json_abi::JsonAbi;
7use alloy_primitives::map::HashMap;
8use eyre::Context;
9use serde::{de::DeserializeOwned, Deserialize, Serialize};
10use std::{
11 fmt,
12 sync::{
13 atomic::{AtomicBool, AtomicUsize, Ordering},
14 Arc,
15 },
16 time::Duration,
17};
18
19const BASE_URL: &str = "https://api.openchain.xyz";
20const SELECTOR_LOOKUP_URL: &str = "https://api.openchain.xyz/signature-database/v1/lookup";
21const SELECTOR_IMPORT_URL: &str = "https://api.openchain.xyz/signature-database/v1/import";
22
23const REQ_TIMEOUT: Duration = Duration::from_secs(15);
25
26const MAX_TIMEDOUT_REQ: usize = 4usize;
28
29#[derive(Clone, Debug)]
31pub struct OpenChainClient {
32 inner: reqwest::Client,
33 spurious_connection: Arc<AtomicBool>,
35 timedout_requests: Arc<AtomicUsize>,
37 max_timedout_requests: usize,
39}
40
41impl OpenChainClient {
42 pub fn new() -> eyre::Result<Self> {
44 let inner = RuntimeTransportBuilder::new(BASE_URL.parse().unwrap())
45 .with_timeout(REQ_TIMEOUT)
46 .build()
47 .reqwest_client()
48 .wrap_err("failed to build OpenChain client")?;
49 Ok(Self {
50 inner,
51 spurious_connection: Default::default(),
52 timedout_requests: Default::default(),
53 max_timedout_requests: MAX_TIMEDOUT_REQ,
54 })
55 }
56
57 async fn get_text(&self, url: &str) -> reqwest::Result<String> {
58 trace!(%url, "GET");
59 self.inner
60 .get(url)
61 .send()
62 .await
63 .inspect_err(|err| self.on_reqwest_err(err))?
64 .text()
65 .await
66 .inspect_err(|err| self.on_reqwest_err(err))
67 }
68
69 async fn post_json<T: Serialize + std::fmt::Debug, R: DeserializeOwned>(
71 &self,
72 url: &str,
73 body: &T,
74 ) -> reqwest::Result<R> {
75 trace!(%url, body=?serde_json::to_string(body), "POST");
76 self.inner
77 .post(url)
78 .json(body)
79 .send()
80 .await
81 .inspect_err(|err| self.on_reqwest_err(err))?
82 .json()
83 .await
84 .inspect_err(|err| self.on_reqwest_err(err))
85 }
86
87 fn on_reqwest_err(&self, err: &reqwest::Error) {
88 fn is_connectivity_err(err: &reqwest::Error) -> bool {
89 if err.is_timeout() || err.is_connect() {
90 return true;
91 }
92 if let Some(status) = err.status() {
94 let code = status.as_u16();
95 if (500..600).contains(&code) {
96 return true;
97 }
98 }
99 false
100 }
101
102 if is_connectivity_err(err) {
103 warn!("spurious network detected for OpenChain");
104 let previous = self.timedout_requests.fetch_add(1, Ordering::SeqCst);
105 if previous >= self.max_timedout_requests {
106 self.set_spurious();
107 }
108 }
109 }
110
111 fn is_spurious(&self) -> bool {
113 self.spurious_connection.load(Ordering::Relaxed)
114 }
115
116 fn set_spurious(&self) {
118 self.spurious_connection.store(true, Ordering::Relaxed)
119 }
120
121 fn ensure_not_spurious(&self) -> eyre::Result<()> {
122 if self.is_spurious() {
123 eyre::bail!("Spurious connection detected")
124 }
125 Ok(())
126 }
127
128 pub async fn decode_selector(
130 &self,
131 selector: &str,
132 selector_type: SelectorType,
133 ) -> eyre::Result<Vec<String>> {
134 self.decode_selectors(selector_type, std::iter::once(selector))
135 .await?
136 .pop() .unwrap()
138 .ok_or_else(|| eyre::eyre!("No signature found"))
139 }
140
141 pub async fn decode_selectors(
143 &self,
144 selector_type: SelectorType,
145 selectors: impl IntoIterator<Item = impl Into<String>>,
146 ) -> eyre::Result<Vec<Option<Vec<String>>>> {
147 let selectors: Vec<String> = selectors
148 .into_iter()
149 .map(Into::into)
150 .map(|s| s.to_lowercase())
151 .map(|s| if s.starts_with("0x") { s } else { format!("0x{s}") })
152 .collect();
153
154 if selectors.is_empty() {
155 return Ok(vec![]);
156 }
157
158 debug!(len = selectors.len(), "decoding selectors");
159 trace!(?selectors, "decoding selectors");
160
161 self.ensure_not_spurious()?;
163
164 let expected_len = match selector_type {
165 SelectorType::Function | SelectorType::Error => 10, SelectorType::Event => 66, };
168 if let Some(s) = selectors.iter().find(|s| s.len() != expected_len) {
169 eyre::bail!(
170 "Invalid selector {s}: expected {expected_len} characters (including 0x prefix)."
171 )
172 }
173
174 #[derive(Deserialize)]
175 struct Decoded {
176 name: String,
177 }
178
179 #[derive(Deserialize)]
180 struct ApiResult {
181 event: HashMap<String, Option<Vec<Decoded>>>,
182 function: HashMap<String, Option<Vec<Decoded>>>,
183 }
184
185 #[derive(Deserialize)]
186 struct ApiResponse {
187 ok: bool,
188 result: ApiResult,
189 }
190
191 let url = format!(
192 "{SELECTOR_LOOKUP_URL}?{ltype}={selectors_str}",
193 ltype = match selector_type {
194 SelectorType::Function | SelectorType::Error => "function",
195 SelectorType::Event => "event",
196 },
197 selectors_str = selectors.join(",")
198 );
199
200 let res = self.get_text(&url).await?;
201 let api_response = match serde_json::from_str::<ApiResponse>(&res) {
202 Ok(inner) => inner,
203 Err(err) => {
204 eyre::bail!("Could not decode response:\n {res}.\nError: {err}")
205 }
206 };
207
208 if !api_response.ok {
209 eyre::bail!("Failed to decode:\n {res}")
210 }
211
212 let decoded = match selector_type {
213 SelectorType::Function | SelectorType::Error => api_response.result.function,
214 SelectorType::Event => api_response.result.event,
215 };
216
217 Ok(selectors
218 .into_iter()
219 .map(|selector| match decoded.get(&selector) {
220 Some(Some(r)) => Some(r.iter().map(|d| d.name.clone()).collect()),
221 _ => None,
222 })
223 .collect())
224 }
225
226 pub async fn decode_function_selector(&self, selector: &str) -> eyre::Result<Vec<String>> {
228 self.decode_selector(selector, SelectorType::Function).await
229 }
230
231 pub async fn decode_calldata(&self, calldata: &str) -> eyre::Result<Vec<String>> {
233 let calldata = calldata.strip_prefix("0x").unwrap_or(calldata);
234 if calldata.len() < 8 {
235 eyre::bail!(
236 "Calldata too short: expected at least 8 characters (excluding 0x prefix), got {}.",
237 calldata.len()
238 )
239 }
240
241 let sigs = self.decode_function_selector(&calldata[..8]).await?;
242
243 Ok(sigs
245 .iter()
246 .filter(|sig| abi_decode_calldata(sig, calldata, true, true).is_ok())
247 .cloned()
248 .collect::<Vec<String>>())
249 }
250
251 pub async fn decode_event_topic(&self, topic: &str) -> eyre::Result<Vec<String>> {
253 self.decode_selector(topic, SelectorType::Event).await
254 }
255
256 pub async fn pretty_calldata(
274 &self,
275 calldata: impl AsRef<str>,
276 offline: bool,
277 ) -> eyre::Result<PossibleSigs> {
278 let mut possible_info = PossibleSigs::new();
279 let calldata = calldata.as_ref().trim_start_matches("0x");
280
281 let selector =
282 calldata.get(..8).ok_or_else(|| eyre::eyre!("calldata cannot be less that 4 bytes"))?;
283
284 let sigs = if offline {
285 vec![]
286 } else {
287 self.decode_function_selector(selector).await.unwrap_or_default().into_iter().collect()
288 };
289 let (_, data) = calldata.split_at(8);
290
291 if data.len() % 64 != 0 {
292 eyre::bail!("\nInvalid calldata size")
293 }
294
295 let row_length = data.len() / 64;
296
297 for row in 0..row_length {
298 possible_info.data.push(data[64 * row..64 * (row + 1)].to_string());
299 }
300 if sigs.is_empty() {
301 possible_info.method = SelectorOrSig::Selector(selector.to_string());
302 } else {
303 possible_info.method = SelectorOrSig::Sig(sigs);
304 }
305 Ok(possible_info)
306 }
307
308 pub async fn import_selectors(
310 &self,
311 data: SelectorImportData,
312 ) -> eyre::Result<SelectorImportResponse> {
313 self.ensure_not_spurious()?;
314
315 let request = match data {
316 SelectorImportData::Abi(abis) => {
317 let functions_and_errors: Vec<String> = abis
318 .iter()
319 .flat_map(|abi| {
320 abi.functions()
321 .map(|func| func.signature())
322 .chain(abi.errors().map(|error| error.signature()))
323 .collect::<Vec<_>>()
324 })
325 .collect();
326
327 let events = abis
328 .iter()
329 .flat_map(|abi| abi.events().map(|event| event.signature()))
330 .collect::<Vec<_>>();
331
332 SelectorImportRequest { function: functions_and_errors, event: events }
333 }
334 SelectorImportData::Raw(raw) => {
335 let function_and_error =
336 raw.function.iter().chain(raw.error.iter()).cloned().collect::<Vec<_>>();
337 SelectorImportRequest { function: function_and_error, event: raw.event }
338 }
339 };
340
341 Ok(self.post_json(SELECTOR_IMPORT_URL, &request).await?)
342 }
343}
344
345pub enum SelectorOrSig {
346 Selector(String),
347 Sig(Vec<String>),
348}
349
350pub struct PossibleSigs {
351 method: SelectorOrSig,
352 data: Vec<String>,
353}
354
355impl PossibleSigs {
356 fn new() -> Self {
357 Self { method: SelectorOrSig::Selector("0x00000000".to_string()), data: vec![] }
358 }
359}
360
361impl fmt::Display for PossibleSigs {
362 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
363 match &self.method {
364 SelectorOrSig::Selector(selector) => {
365 writeln!(f, "\n Method: {selector}")?;
366 }
367 SelectorOrSig::Sig(sigs) => {
368 writeln!(f, "\n Possible methods:")?;
369 for sig in sigs {
370 writeln!(f, " - {sig}")?;
371 }
372 }
373 }
374
375 writeln!(f, " ------------")?;
376 for (i, row) in self.data.iter().enumerate() {
377 let row_label_decimal = i * 32;
378 let row_label_hex = format!("{row_label_decimal:03x}");
379 writeln!(f, " [{row_label_hex}]: {row}")?;
380 }
381 Ok(())
382 }
383}
384
385#[derive(Clone, Copy)]
387pub enum SelectorType {
388 Function,
390 Event,
392 Error,
394}
395
396pub async fn decode_selector(
398 selector_type: SelectorType,
399 selector: &str,
400) -> eyre::Result<Vec<String>> {
401 OpenChainClient::new()?.decode_selector(selector, selector_type).await
402}
403
404pub async fn decode_selectors(
406 selector_type: SelectorType,
407 selectors: impl IntoIterator<Item = impl Into<String>>,
408) -> eyre::Result<Vec<Option<Vec<String>>>> {
409 OpenChainClient::new()?.decode_selectors(selector_type, selectors).await
410}
411
412pub async fn decode_function_selector(selector: &str) -> eyre::Result<Vec<String>> {
414 OpenChainClient::new()?.decode_function_selector(selector).await
415}
416
417pub async fn decode_calldata(calldata: &str) -> eyre::Result<Vec<String>> {
419 OpenChainClient::new()?.decode_calldata(calldata).await
420}
421
422pub async fn decode_event_topic(topic: &str) -> eyre::Result<Vec<String>> {
424 OpenChainClient::new()?.decode_event_topic(topic).await
425}
426
427pub async fn pretty_calldata(
443 calldata: impl AsRef<str>,
444 offline: bool,
445) -> eyre::Result<PossibleSigs> {
446 OpenChainClient::new()?.pretty_calldata(calldata, offline).await
447}
448
449#[derive(Debug, Default, PartialEq, Eq, Serialize)]
450pub struct RawSelectorImportData {
451 pub function: Vec<String>,
452 pub event: Vec<String>,
453 pub error: Vec<String>,
454}
455
456impl RawSelectorImportData {
457 pub fn is_empty(&self) -> bool {
458 self.function.is_empty() && self.event.is_empty() && self.error.is_empty()
459 }
460}
461
462#[derive(Serialize)]
463#[serde(untagged)]
464pub enum SelectorImportData {
465 Abi(Vec<JsonAbi>),
466 Raw(RawSelectorImportData),
467}
468
469#[derive(Debug, Default, Serialize)]
470struct SelectorImportRequest {
471 function: Vec<String>,
472 event: Vec<String>,
473}
474
475#[derive(Debug, Deserialize)]
476struct SelectorImportEffect {
477 imported: HashMap<String, String>,
478 duplicated: HashMap<String, String>,
479}
480
481#[derive(Debug, Deserialize)]
482struct SelectorImportResult {
483 function: SelectorImportEffect,
484 event: SelectorImportEffect,
485}
486
487#[derive(Debug, Deserialize)]
488pub struct SelectorImportResponse {
489 result: SelectorImportResult,
490}
491
492impl SelectorImportResponse {
493 pub fn describe(&self) {
495 self.result.function.imported.iter().for_each(|(k, v)| {
496 let _ = sh_println!("Imported: Function {k}: {v}");
497 });
498 self.result.event.imported.iter().for_each(|(k, v)| {
499 let _ = sh_println!("Imported: Event {k}: {v}");
500 });
501 self.result.function.duplicated.iter().for_each(|(k, v)| {
502 let _ = sh_println!("Duplicated: Function {k}: {v}");
503 });
504 self.result.event.duplicated.iter().for_each(|(k, v)| {
505 let _ = sh_println!("Duplicated: Event {k}: {v}");
506 });
507
508 let _ = sh_println!("Selectors successfully uploaded to OpenChain");
509 }
510}
511
512pub async fn import_selectors(data: SelectorImportData) -> eyre::Result<SelectorImportResponse> {
514 OpenChainClient::new()?.import_selectors(data).await
515}
516
517#[derive(Debug, Default, PartialEq, Eq)]
518pub struct ParsedSignatures {
519 pub signatures: RawSelectorImportData,
520 pub abis: Vec<JsonAbi>,
521}
522
523#[derive(Deserialize)]
524struct Artifact {
525 abi: JsonAbi,
526}
527
528pub fn parse_signatures(tokens: Vec<String>) -> ParsedSignatures {
532 let abis = tokens
535 .iter()
536 .filter(|sig| sig.ends_with(".json"))
537 .filter_map(|filename| std::fs::read_to_string(filename).ok())
538 .filter_map(|file| serde_json::from_str(file.as_str()).ok())
539 .map(|artifact: Artifact| artifact.abi)
540 .collect();
541
542 let signatures = tokens.iter().filter(|sig| !sig.ends_with(".json")).fold(
545 RawSelectorImportData::default(),
546 |mut data, signature| {
547 let mut split = signature.split(' ');
548 match split.next() {
549 Some("function") => {
550 if let Some(sig) = split.next() {
551 data.function.push(sig.to_string())
552 }
553 }
554 Some("event") => {
555 if let Some(sig) = split.next() {
556 data.event.push(sig.to_string())
557 }
558 }
559 Some("error") => {
560 if let Some(sig) = split.next() {
561 data.error.push(sig.to_string())
562 }
563 }
564 Some(signature) => {
565 data.function.push(signature.to_string());
567 }
568 None => {}
569 }
570 data
571 },
572 );
573
574 ParsedSignatures { signatures, abis }
575}
576
577#[cfg(test)]
578mod tests {
579 use super::*;
580
581 #[test]
582 fn test_parse_signatures() {
583 let result = parse_signatures(vec!["transfer(address,uint256)".to_string()]);
584 assert_eq!(
585 result,
586 ParsedSignatures {
587 signatures: RawSelectorImportData {
588 function: vec!["transfer(address,uint256)".to_string()],
589 ..Default::default()
590 },
591 ..Default::default()
592 }
593 );
594
595 let result = parse_signatures(vec![
596 "transfer(address,uint256)".to_string(),
597 "function approve(address,uint256)".to_string(),
598 ]);
599 assert_eq!(
600 result,
601 ParsedSignatures {
602 signatures: RawSelectorImportData {
603 function: vec![
604 "transfer(address,uint256)".to_string(),
605 "approve(address,uint256)".to_string()
606 ],
607 ..Default::default()
608 },
609 ..Default::default()
610 }
611 );
612
613 let result = parse_signatures(vec![
614 "transfer(address,uint256)".to_string(),
615 "event Approval(address,address,uint256)".to_string(),
616 "error ERC20InsufficientBalance(address,uint256,uint256)".to_string(),
617 ]);
618 assert_eq!(
619 result,
620 ParsedSignatures {
621 signatures: RawSelectorImportData {
622 function: vec!["transfer(address,uint256)".to_string()],
623 event: vec!["Approval(address,address,uint256)".to_string()],
624 error: vec!["ERC20InsufficientBalance(address,uint256,uint256)".to_string()]
625 },
626 ..Default::default()
627 }
628 );
629
630 let result = parse_signatures(vec!["event".to_string()]);
632 assert_eq!(
633 result,
634 ParsedSignatures { signatures: Default::default(), ..Default::default() }
635 );
636 }
637}