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}