anvil_zksync_traces/decode/
revert_decoder.rs

1//! Various utilities to decode test results.
2//////////////////////////////////////////////////////////////////////////////////////
3// Attribution: File adapted from the `foundry-evm` crate                           //
4//                                                                                  //
5// Full credit goes to its authors. See the original implementation here:           //
6// https://github.com/foundry-rs/foundry/blob/master/crates/evm/core/src/decode.rs. //
7//                                                                                  //
8// Note: These methods are used under the terms of the original project's license.  //
9//////////////////////////////////////////////////////////////////////////////////////
10
11use super::{decode_value, SELECTOR_LEN};
12use alloy::dyn_abi::JsonAbiExt;
13use alloy::json_abi::{Error, JsonAbi};
14use alloy::primitives::{map::HashMap, Selector};
15use alloy::sol_types::{SolInterface, SolValue};
16use anvil_zksync_types::traces::DecodedError;
17use std::sync::OnceLock;
18
19/// Decodes revert data.
20#[derive(Clone, Debug, Default)]
21pub struct RevertDecoder {
22    /// The custom errors to use for decoding.
23    pub errors: HashMap<Selector, Vec<Error>>,
24}
25
26impl Default for &RevertDecoder {
27    fn default() -> Self {
28        static EMPTY: OnceLock<RevertDecoder> = OnceLock::new();
29        EMPTY.get_or_init(RevertDecoder::new)
30    }
31}
32
33impl RevertDecoder {
34    /// Creates a new, empty revert decoder.
35    pub fn new() -> Self {
36        Self::default()
37    }
38
39    /// Sets the ABIs to use for error decoding.
40    ///
41    /// Note that this is decently expensive as it will hash all errors for faster indexing.
42    pub fn with_abis<'a>(mut self, abi: impl IntoIterator<Item = &'a JsonAbi>) -> Self {
43        self.extend_from_abis(abi);
44        self
45    }
46
47    /// Sets the ABI to use for error decoding.
48    ///
49    /// Note that this is decently expensive as it will hash all errors for faster indexing.
50    pub fn with_abi(mut self, abi: &JsonAbi) -> Self {
51        self.extend_from_abi(abi);
52        self
53    }
54
55    /// Sets the ABI to use for error decoding, if it is present.
56    ///
57    /// Note that this is decently expensive as it will hash all errors for faster indexing.
58    pub fn with_abi_opt(mut self, abi: Option<&JsonAbi>) -> Self {
59        if let Some(abi) = abi {
60            self.extend_from_abi(abi);
61        }
62        self
63    }
64
65    /// Extends the decoder with the given ABI's custom errors.
66    pub fn extend_from_abis<'a>(&mut self, abi: impl IntoIterator<Item = &'a JsonAbi>) {
67        for abi in abi {
68            self.extend_from_abi(abi);
69        }
70    }
71
72    /// Extends the decoder with the given ABI's custom errors.
73    pub fn extend_from_abi(&mut self, abi: &JsonAbi) {
74        for error in abi.errors() {
75            self.push_error(error.clone());
76        }
77    }
78
79    /// Adds a custom error to use for decoding.
80    pub fn push_error(&mut self, error: Error) {
81        self.errors.entry(error.selector()).or_default().push(error);
82    }
83
84    /// Tries to decode an error message from the given revert bytes.
85    ///
86    /// Note that this is just a best-effort guess, and should not be relied upon for anything other
87    /// than user output.
88    pub fn decode(&self, err: &[u8]) -> DecodedError {
89        self.maybe_decode(err).unwrap_or_else(|| {
90            if err.is_empty() {
91                // Empty revert data
92                DecodedError::Empty
93            } else {
94                DecodedError::Raw(err.to_vec())
95            }
96        })
97    }
98
99    /// Tries to decode an error message from the given revert bytes.
100    ///
101    /// See [`decode`](Self::decode) for more information.
102    pub fn maybe_decode(&self, err: &[u8]) -> Option<DecodedError> {
103        let Some((selector, data)) = err.split_first_chunk::<SELECTOR_LEN>() else {
104            return if err.is_empty() {
105                None
106            } else {
107                Some(DecodedError::Raw(err.to_vec()))
108            };
109        };
110
111        // Solidity's `Error(string)` or `Panic(uint256)`
112        if let Ok(e) =
113            alloy::sol_types::ContractError::<std::convert::Infallible>::abi_decode(err, false)
114        {
115            return match e {
116                alloy::sol_types::ContractError::CustomError(_) => unreachable!(),
117                alloy::sol_types::ContractError::Revert(revert) => {
118                    Some(DecodedError::Revert(revert.reason))
119                }
120                alloy::sol_types::ContractError::Panic(panic) => {
121                    Some(DecodedError::Panic(panic.to_string()))
122                }
123            };
124        }
125
126        // Custom errors.
127        if let Some(errors) = self.errors.get(selector) {
128            for error in errors {
129                // If we don't decode, don't return an error, try to decode as a string later.
130                if let Ok(decoded) = error.abi_decode_input(data, false) {
131                    return Some(DecodedError::CustomError {
132                        name: error.name.to_owned(),
133                        fields: decoded.into_iter().map(decode_value).collect(),
134                    });
135                }
136            }
137        }
138
139        // ABI-encoded `string`.
140        if let Ok(s) = String::abi_decode(err, true) {
141            return Some(DecodedError::Revert(s));
142        }
143
144        // ASCII string.
145        if err.is_ascii() {
146            return Some(DecodedError::Revert(
147                std::str::from_utf8(err).unwrap().to_string(),
148            ));
149        }
150
151        // Generic custom error.
152        Some(DecodedError::GenericCustomError {
153            selector: *selector,
154            raw: data.to_vec(),
155        })
156    }
157}