Skip to main content

eth_valkyoth_protocol/receipt/
error.rs

1use core::fmt;
2
3use eth_valkyoth_codec::{DecodeError, DecodeErrorCategory};
4
5/// Receipt field identifier.
6#[non_exhaustive]
7#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8pub enum ReceiptField {
9    /// Whole typed-receipt payload.
10    Payload,
11    /// Status code or pre-Byzantium state root.
12    StatusOrStateRoot,
13    /// Cumulative gas used.
14    CumulativeGasUsed,
15    /// Logs bloom.
16    LogsBloom,
17    /// Logs list.
18    Logs,
19    /// Log address.
20    LogAddress,
21    /// Log topics.
22    LogTopics,
23    /// Log data.
24    LogData,
25}
26
27/// Receipt decode failure.
28#[non_exhaustive]
29#[derive(Clone, Copy, Debug, Eq, PartialEq)]
30pub enum ReceiptDecodeError {
31    /// No bytes were supplied.
32    EmptyInput,
33    /// The first byte is the legacy-zero typed prefix.
34    UnsupportedReceiptType {
35        /// Unsupported typed receipt prefix byte.
36        type_byte: u8,
37    },
38    /// The first byte is an RLP scalar prefix, not a receipt envelope.
39    ScalarPrefix {
40        /// Observed scalar prefix byte.
41        prefix: u8,
42    },
43    /// The first byte is the EIP-2718 reserved extension sentinel.
44    ReservedPrefix,
45    /// Receipt list did not contain exactly four fields.
46    WrongFieldCount {
47        /// Expected field count.
48        expected: usize,
49        /// Actual field count.
50        found: usize,
51    },
52    /// A log entry did not contain exactly three fields.
53    InvalidLogFieldCount {
54        /// Actual field count.
55        found: usize,
56    },
57    /// A field failed bounded RLP or primitive-domain decoding.
58    FieldDecode {
59        /// Field being decoded.
60        field: ReceiptField,
61        /// Underlying decode error.
62        source: DecodeError,
63    },
64    /// Status/root field was neither status `0`/`1` nor a 32-byte root.
65    InvalidStatusOrStateRoot {
66        /// Actual decoded scalar byte length.
67        found: usize,
68    },
69    /// Logs bloom was not exactly 256 bytes.
70    InvalidLogsBloomLength {
71        /// Actual decoded scalar byte length.
72        found: usize,
73    },
74    /// Log address was not exactly 20 bytes.
75    InvalidLogAddressLength {
76        /// Actual decoded scalar byte length.
77        found: usize,
78    },
79    /// Log topic was not exactly 32 bytes.
80    InvalidLogTopicLength {
81        /// Actual decoded scalar byte length.
82        found: usize,
83    },
84}
85
86impl ReceiptDecodeError {
87    /// Stable machine-readable error code.
88    #[must_use]
89    pub const fn code(self) -> &'static str {
90        match self {
91            Self::EmptyInput => "ETH_RECEIPT_EMPTY_INPUT",
92            Self::UnsupportedReceiptType { .. } => "ETH_RECEIPT_UNSUPPORTED_TYPE",
93            Self::ScalarPrefix { .. } => "ETH_RECEIPT_SCALAR_PREFIX",
94            Self::ReservedPrefix => "ETH_RECEIPT_RESERVED_PREFIX",
95            Self::WrongFieldCount { .. } => "ETH_RECEIPT_WRONG_FIELD_COUNT",
96            Self::InvalidLogFieldCount { .. } => "ETH_RECEIPT_INVALID_LOG_FIELD_COUNT",
97            Self::FieldDecode { source, .. } => source.code(),
98            Self::InvalidStatusOrStateRoot { .. } => "ETH_RECEIPT_INVALID_STATUS_OR_ROOT",
99            Self::InvalidLogsBloomLength { .. } => "ETH_RECEIPT_INVALID_BLOOM_LENGTH",
100            Self::InvalidLogAddressLength { .. } => "ETH_RECEIPT_INVALID_LOG_ADDRESS_LENGTH",
101            Self::InvalidLogTopicLength { .. } => "ETH_RECEIPT_INVALID_LOG_TOPIC_LENGTH",
102        }
103    }
104
105    /// Stable human-readable error message.
106    #[must_use]
107    pub const fn message(self) -> &'static str {
108        match self {
109            Self::EmptyInput => "receipt input is empty",
110            Self::UnsupportedReceiptType { .. } => {
111                "receipt type is not supported by this receipt decoder"
112            }
113            Self::ScalarPrefix { .. } => "receipt envelope starts with an RLP scalar prefix",
114            Self::ReservedPrefix => "receipt envelope starts with the reserved 0xff prefix",
115            Self::WrongFieldCount { .. } => "receipt must contain exactly four RLP fields",
116            Self::InvalidLogFieldCount { .. } => "receipt log must contain exactly three fields",
117            Self::FieldDecode { .. } => "receipt field failed bounded decoding",
118            Self::InvalidStatusOrStateRoot { .. } => {
119                "receipt status/root field must be status 0 or 1, or a 32-byte root"
120            }
121            Self::InvalidLogsBloomLength { .. } => "receipt logs bloom must be exactly 256 bytes",
122            Self::InvalidLogAddressLength { .. } => "receipt log address must be exactly 20 bytes",
123            Self::InvalidLogTopicLength { .. } => "receipt log topic must be exactly 32 bytes",
124        }
125    }
126
127    /// Stable high-level category for policy decisions.
128    #[must_use]
129    pub const fn category(self) -> ReceiptDecodeErrorCategory {
130        match self {
131            Self::UnsupportedReceiptType { .. } => ReceiptDecodeErrorCategory::Unsupported,
132            Self::EmptyInput
133            | Self::ScalarPrefix { .. }
134            | Self::ReservedPrefix
135            | Self::WrongFieldCount { .. }
136            | Self::InvalidLogFieldCount { .. }
137            | Self::InvalidStatusOrStateRoot { .. }
138            | Self::InvalidLogsBloomLength { .. }
139            | Self::InvalidLogAddressLength { .. }
140            | Self::InvalidLogTopicLength { .. } => ReceiptDecodeErrorCategory::MalformedInput,
141            Self::FieldDecode { source, .. } => match source.category() {
142                DecodeErrorCategory::MalformedInput => ReceiptDecodeErrorCategory::MalformedInput,
143                DecodeErrorCategory::ResourceExhaustion => {
144                    ReceiptDecodeErrorCategory::ResourceExhaustion
145                }
146                _ => ReceiptDecodeErrorCategory::MalformedInput,
147            },
148        }
149    }
150}
151
152impl fmt::Display for ReceiptDecodeError {
153    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
154        formatter.write_str(self.message())
155    }
156}
157
158#[cfg(feature = "std")]
159impl std::error::Error for ReceiptDecodeError {}
160
161/// Stable high-level receipt decode error categories.
162#[non_exhaustive]
163#[derive(Clone, Copy, Debug, Eq, PartialEq)]
164pub enum ReceiptDecodeErrorCategory {
165    /// Input is malformed for a receipt envelope or payload.
166    MalformedInput,
167    /// A future or unsupported receipt domain was encountered.
168    Unsupported,
169    /// The active decode policy rejected the input as too large or too deep.
170    ResourceExhaustion,
171}