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;
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
23/// The standard request timeout for API requests.
24const REQ_TIMEOUT: Duration = Duration::from_secs(15);
25
26/// How many request can time out before we decide this is a spurious connection.
27const MAX_TIMEDOUT_REQ: usize = 4usize;
28
29/// A client that can request API data from OpenChain.
30#[derive(Clone, Debug)]
31pub struct OpenChainClient {
32    inner: reqwest::Client,
33    /// Whether the connection is spurious, or API is down
34    spurious_connection: Arc<AtomicBool>,
35    /// How many requests timed out
36    timedout_requests: Arc<AtomicUsize>,
37    /// Max allowed request that can time out
38    max_timedout_requests: usize,
39}
40
41impl OpenChainClient {
42    /// Creates a new client with default settings.
43    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    /// Sends a new post request
70    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            // Error HTTP codes (5xx) are considered connectivity issues and will prompt retry
93            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    /// Returns whether the connection was marked as spurious
112    fn is_spurious(&self) -> bool {
113        self.spurious_connection.load(Ordering::Relaxed)
114    }
115
116    /// Marks the connection as spurious
117    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    /// Decodes the given function or event selector using OpenChain
129    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() // Not returning on the previous line ensures a vector with exactly 1 element
137            .unwrap()
138            .ok_or_else(|| eyre::eyre!("No signature found"))
139    }
140
141    /// Decodes the given function, error or event selectors using OpenChain.
142    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        // exit early if spurious connection
162        self.ensure_not_spurious()?;
163
164        let expected_len = match selector_type {
165            SelectorType::Function | SelectorType::Error => 10, // 0x + hex(4bytes)
166            SelectorType::Event => 66,                          // 0x + hex(32bytes)
167        };
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    /// Fetches a function signature given the selector using OpenChain
227    pub async fn decode_function_selector(&self, selector: &str) -> eyre::Result<Vec<String>> {
228        self.decode_selector(selector, SelectorType::Function).await
229    }
230
231    /// Fetches all possible signatures and attempts to abi decode the calldata
232    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        // filter for signatures that can be decoded
244        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    /// Fetches an event signature given the 32 byte topic using OpenChain
252    pub async fn decode_event_topic(&self, topic: &str) -> eyre::Result<Vec<String>> {
253        self.decode_selector(topic, SelectorType::Event).await
254    }
255
256    /// Pretty print calldata and if available, fetch possible function signatures
257    ///
258    /// ```no_run
259    /// use foundry_common::selectors::OpenChainClient;
260    ///
261    /// # async fn foo() -> eyre::Result<()> {
262    /// let pretty_data = OpenChainClient::new()?
263    ///     .pretty_calldata(
264    ///         "0x70a08231000000000000000000000000d0074f4e6490ae3f888d1d4f7e3e43326bd3f0f5"
265    ///             .to_string(),
266    ///         false,
267    ///     )
268    ///     .await?;
269    /// println!("{}", pretty_data);
270    /// # Ok(())
271    /// # }
272    /// ```
273    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    /// uploads selectors to OpenChain using the given data
309    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/// The type of selector fetched from OpenChain.
386#[derive(Clone, Copy)]
387pub enum SelectorType {
388    /// A function selector.
389    Function,
390    /// An event selector.
391    Event,
392    /// An custom error selector.
393    Error,
394}
395
396/// Decodes the given function or event selector using OpenChain.
397pub 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
404/// Decodes the given function or event selectors using OpenChain.
405pub 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
412/// Fetches a function signature given the selector using OpenChain.
413pub async fn decode_function_selector(selector: &str) -> eyre::Result<Vec<String>> {
414    OpenChainClient::new()?.decode_function_selector(selector).await
415}
416
417/// Fetches all possible signatures and attempts to abi decode the calldata using OpenChain.
418pub async fn decode_calldata(calldata: &str) -> eyre::Result<Vec<String>> {
419    OpenChainClient::new()?.decode_calldata(calldata).await
420}
421
422/// Fetches an event signature given the 32 byte topic using OpenChain.
423pub async fn decode_event_topic(topic: &str) -> eyre::Result<Vec<String>> {
424    OpenChainClient::new()?.decode_event_topic(topic).await
425}
426
427/// Pretty print calldata and if available, fetch possible function signatures.
428///
429/// ```no_run
430/// use foundry_common::selectors::pretty_calldata;
431///
432/// # async fn foo() -> eyre::Result<()> {
433/// let pretty_data = pretty_calldata(
434///     "0x70a08231000000000000000000000000d0074f4e6490ae3f888d1d4f7e3e43326bd3f0f5".to_string(),
435///     false,
436/// )
437/// .await?;
438/// println!("{}", pretty_data);
439/// # Ok(())
440/// # }
441/// ```
442pub 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    /// Print info about the functions which were uploaded or already known
494    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
512/// uploads selectors to OpenChain using the given data
513pub 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
528/// Parses a list of tokens into function, event, and error signatures.
529/// Also handles JSON artifact files
530/// Ignores invalid tokens
531pub fn parse_signatures(tokens: Vec<String>) -> ParsedSignatures {
532    // if any of the given tokens are json artifact files,
533    // Parse them and read in the ABI from the file
534    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    // for tokens that are not json artifact files,
543    // try to parse them as raw signatures
544    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                    // if no type given, assume function
566                    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        // skips invalid
631        let result = parse_signatures(vec!["event".to_string()]);
632        assert_eq!(
633            result,
634            ParsedSignatures { signatures: Default::default(), ..Default::default() }
635        );
636    }
637}