Skip to main content

eth_valkyoth_codec/
error.rs

1use core::fmt;
2
3/// Shared decode failure categories.
4#[non_exhaustive]
5#[derive(Clone, Copy, Debug, Eq, PartialEq)]
6pub enum DecodeError {
7    /// The byte input is larger than the active decode budget.
8    InputTooLarge,
9    /// The input contains trailing bytes after a decoded value.
10    TrailingBytes,
11    /// A decoder reported consuming more bytes than the input contains.
12    DecoderOverread,
13    /// The input is malformed for the selected wire format.
14    Malformed,
15    /// An RLP list was encountered where a scalar byte string was required.
16    UnexpectedList,
17    /// An RLP scalar was encountered where a list was required.
18    UnexpectedScalar,
19    /// A decoded list contains more items than the active list budget.
20    ListTooLong,
21    /// Decoding exceeded the active nesting-depth budget.
22    NestingTooDeep,
23    /// A decoder requested allocation beyond the active allocation budget.
24    AllocationExceeded,
25    /// A proof contains more nodes than the active proof-node budget.
26    ProofTooLarge,
27    /// A decoder visited more items than the active cumulative item budget.
28    ItemCountExceeded,
29    /// A deployment used an unchanged starter budget policy.
30    UnreviewedDeploymentPolicy,
31    /// Length or offset arithmetic overflowed.
32    LengthOverflow,
33    /// An offset or range points outside the input.
34    OffsetOutOfBounds,
35}
36
37impl DecodeError {
38    /// Stable machine-readable error code.
39    #[must_use]
40    pub const fn code(self) -> &'static str {
41        match self {
42            Self::InputTooLarge => "ETH_CODEC_INPUT_TOO_LARGE",
43            Self::TrailingBytes => "ETH_CODEC_TRAILING_BYTES",
44            Self::DecoderOverread => "ETH_CODEC_DECODER_OVERREAD",
45            Self::Malformed => "ETH_CODEC_MALFORMED",
46            Self::UnexpectedList => "ETH_CODEC_UNEXPECTED_LIST",
47            Self::UnexpectedScalar => "ETH_CODEC_UNEXPECTED_SCALAR",
48            Self::ListTooLong => "ETH_CODEC_LIST_TOO_LONG",
49            Self::NestingTooDeep => "ETH_CODEC_NESTING_TOO_DEEP",
50            Self::AllocationExceeded => "ETH_CODEC_ALLOCATION_EXCEEDED",
51            Self::ProofTooLarge => "ETH_CODEC_PROOF_TOO_LARGE",
52            Self::ItemCountExceeded => "ETH_CODEC_ITEM_COUNT_EXCEEDED",
53            Self::UnreviewedDeploymentPolicy => "ETH_CODEC_UNREVIEWED_DEPLOYMENT_POLICY",
54            Self::LengthOverflow => "ETH_CODEC_LENGTH_OVERFLOW",
55            Self::OffsetOutOfBounds => "ETH_CODEC_OFFSET_OUT_OF_BOUNDS",
56        }
57    }
58
59    /// Stable human-readable error message.
60    #[must_use]
61    pub const fn message(self) -> &'static str {
62        match self {
63            Self::InputTooLarge => "input exceeds the active decode byte limit",
64            Self::TrailingBytes => "decoded value did not consume the full input",
65            Self::DecoderOverread => "decoder consumed more bytes than were available",
66            Self::Malformed => "input is malformed for the selected codec",
67            Self::UnexpectedList => "RLP list encountered where scalar was required",
68            Self::UnexpectedScalar => "RLP scalar encountered where list was required",
69            Self::ListTooLong => "decoded list exceeds the active item limit",
70            Self::NestingTooDeep => "decoded structure exceeds the active nesting limit",
71            Self::AllocationExceeded => "decoder exceeded the active allocation limit",
72            Self::ProofTooLarge => "proof exceeds the active proof-node limit",
73            Self::ItemCountExceeded => "decoder exceeded the active cumulative item limit",
74            Self::UnreviewedDeploymentPolicy => {
75                "deployment decode policy must be reviewed and tightened"
76            }
77            Self::LengthOverflow => "length or offset arithmetic overflowed",
78            Self::OffsetOutOfBounds => "offset or range is outside the input",
79        }
80    }
81
82    /// Stable high-level category for policy decisions.
83    #[must_use]
84    pub const fn category(self) -> DecodeErrorCategory {
85        match self {
86            Self::InputTooLarge
87            | Self::ListTooLong
88            | Self::NestingTooDeep
89            | Self::AllocationExceeded
90            | Self::ProofTooLarge
91            | Self::ItemCountExceeded
92            | Self::UnreviewedDeploymentPolicy => DecodeErrorCategory::ResourceExhaustion,
93            Self::TrailingBytes
94            | Self::DecoderOverread
95            | Self::Malformed
96            | Self::UnexpectedList
97            | Self::UnexpectedScalar
98            | Self::LengthOverflow
99            | Self::OffsetOutOfBounds => DecodeErrorCategory::MalformedInput,
100        }
101    }
102
103    /// Returns the resource budget that was exceeded, if this is a resource
104    /// exhaustion error.
105    #[must_use]
106    pub const fn resource(self) -> Option<ResourceError> {
107        match self {
108            Self::InputTooLarge => Some(ResourceError::InputBytes),
109            Self::ListTooLong => Some(ResourceError::ListItems),
110            Self::NestingTooDeep => Some(ResourceError::NestingDepth),
111            Self::AllocationExceeded => Some(ResourceError::AllocationBytes),
112            Self::ProofTooLarge => Some(ResourceError::ProofNodes),
113            Self::ItemCountExceeded => Some(ResourceError::TotalItems),
114            Self::UnreviewedDeploymentPolicy => Some(ResourceError::DeploymentPolicy),
115            Self::TrailingBytes
116            | Self::DecoderOverread
117            | Self::Malformed
118            | Self::UnexpectedList
119            | Self::UnexpectedScalar
120            | Self::LengthOverflow
121            | Self::OffsetOutOfBounds => None,
122        }
123    }
124}
125
126impl fmt::Display for DecodeError {
127    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
128        formatter.write_str(self.message())
129    }
130}
131
132#[cfg(feature = "std")]
133impl std::error::Error for DecodeError {}
134
135/// Stable high-level decode error categories.
136#[non_exhaustive]
137#[derive(Clone, Copy, Debug, Eq, PartialEq)]
138pub enum DecodeErrorCategory {
139    /// The input is malformed or internally inconsistent.
140    MalformedInput,
141    /// The input exceeded an explicit resource budget.
142    ResourceExhaustion,
143}
144
145/// Stable resource budget categories.
146#[non_exhaustive]
147#[derive(Clone, Copy, Debug, Eq, PartialEq)]
148pub enum ResourceError {
149    /// Input byte limit was exceeded.
150    InputBytes,
151    /// Per-list item limit was exceeded.
152    ListItems,
153    /// Nesting depth limit was exceeded.
154    NestingDepth,
155    /// Cumulative allocation limit was exceeded.
156    AllocationBytes,
157    /// Proof-node limit was exceeded.
158    ProofNodes,
159    /// Cumulative decoded item limit was exceeded.
160    TotalItems,
161    /// Deployment policy was not reviewed.
162    DeploymentPolicy,
163}
164
165impl ResourceError {
166    /// Stable machine-readable error code.
167    #[must_use]
168    pub const fn code(self) -> &'static str {
169        match self {
170            Self::InputBytes => "ETH_RESOURCE_INPUT_BYTES",
171            Self::ListItems => "ETH_RESOURCE_LIST_ITEMS",
172            Self::NestingDepth => "ETH_RESOURCE_NESTING_DEPTH",
173            Self::AllocationBytes => "ETH_RESOURCE_ALLOCATION_BYTES",
174            Self::ProofNodes => "ETH_RESOURCE_PROOF_NODES",
175            Self::TotalItems => "ETH_RESOURCE_TOTAL_ITEMS",
176            Self::DeploymentPolicy => "ETH_RESOURCE_DEPLOYMENT_POLICY",
177        }
178    }
179
180    /// Stable human-readable error message.
181    #[must_use]
182    pub const fn message(self) -> &'static str {
183        match self {
184            Self::InputBytes => "input byte budget exceeded",
185            Self::ListItems => "list item budget exceeded",
186            Self::NestingDepth => "nesting depth budget exceeded",
187            Self::AllocationBytes => "allocation byte budget exceeded",
188            Self::ProofNodes => "proof-node budget exceeded",
189            Self::TotalItems => "total item budget exceeded",
190            Self::DeploymentPolicy => "deployment policy review required",
191        }
192    }
193}
194
195impl fmt::Display for ResourceError {
196    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
197        formatter.write_str(self.message())
198    }
199}
200
201#[cfg(feature = "std")]
202impl std::error::Error for ResourceError {}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207    extern crate std;
208    use std::string::ToString;
209
210    #[test]
211    fn decode_errors_have_stable_codes_and_messages() {
212        assert_eq!(DecodeError::Malformed.code(), "ETH_CODEC_MALFORMED");
213        assert_eq!(
214            DecodeError::Malformed.message(),
215            "input is malformed for the selected codec"
216        );
217        assert_eq!(
218            DecodeError::Malformed.category(),
219            DecodeErrorCategory::MalformedInput
220        );
221        assert_eq!(
222            DecodeError::Malformed.to_string(),
223            "input is malformed for the selected codec"
224        );
225    }
226
227    #[test]
228    fn resource_errors_are_classified_without_payloads() {
229        let error = DecodeError::AllocationExceeded;
230
231        assert_eq!(error.category(), DecodeErrorCategory::ResourceExhaustion);
232        assert_eq!(error.resource(), Some(ResourceError::AllocationBytes));
233        assert_eq!(
234            ResourceError::AllocationBytes.code(),
235            "ETH_RESOURCE_ALLOCATION_BYTES"
236        );
237        assert_eq!(
238            ResourceError::AllocationBytes.to_string(),
239            "allocation byte budget exceeded"
240        );
241    }
242
243    #[test]
244    fn new_resource_errors_are_classified() {
245        assert_eq!(
246            DecodeError::ProofTooLarge.resource(),
247            Some(ResourceError::ProofNodes)
248        );
249        assert_eq!(
250            DecodeError::ItemCountExceeded.resource(),
251            Some(ResourceError::TotalItems)
252        );
253        assert_eq!(
254            DecodeError::UnreviewedDeploymentPolicy.resource(),
255            Some(ResourceError::DeploymentPolicy)
256        );
257    }
258
259    #[test]
260    fn arithmetic_errors_are_malformed_input() {
261        assert_eq!(
262            DecodeError::LengthOverflow.category(),
263            DecodeErrorCategory::MalformedInput
264        );
265        assert_eq!(DecodeError::OffsetOutOfBounds.resource(), None);
266    }
267
268    #[test]
269    fn unexpected_list_is_malformed_input() {
270        assert_eq!(
271            DecodeError::UnexpectedList.code(),
272            "ETH_CODEC_UNEXPECTED_LIST"
273        );
274        assert_eq!(
275            DecodeError::UnexpectedList.category(),
276            DecodeErrorCategory::MalformedInput
277        );
278        assert_eq!(DecodeError::UnexpectedList.resource(), None);
279    }
280
281    #[test]
282    fn unexpected_scalar_is_malformed_input() {
283        assert_eq!(
284            DecodeError::UnexpectedScalar.code(),
285            "ETH_CODEC_UNEXPECTED_SCALAR"
286        );
287        assert_eq!(
288            DecodeError::UnexpectedScalar.category(),
289            DecodeErrorCategory::MalformedInput
290        );
291        assert_eq!(DecodeError::UnexpectedScalar.resource(), None);
292    }
293}