Skip to main content

alloy_sol_types/
errors.rs

1// Copyright 2015-2020 Parity Technologies
2// Copyright 2023-2023 Alloy Contributors
3
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10use alloc::{borrow::Cow, boxed::Box, collections::TryReserveError, string::String};
11use alloy_primitives::{B256, LogData, hex};
12use core::{cmp, fmt};
13
14const MAX_TYPE_CHECK_FAIL_DATA_LEN: usize = 128;
15
16/// ABI result type.
17pub type Result<T, E = Error> = core::result::Result<T, E>;
18
19/// ABI Encoding and Decoding errors.
20#[derive(Clone, Debug, PartialEq)]
21pub enum Error {
22    /// A typecheck detected a word that does not match the data type.
23    TypeCheckFail {
24        /// The Solidity type we failed to produce.
25        expected_type: Cow<'static, str>,
26        /// Hex-encoded data, when available.
27        data: String,
28    },
29
30    /// Overran deserialization buffer.
31    Overrun,
32
33    /// Allocation failed.
34    Reserve(TryReserveError),
35
36    /// Trailing bytes in deserialization buffer.
37    BufferNotEmpty,
38
39    /// Validation reserialization did not match input.
40    ReserMismatch,
41
42    /// ABI Decoding recursion limit exceeded.
43    RecursionLimitExceeded(usize),
44
45    /// ABI decoding exceeded its configured memory limit.
46    MemoryLimitExceeded(usize),
47
48    /// Invalid enum value.
49    InvalidEnumValue {
50        /// The name of the enum.
51        name: &'static str,
52        /// The invalid value.
53        value: u8,
54        /// The maximum valid value.
55        max: u8,
56    },
57
58    /// Invalid event signature hash.
59    InvalidEventSignatureHash {
60        /// The event signature.
61        name: &'static str,
62        /// The received signature hash.
63        got: B256,
64        /// The expected signature hash.
65        expected: B256,
66    },
67
68    /// Could not decode an event from log topics.
69    InvalidLog {
70        /// The name of the enum or event.
71        name: &'static str,
72        /// The invalid log.
73        log: Box<LogData>,
74    },
75
76    /// Unknown selector.
77    UnknownSelector {
78        /// The type name.
79        name: &'static str,
80        /// The unknown selector.
81        selector: alloy_primitives::FixedBytes<4>,
82    },
83
84    /// Hex error.
85    FromHexError(hex::FromHexError),
86
87    /// Other errors.
88    Other(Cow<'static, str>),
89}
90
91impl core::error::Error for Error {
92    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
93        match self {
94            Self::Reserve(e) => Some(e),
95            Self::FromHexError(e) => Some(e),
96            _ => None,
97        }
98    }
99}
100
101impl fmt::Display for Error {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        match self {
104            Self::TypeCheckFail { expected_type, data } => {
105                write!(f, "type check failed for {expected_type:?}")?;
106                if !data.is_empty() {
107                    write!(f, " with data: {data}")?;
108                }
109                Ok(())
110            }
111            Self::Overrun
112            | Self::BufferNotEmpty
113            | Self::ReserMismatch
114            | Self::RecursionLimitExceeded(_)
115            | Self::MemoryLimitExceeded(_) => {
116                f.write_str("ABI decoding failed: ")?;
117                match *self {
118                    Self::Overrun => f.write_str("buffer overrun while deserializing"),
119                    Self::BufferNotEmpty => f.write_str("buffer not empty after deserialization"),
120                    Self::ReserMismatch => f.write_str("reserialization did not match original"),
121                    Self::RecursionLimitExceeded(limit) => {
122                        write!(f, "recursion limit of {limit} exceeded during decoding")
123                    }
124                    Self::MemoryLimitExceeded(limit) => {
125                        write!(f, "memory limit of {limit} bytes exceeded during decoding")
126                    }
127                    _ => unreachable!(),
128                }
129            }
130            Self::Reserve(e) => e.fmt(f),
131            Self::InvalidEnumValue { name, value, max } => {
132                write!(f, "`{value}` is not a valid {name} enum value (max: `{max}`)")
133            }
134            Self::InvalidEventSignatureHash { name, got, expected } => {
135                write!(
136                    f,
137                    "invalid signature hash for event {name:?}: got {got}, expected {expected}"
138                )
139            }
140            Self::InvalidLog { name, log } => {
141                write!(f, "could not decode {name} from log: {log:?}")
142            }
143            Self::UnknownSelector { name, selector } => {
144                write!(f, "unknown selector `{selector}` for {name}")
145            }
146            Self::FromHexError(e) => e.fmt(f),
147            Self::Other(e) => f.write_str(e),
148        }
149    }
150}
151
152impl Error {
153    /// Instantiates a new error with a static str.
154    #[cold]
155    pub fn custom(s: impl Into<Cow<'static, str>>) -> Self {
156        Self::Other(s.into())
157    }
158
159    /// Instantiates a new [`Error::TypeCheckFail`] with the provided data.
160    #[cold]
161    pub fn type_check_fail_sig(mut data: &[u8], signature: &'static str) -> Self {
162        if data.len() > 4 {
163            data = &data[..4];
164        }
165        let expected_type = signature.split('(').next().unwrap();
166        Self::type_check_fail(data, expected_type)
167    }
168
169    /// Instantiates a new [`Error::TypeCheckFail`] with the provided token.
170    #[cold]
171    pub fn type_check_fail_token<T: crate::SolType>(_token: &T::Token<'_>) -> Self {
172        Self::type_check_fail(&[], T::SOL_NAME)
173    }
174
175    /// Instantiates a new [`Error::TypeCheckFail`] with the provided data.
176    #[cold]
177    pub fn type_check_fail(data: &[u8], expected_type: impl Into<Cow<'static, str>>) -> Self {
178        let data = &data[..cmp::min(data.len(), MAX_TYPE_CHECK_FAIL_DATA_LEN)];
179        Self::TypeCheckFail { expected_type: expected_type.into(), data: hex::encode(data) }
180    }
181
182    /// Instantiates a new [`Error::UnknownSelector`] with the provided data.
183    #[cold]
184    pub fn unknown_selector(name: &'static str, selector: [u8; 4]) -> Self {
185        Self::UnknownSelector { name, selector: selector.into() }
186    }
187
188    #[doc(hidden)] // Not public API.
189    #[cold]
190    pub const fn invalid_event_signature_hash(
191        name: &'static str,
192        got: B256,
193        expected: B256,
194    ) -> Self {
195        Self::InvalidEventSignatureHash { name, got, expected }
196    }
197}
198
199impl From<hex::FromHexError> for Error {
200    #[inline]
201    fn from(value: hex::FromHexError) -> Self {
202        Self::FromHexError(value)
203    }
204}
205
206impl From<TryReserveError> for Error {
207    #[inline]
208    fn from(value: TryReserveError) -> Self {
209        Self::Reserve(value)
210    }
211}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216    use alloc::string::ToString;
217
218    #[test]
219    fn type_check_fail_data_is_bounded() {
220        let error = Error::type_check_fail(&[0; MAX_TYPE_CHECK_FAIL_DATA_LEN + 1], "test");
221        let Error::TypeCheckFail { data, .. } = error else { unreachable!() };
222        assert_eq!(data.len(), MAX_TYPE_CHECK_FAIL_DATA_LEN * 2);
223    }
224
225    #[test]
226    fn type_check_fail_token_omits_token_data() {
227        let token = crate::abi::token::WordToken(crate::Word::ZERO);
228        let error = Error::type_check_fail_token::<crate::sol_data::Bool>(&token);
229        let Error::TypeCheckFail { data, .. } = error else { unreachable!() };
230        assert!(data.is_empty());
231    }
232
233    #[test]
234    fn empty_type_check_fail_data_is_not_displayed() {
235        assert_eq!(
236            Error::type_check_fail(&[], "test").to_string(),
237            "type check failed for \"test\""
238        );
239    }
240}