foundry_common/
selectors.rs

1//! Support for handling/identifying selectors.
2
3#![allow(missing_docs)]
4
5use crate::{abi::abi_decode_calldata, provider::runtime_transport::RuntimeTransportBuilder};
6use alloy_json_abi::JsonAbi;
7use alloy_primitives::{map::HashMap, Selector, B256};
8use eyre::Context;
9use itertools::Itertools;
10use serde::{de::DeserializeOwned, Deserialize, Serialize};
11use std::{
12    fmt,
13    sync::{
14        atomic::{AtomicBool, AtomicUsize, Ordering},
15        Arc,
16    },
17    time::Duration,
18};
19
20const BASE_URL: &str = "https://api.openchain.xyz";
21const SELECTOR_LOOKUP_URL: &str = "https://api.openchain.xyz/signature-database/v1/lookup";
22const SELECTOR_IMPORT_URL: &str = "https://api.openchain.xyz/signature-database/v1/import";
23
24/// The standard request timeout for API requests.
25const REQ_TIMEOUT: Duration = Duration::from_secs(15);
26
27/// How many request can time out before we decide this is a spurious connection.
28const MAX_TIMEDOUT_REQ: usize = 4usize;
29
30/// List of signatures for a given [`SelectorKind`].
31pub type OpenChainSignatures = Vec<String>;
32
33/// A client that can request API data from OpenChain.
34#[derive(Clone, Debug)]
35pub struct OpenChainClient {
36    inner: reqwest::Client,
37    /// Whether the connection is spurious, or API is down
38    spurious_connection: Arc<AtomicBool>,
39    /// How many requests timed out
40    timedout_requests: Arc<AtomicUsize>,
41    /// Max allowed request that can time out
42    max_timedout_requests: usize,
43}
44
45impl OpenChainClient {
46    /// Creates a new client with default settings.
47    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    /// Sends a new post request
74    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            // Error HTTP codes (5xx) are considered connectivity issues and will prompt retry
97            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 >= self.max_timedout_requests {
110                self.set_spurious();
111            }
112        }
113    }
114
115    /// Returns whether the connection was marked as spurious
116    fn is_spurious(&self) -> bool {
117        self.spurious_connection.load(Ordering::Relaxed)
118    }
119
120    /// Marks the connection as spurious
121    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    /// Decodes the given function or event selector using OpenChain
133    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    /// Decodes the given function, error or event selectors using OpenChain.
141    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        // Exit early if spurious connection.
156        self.ensure_not_spurious()?;
157
158        // Build the URL with the query string.
159        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) => eyre::bail!("could not decode response: {err}: {text}"),
177        };
178        if !ok {
179            eyre::bail!("OpenChain returned an error: {text}");
180        }
181
182        Ok(selectors
183            .iter()
184            .map(|selector| {
185                let signatures = match selector {
186                    SelectorKind::Function(selector) | SelectorKind::Error(selector) => {
187                        result.function.get(selector)
188                    }
189                    SelectorKind::Event(hash) => result.event.get(hash),
190                };
191                signatures
192                    .map(Option::as_deref)
193                    .unwrap_or_default()
194                    .unwrap_or_default()
195                    .iter()
196                    .map(|sig| sig.name.clone())
197                    .collect()
198            })
199            .collect())
200    }
201
202    /// Fetches a function signature given the selector using OpenChain
203    pub async fn decode_function_selector(
204        &self,
205        selector: Selector,
206    ) -> eyre::Result<OpenChainSignatures> {
207        self.decode_selector(SelectorKind::Function(selector)).await
208    }
209
210    /// Fetches all possible signatures and attempts to abi decode the calldata
211    pub async fn decode_calldata(&self, calldata: &str) -> eyre::Result<OpenChainSignatures> {
212        let calldata = calldata.strip_prefix("0x").unwrap_or(calldata);
213        if calldata.len() < 8 {
214            eyre::bail!(
215                "Calldata too short: expected at least 8 characters (excluding 0x prefix), got {}.",
216                calldata.len()
217            )
218        }
219
220        let mut sigs = self.decode_function_selector(calldata[..8].parse().unwrap()).await?;
221        // Retain only signatures that can be decoded.
222        sigs.retain(|sig| abi_decode_calldata(sig, calldata, true, true).is_ok());
223        Ok(sigs)
224    }
225
226    /// Fetches an event signature given the 32 byte topic using OpenChain.
227    pub async fn decode_event_topic(&self, topic: B256) -> eyre::Result<OpenChainSignatures> {
228        self.decode_selector(SelectorKind::Event(topic)).await
229    }
230
231    /// Pretty print calldata and if available, fetch possible function signatures
232    ///
233    /// ```no_run
234    /// use foundry_common::selectors::OpenChainClient;
235    ///
236    /// # async fn foo() -> eyre::Result<()> {
237    /// let pretty_data = OpenChainClient::new()?
238    ///     .pretty_calldata(
239    ///         "0x70a08231000000000000000000000000d0074f4e6490ae3f888d1d4f7e3e43326bd3f0f5"
240    ///             .to_string(),
241    ///         false,
242    ///     )
243    ///     .await?;
244    /// println!("{}", pretty_data);
245    /// # Ok(())
246    /// # }
247    /// ```
248    pub async fn pretty_calldata(
249        &self,
250        calldata: impl AsRef<str>,
251        offline: bool,
252    ) -> eyre::Result<PossibleSigs> {
253        let mut possible_info = PossibleSigs::new();
254        let calldata = calldata.as_ref().trim_start_matches("0x");
255
256        let selector =
257            calldata.get(..8).ok_or_else(|| eyre::eyre!("calldata cannot be less that 4 bytes"))?;
258
259        let sigs = if offline {
260            vec![]
261        } else {
262            let selector = selector.parse()?;
263            self.decode_function_selector(selector).await.unwrap_or_default().into_iter().collect()
264        };
265        let (_, data) = calldata.split_at(8);
266
267        if data.len() % 64 != 0 {
268            eyre::bail!("\nInvalid calldata size")
269        }
270
271        let row_length = data.len() / 64;
272
273        for row in 0..row_length {
274            possible_info.data.push(data[64 * row..64 * (row + 1)].to_string());
275        }
276        if sigs.is_empty() {
277            possible_info.method = SelectorOrSig::Selector(selector.to_string());
278        } else {
279            possible_info.method = SelectorOrSig::Sig(sigs);
280        }
281        Ok(possible_info)
282    }
283
284    /// uploads selectors to OpenChain using the given data
285    pub async fn import_selectors(
286        &self,
287        data: SelectorImportData,
288    ) -> eyre::Result<SelectorImportResponse> {
289        self.ensure_not_spurious()?;
290
291        let request = match data {
292            SelectorImportData::Abi(abis) => {
293                let functions_and_errors: OpenChainSignatures = abis
294                    .iter()
295                    .flat_map(|abi| {
296                        abi.functions()
297                            .map(|func| func.signature())
298                            .chain(abi.errors().map(|error| error.signature()))
299                            .collect::<Vec<_>>()
300                    })
301                    .collect();
302
303                let events = abis
304                    .iter()
305                    .flat_map(|abi| abi.events().map(|event| event.signature()))
306                    .collect::<Vec<_>>();
307
308                SelectorImportRequest { function: functions_and_errors, event: events }
309            }
310            SelectorImportData::Raw(raw) => {
311                let function_and_error =
312                    raw.function.iter().chain(raw.error.iter()).cloned().collect::<Vec<_>>();
313                SelectorImportRequest { function: function_and_error, event: raw.event }
314            }
315        };
316
317        Ok(self.post_json(SELECTOR_IMPORT_URL, &request).await?)
318    }
319}
320
321pub enum SelectorOrSig {
322    Selector(String),
323    Sig(OpenChainSignatures),
324}
325
326pub struct PossibleSigs {
327    method: SelectorOrSig,
328    data: OpenChainSignatures,
329}
330
331impl PossibleSigs {
332    fn new() -> Self {
333        Self { method: SelectorOrSig::Selector("0x00000000".to_string()), data: vec![] }
334    }
335}
336
337impl fmt::Display for PossibleSigs {
338    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
339        match &self.method {
340            SelectorOrSig::Selector(selector) => {
341                writeln!(f, "\n Method: {selector}")?;
342            }
343            SelectorOrSig::Sig(sigs) => {
344                writeln!(f, "\n Possible methods:")?;
345                for sig in sigs {
346                    writeln!(f, " - {sig}")?;
347                }
348            }
349        }
350
351        writeln!(f, " ------------")?;
352        for (i, row) in self.data.iter().enumerate() {
353            let row_label_decimal = i * 32;
354            let row_label_hex = format!("{row_label_decimal:03x}");
355            writeln!(f, " [{row_label_hex}]: {row}")?;
356        }
357        Ok(())
358    }
359}
360
361/// The kind of selector to fetch from OpenChain.
362#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
363pub enum SelectorKind {
364    /// A function selector.
365    Function(Selector),
366    /// A custom error selector. Behaves the same as a function selector.
367    Error(Selector),
368    /// An event selector.
369    Event(B256),
370}
371
372impl SelectorKind {
373    /// Returns the function selector if it is a function OR custom error.
374    pub fn as_function(&self) -> Option<Selector> {
375        match *self {
376            Self::Function(selector) | Self::Error(selector) => Some(selector),
377            _ => None,
378        }
379    }
380
381    /// Returns the event selector if it is an event.
382    pub fn as_event(&self) -> Option<B256> {
383        match *self {
384            Self::Event(hash) => Some(hash),
385            _ => None,
386        }
387    }
388}
389
390/// Decodes the given function or event selector using OpenChain.
391pub async fn decode_selector(selector: SelectorKind) -> eyre::Result<OpenChainSignatures> {
392    OpenChainClient::new()?.decode_selector(selector).await
393}
394
395/// Decodes the given function or event selectors using OpenChain.
396pub async fn decode_selectors(
397    selectors: &[SelectorKind],
398) -> eyre::Result<Vec<OpenChainSignatures>> {
399    OpenChainClient::new()?.decode_selectors(selectors).await
400}
401
402/// Fetches a function signature given the selector using OpenChain.
403pub async fn decode_function_selector(selector: Selector) -> eyre::Result<OpenChainSignatures> {
404    OpenChainClient::new()?.decode_function_selector(selector).await
405}
406
407/// Fetches all possible signatures and attempts to abi decode the calldata using OpenChain.
408pub async fn decode_calldata(calldata: &str) -> eyre::Result<OpenChainSignatures> {
409    OpenChainClient::new()?.decode_calldata(calldata).await
410}
411
412/// Fetches an event signature given the 32 byte topic using OpenChain.
413pub async fn decode_event_topic(topic: B256) -> eyre::Result<OpenChainSignatures> {
414    OpenChainClient::new()?.decode_event_topic(topic).await
415}
416
417/// Pretty print calldata and if available, fetch possible function signatures.
418///
419/// ```no_run
420/// use foundry_common::selectors::pretty_calldata;
421///
422/// # async fn foo() -> eyre::Result<()> {
423/// let pretty_data = pretty_calldata(
424///     "0x70a08231000000000000000000000000d0074f4e6490ae3f888d1d4f7e3e43326bd3f0f5".to_string(),
425///     false,
426/// )
427/// .await?;
428/// println!("{}", pretty_data);
429/// # Ok(())
430/// # }
431/// ```
432pub async fn pretty_calldata(
433    calldata: impl AsRef<str>,
434    offline: bool,
435) -> eyre::Result<PossibleSigs> {
436    OpenChainClient::new()?.pretty_calldata(calldata, offline).await
437}
438
439#[derive(Debug, Default, PartialEq, Eq, Serialize)]
440pub struct RawSelectorImportData {
441    pub function: OpenChainSignatures,
442    pub event: OpenChainSignatures,
443    pub error: OpenChainSignatures,
444}
445
446impl RawSelectorImportData {
447    pub fn is_empty(&self) -> bool {
448        self.function.is_empty() && self.event.is_empty() && self.error.is_empty()
449    }
450}
451
452#[derive(Serialize)]
453#[serde(untagged)]
454pub enum SelectorImportData {
455    Abi(Vec<JsonAbi>),
456    Raw(RawSelectorImportData),
457}
458
459#[derive(Debug, Default, Serialize)]
460struct SelectorImportRequest {
461    function: OpenChainSignatures,
462    event: OpenChainSignatures,
463}
464
465#[derive(Debug, Deserialize)]
466struct SelectorImportEffect {
467    imported: HashMap<String, String>,
468    duplicated: HashMap<String, String>,
469}
470
471#[derive(Debug, Deserialize)]
472struct SelectorImportResult {
473    function: SelectorImportEffect,
474    event: SelectorImportEffect,
475}
476
477#[derive(Debug, Deserialize)]
478pub struct SelectorImportResponse {
479    result: SelectorImportResult,
480}
481
482impl SelectorImportResponse {
483    /// Print info about the functions which were uploaded or already known
484    pub fn describe(&self) {
485        self.result.function.imported.iter().for_each(|(k, v)| {
486            let _ = sh_println!("Imported: Function {k}: {v}");
487        });
488        self.result.event.imported.iter().for_each(|(k, v)| {
489            let _ = sh_println!("Imported: Event {k}: {v}");
490        });
491        self.result.function.duplicated.iter().for_each(|(k, v)| {
492            let _ = sh_println!("Duplicated: Function {k}: {v}");
493        });
494        self.result.event.duplicated.iter().for_each(|(k, v)| {
495            let _ = sh_println!("Duplicated: Event {k}: {v}");
496        });
497
498        let _ = sh_println!("Selectors successfully uploaded to OpenChain");
499    }
500}
501
502/// uploads selectors to OpenChain using the given data
503pub async fn import_selectors(data: SelectorImportData) -> eyre::Result<SelectorImportResponse> {
504    OpenChainClient::new()?.import_selectors(data).await
505}
506
507#[derive(Debug, Default, PartialEq, Eq)]
508pub struct ParsedSignatures {
509    pub signatures: RawSelectorImportData,
510    pub abis: Vec<JsonAbi>,
511}
512
513#[derive(Deserialize)]
514struct Artifact {
515    abi: JsonAbi,
516}
517
518/// Parses a list of tokens into function, event, and error signatures.
519/// Also handles JSON artifact files
520/// Ignores invalid tokens
521pub fn parse_signatures(tokens: Vec<String>) -> ParsedSignatures {
522    // if any of the given tokens are json artifact files,
523    // Parse them and read in the ABI from the file
524    let abis = tokens
525        .iter()
526        .filter(|sig| sig.ends_with(".json"))
527        .filter_map(|filename| std::fs::read_to_string(filename).ok())
528        .filter_map(|file| serde_json::from_str(file.as_str()).ok())
529        .map(|artifact: Artifact| artifact.abi)
530        .collect();
531
532    // for tokens that are not json artifact files,
533    // try to parse them as raw signatures
534    let signatures = tokens.iter().filter(|sig| !sig.ends_with(".json")).fold(
535        RawSelectorImportData::default(),
536        |mut data, signature| {
537            let mut split = signature.split(' ');
538            match split.next() {
539                Some("function") => {
540                    if let Some(sig) = split.next() {
541                        data.function.push(sig.to_string())
542                    }
543                }
544                Some("event") => {
545                    if let Some(sig) = split.next() {
546                        data.event.push(sig.to_string())
547                    }
548                }
549                Some("error") => {
550                    if let Some(sig) = split.next() {
551                        data.error.push(sig.to_string())
552                    }
553                }
554                Some(signature) => {
555                    // if no type given, assume function
556                    data.function.push(signature.to_string());
557                }
558                None => {}
559            }
560            data
561        },
562    );
563
564    ParsedSignatures { signatures, abis }
565}
566
567/// [`SELECTOR_LOOKUP_URL`] response.
568#[derive(Deserialize)]
569struct SignatureResponse {
570    ok: bool,
571    result: SignatureResult,
572}
573
574#[derive(Deserialize)]
575struct SignatureResult {
576    event: HashMap<B256, Option<Vec<Signature>>>,
577    function: HashMap<Selector, Option<Vec<Signature>>>,
578}
579
580#[derive(Deserialize)]
581struct Signature {
582    name: String,
583}
584
585#[cfg(test)]
586mod tests {
587    use super::*;
588
589    #[test]
590    fn test_parse_signatures() {
591        let result = parse_signatures(vec!["transfer(address,uint256)".to_string()]);
592        assert_eq!(
593            result,
594            ParsedSignatures {
595                signatures: RawSelectorImportData {
596                    function: vec!["transfer(address,uint256)".to_string()],
597                    ..Default::default()
598                },
599                ..Default::default()
600            }
601        );
602
603        let result = parse_signatures(vec![
604            "transfer(address,uint256)".to_string(),
605            "function approve(address,uint256)".to_string(),
606        ]);
607        assert_eq!(
608            result,
609            ParsedSignatures {
610                signatures: RawSelectorImportData {
611                    function: vec![
612                        "transfer(address,uint256)".to_string(),
613                        "approve(address,uint256)".to_string()
614                    ],
615                    ..Default::default()
616                },
617                ..Default::default()
618            }
619        );
620
621        let result = parse_signatures(vec![
622            "transfer(address,uint256)".to_string(),
623            "event Approval(address,address,uint256)".to_string(),
624            "error ERC20InsufficientBalance(address,uint256,uint256)".to_string(),
625        ]);
626        assert_eq!(
627            result,
628            ParsedSignatures {
629                signatures: RawSelectorImportData {
630                    function: vec!["transfer(address,uint256)".to_string()],
631                    event: vec!["Approval(address,address,uint256)".to_string()],
632                    error: vec!["ERC20InsufficientBalance(address,uint256,uint256)".to_string()]
633                },
634                ..Default::default()
635            }
636        );
637
638        // skips invalid
639        let result = parse_signatures(vec!["event".to_string()]);
640        assert_eq!(
641            result,
642            ParsedSignatures { signatures: Default::default(), ..Default::default() }
643        );
644    }
645}