foundry_cheatcodes/
error.rs

1use crate::Vm;
2use alloy_primitives::{hex, Address, Bytes};
3use alloy_signer::Error as SignerError;
4use alloy_signer_local::LocalSignerError;
5use alloy_sol_types::SolError;
6use foundry_common::errors::FsPathError;
7use foundry_config::UnresolvedEnvVarError;
8use foundry_evm_core::backend::{BackendError, DatabaseError};
9use foundry_wallets::error::WalletSignerError;
10use k256::ecdsa::signature::Error as SignatureError;
11use revm::primitives::EVMError;
12use std::{borrow::Cow, fmt};
13
14/// Cheatcode result type.
15///
16/// Type alias with a default Ok type of [`Vec<u8>`], and default Err type of [`Error`].
17pub type Result<T = Vec<u8>, E = Error> = std::result::Result<T, E>;
18
19macro_rules! fmt_err {
20    ($msg:literal $(,)?) => {
21        $crate::Error::fmt(::std::format_args!($msg))
22    };
23    ($err:expr $(,)?) => {
24        <$crate::Error as ::std::convert::From<_>>::from($err)
25    };
26    ($fmt:expr, $($arg:tt)*) => {
27        $crate::Error::fmt(::std::format_args!($fmt, $($arg)*))
28    };
29}
30
31macro_rules! bail {
32    ($msg:literal $(,)?) => {
33        return ::std::result::Result::Err(fmt_err!($msg))
34    };
35    ($err:expr $(,)?) => {
36        return ::std::result::Result::Err(fmt_err!($err))
37    };
38    ($fmt:expr, $($arg:tt)*) => {
39        return ::std::result::Result::Err(fmt_err!($fmt, $($arg)*))
40    };
41}
42
43macro_rules! ensure {
44    ($cond:expr $(,)?) => {
45        if !$cond {
46            return ::std::result::Result::Err($crate::Error::custom(
47                ::std::concat!("Condition failed: `", ::std::stringify!($cond), "`")
48            ));
49        }
50    };
51    ($cond:expr, $msg:literal $(,)?) => {
52        if !$cond {
53            return ::std::result::Result::Err(fmt_err!($msg));
54        }
55    };
56    ($cond:expr, $err:expr $(,)?) => {
57        if !$cond {
58            return ::std::result::Result::Err(fmt_err!($err));
59        }
60    };
61    ($cond:expr, $fmt:expr, $($arg:tt)*) => {
62        if !$cond {
63            return ::std::result::Result::Err(fmt_err!($fmt, $($arg)*));
64        }
65    };
66}
67
68macro_rules! ensure_not_precompile {
69    ($address:expr, $ctxt:expr) => {
70        if $ctxt.is_precompile($address) {
71            return Err($crate::error::precompile_error($address));
72        }
73    };
74}
75
76#[cold]
77pub(crate) fn precompile_error(address: &Address) -> Error {
78    fmt_err!("cannot use precompile {address} as an argument")
79}
80
81/// Error thrown by cheatcodes.
82// This uses a custom repr to minimize the size of the error.
83// The repr is basically `enum { Cow<'static, str>, Cow<'static, [u8]> }`
84pub struct Error {
85    /// If true, encode `data` as `Error(string)`, otherwise encode it directly as `bytes`.
86    is_str: bool,
87    /// Whether this was constructed from an owned byte vec, which means we have to drop the data
88    /// in `impl Drop`.
89    drop: bool,
90    /// The error data. Always a valid pointer, and never modified.
91    data: *const [u8],
92}
93
94impl std::error::Error for Error {}
95
96impl fmt::Debug for Error {
97    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98        f.write_str("Error::")?;
99        self.kind().fmt(f)
100    }
101}
102
103impl fmt::Display for Error {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        self.kind().fmt(f)
106    }
107}
108
109/// Kind of cheatcode errors.
110///
111/// Constructed by [`Error::kind`].
112#[derive(Debug)]
113pub enum ErrorKind<'a> {
114    /// A string error, ABI-encoded as `CheatcodeError(string)`.
115    String(&'a str),
116    /// A raw bytes error. Does not get encoded.
117    Bytes(&'a [u8]),
118}
119
120impl fmt::Display for ErrorKind<'_> {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        match *self {
123            Self::String(ss) => f.write_str(ss),
124            Self::Bytes(b) => f.write_str(&hex::encode_prefixed(b)),
125        }
126    }
127}
128
129impl Error {
130    /// Creates a new error and ABI encodes it as `CheatcodeError(string)`.
131    pub fn encode(error: impl Into<Self>) -> Bytes {
132        error.into().abi_encode().into()
133    }
134
135    /// Creates a new error with a custom message.
136    pub fn display(msg: impl fmt::Display) -> Self {
137        Self::fmt(format_args!("{msg}"))
138    }
139
140    /// Creates a new error with a custom [`fmt::Arguments`] message.
141    pub fn fmt(args: fmt::Arguments<'_>) -> Self {
142        match args.as_str() {
143            Some(s) => Self::new_str(s),
144            None => Self::new_string(std::fmt::format(args)),
145        }
146    }
147
148    /// ABI-encodes this error as `CheatcodeError(string)` if the inner message is a string,
149    /// otherwise returns the raw bytes.
150    pub fn abi_encode(&self) -> Vec<u8> {
151        match self.kind() {
152            ErrorKind::String(string) => Vm::CheatcodeError { message: string.into() }.abi_encode(),
153            ErrorKind::Bytes(bytes) => bytes.into(),
154        }
155    }
156
157    /// Returns the kind of this error.
158    #[inline]
159    pub fn kind(&self) -> ErrorKind<'_> {
160        let data = self.data();
161        if self.is_str {
162            debug_assert!(std::str::from_utf8(data).is_ok());
163            ErrorKind::String(unsafe { std::str::from_utf8_unchecked(data) })
164        } else {
165            ErrorKind::Bytes(data)
166        }
167    }
168
169    /// Returns the raw data of this error.
170    #[inline]
171    pub fn data(&self) -> &[u8] {
172        unsafe { &*self.data }
173    }
174
175    /// Returns `true` if this error is a human-readable string.
176    #[inline]
177    pub fn is_str(&self) -> bool {
178        self.is_str
179    }
180
181    #[inline]
182    fn new_str(data: &'static str) -> Self {
183        Self::_new(true, false, data.as_bytes())
184    }
185
186    #[inline]
187    fn new_string(data: String) -> Self {
188        Self::_new(true, true, Box::into_raw(data.into_boxed_str().into_boxed_bytes()))
189    }
190
191    #[inline]
192    fn new_bytes(data: &'static [u8]) -> Self {
193        Self::_new(false, false, data)
194    }
195
196    #[inline]
197    fn new_vec(data: Vec<u8>) -> Self {
198        Self::_new(false, true, Box::into_raw(data.into_boxed_slice()))
199    }
200
201    #[inline]
202    fn _new(is_str: bool, drop: bool, data: *const [u8]) -> Self {
203        debug_assert!(!data.is_null());
204        Self { is_str, drop, data }
205    }
206}
207
208impl Drop for Error {
209    fn drop(&mut self) {
210        if self.drop {
211            drop(unsafe { Box::<[u8]>::from_raw(self.data.cast_mut()) });
212        }
213    }
214}
215
216impl From<Cow<'static, str>> for Error {
217    fn from(value: Cow<'static, str>) -> Self {
218        match value {
219            Cow::Borrowed(str) => Self::new_str(str),
220            Cow::Owned(string) => Self::new_string(string),
221        }
222    }
223}
224
225impl From<String> for Error {
226    fn from(value: String) -> Self {
227        Self::new_string(value)
228    }
229}
230
231impl From<&'static str> for Error {
232    fn from(value: &'static str) -> Self {
233        Self::new_str(value)
234    }
235}
236
237impl From<Cow<'static, [u8]>> for Error {
238    fn from(value: Cow<'static, [u8]>) -> Self {
239        match value {
240            Cow::Borrowed(bytes) => Self::new_bytes(bytes),
241            Cow::Owned(vec) => Self::new_vec(vec),
242        }
243    }
244}
245
246impl From<&'static [u8]> for Error {
247    fn from(value: &'static [u8]) -> Self {
248        Self::new_bytes(value)
249    }
250}
251
252impl<const N: usize> From<&'static [u8; N]> for Error {
253    fn from(value: &'static [u8; N]) -> Self {
254        Self::new_bytes(value)
255    }
256}
257
258impl From<Vec<u8>> for Error {
259    fn from(value: Vec<u8>) -> Self {
260        Self::new_vec(value)
261    }
262}
263
264impl From<Bytes> for Error {
265    #[inline]
266    fn from(value: Bytes) -> Self {
267        Self::new_vec(value.into())
268    }
269}
270
271// So we can use `?` on `Result<_, Error>`.
272macro_rules! impl_from {
273    ($($t:ty),* $(,)?) => {$(
274        impl From<$t> for Error {
275            fn from(value: $t) -> Self {
276                Self::display(value)
277            }
278        }
279    )*};
280}
281
282impl_from!(
283    alloy_sol_types::Error,
284    alloy_dyn_abi::Error,
285    alloy_primitives::SignatureError,
286    eyre::Report,
287    FsPathError,
288    hex::FromHexError,
289    BackendError,
290    DatabaseError,
291    jsonpath_lib::JsonPathError,
292    serde_json::Error,
293    SignatureError,
294    std::io::Error,
295    std::num::TryFromIntError,
296    std::str::Utf8Error,
297    std::string::FromUtf8Error,
298    UnresolvedEnvVarError,
299    LocalSignerError,
300    SignerError,
301    WalletSignerError,
302);
303
304impl<T: Into<BackendError>> From<EVMError<T>> for Error {
305    fn from(err: EVMError<T>) -> Self {
306        Self::display(BackendError::from(err))
307    }
308}
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313
314    #[test]
315    fn encode() {
316        let error = Vm::CheatcodeError { message: "hello".into() }.abi_encode();
317        assert_eq!(Error::from("hello").abi_encode(), error);
318        assert_eq!(Error::encode("hello"), error);
319
320        assert_eq!(Error::from(b"hello").abi_encode(), b"hello");
321        assert_eq!(Error::encode(b"hello"), b"hello"[..]);
322    }
323}