Skip to main content

eth_valkyoth_protocol/withdrawal/
error.rs

1use core::fmt;
2
3use eth_valkyoth_codec::{DecodeError, DecodeErrorCategory};
4
5/// Withdrawal field identifier.
6#[non_exhaustive]
7#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8pub enum WithdrawalField {
9    /// Whole withdrawals list.
10    List,
11    /// One withdrawal entry.
12    Withdrawal,
13    /// Withdrawal global index.
14    Index,
15    /// Consensus-layer validator index.
16    ValidatorIndex,
17    /// Withdrawal recipient address.
18    Address,
19    /// Withdrawal amount in Gwei.
20    Amount,
21}
22
23/// Withdrawal decode failure.
24#[non_exhaustive]
25#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26pub enum WithdrawalDecodeError {
27    /// A withdrawal entry did not contain exactly four fields.
28    WrongFieldCount {
29        /// Expected field count.
30        expected: usize,
31        /// Actual field count.
32        found: usize,
33    },
34    /// A field failed bounded RLP or primitive-domain decoding.
35    FieldDecode {
36        /// Field being decoded.
37        field: WithdrawalField,
38        /// Underlying decode error.
39        source: DecodeError,
40    },
41    /// Withdrawal address was not exactly 20 bytes.
42    InvalidAddressLength {
43        /// Actual decoded scalar byte length.
44        found: usize,
45    },
46    /// EIP-4895 withdrawal amounts must be nonzero.
47    ZeroAmount,
48}
49
50impl WithdrawalDecodeError {
51    /// Stable machine-readable error code.
52    #[must_use]
53    pub const fn code(self) -> &'static str {
54        match self {
55            Self::WrongFieldCount { .. } => "ETH_WITHDRAWAL_WRONG_FIELD_COUNT",
56            Self::FieldDecode { source, .. } => source.code(),
57            Self::InvalidAddressLength { .. } => "ETH_WITHDRAWAL_INVALID_ADDRESS_LENGTH",
58            Self::ZeroAmount => "ETH_WITHDRAWAL_ZERO_AMOUNT",
59        }
60    }
61
62    /// Stable human-readable error message.
63    #[must_use]
64    pub const fn message(self) -> &'static str {
65        match self {
66            Self::WrongFieldCount { .. } => "withdrawal must contain exactly four RLP fields",
67            Self::FieldDecode { .. } => "withdrawal field failed bounded decoding",
68            Self::InvalidAddressLength { .. } => "withdrawal address must be exactly 20 bytes",
69            Self::ZeroAmount => "withdrawal amount must be nonzero",
70        }
71    }
72
73    /// Stable high-level category for policy decisions.
74    #[must_use]
75    pub const fn category(self) -> WithdrawalDecodeErrorCategory {
76        match self {
77            Self::WrongFieldCount { .. } | Self::InvalidAddressLength { .. } | Self::ZeroAmount => {
78                WithdrawalDecodeErrorCategory::MalformedInput
79            }
80            Self::FieldDecode { source, .. } => match source.category() {
81                DecodeErrorCategory::ResourceExhaustion => {
82                    WithdrawalDecodeErrorCategory::ResourceExhaustion
83                }
84                _ => WithdrawalDecodeErrorCategory::MalformedInput,
85            },
86        }
87    }
88}
89
90impl fmt::Display for WithdrawalDecodeError {
91    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
92        formatter.write_str(self.message())
93    }
94}
95
96#[cfg(feature = "std")]
97impl std::error::Error for WithdrawalDecodeError {}
98
99/// Stable high-level withdrawal decode error categories.
100#[non_exhaustive]
101#[derive(Clone, Copy, Debug, Eq, PartialEq)]
102pub enum WithdrawalDecodeErrorCategory {
103    /// Input is malformed for a withdrawals list or withdrawal entry.
104    MalformedInput,
105    /// The active decode policy rejected the input as too large or too deep.
106    ResourceExhaustion,
107}