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