alloy_consensus/receipt/
status.rs

1use alloy_primitives::B256;
2use alloy_rlp::{Buf, BufMut, Decodable, Encodable, Error, Header};
3
4/// Captures the result of a transaction execution.
5#[derive(Copy, Clone, Debug, PartialEq, Eq)]
6#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
7pub enum Eip658Value {
8    /// A boolean `statusCode` introduced by [EIP-658].
9    ///
10    /// [EIP-658]: https://eips.ethereum.org/EIPS/eip-658
11    Eip658(bool),
12    /// A pre-[EIP-658] hash value.
13    ///
14    /// [EIP-658]: https://eips.ethereum.org/EIPS/eip-658
15    PostState(B256),
16}
17
18impl Eip658Value {
19    /// Returns a successful transaction status.
20    pub const fn success() -> Self {
21        Self::Eip658(true)
22    }
23
24    /// Returns true if the transaction was successful OR if the transaction
25    /// is pre-[EIP-658].
26    ///
27    /// [EIP-658]: https://eips.ethereum.org/EIPS/eip-658
28    pub const fn coerce_status(&self) -> bool {
29        matches!(self, Self::Eip658(true) | Self::PostState(_))
30    }
31
32    /// Coerce this variant into a [`Eip658Value::Eip658`] with [`Self::coerce_status`].
33    pub fn coerced_eip658(&mut self) {
34        *self = Self::Eip658(self.coerce_status())
35    }
36
37    /// Returns true if the transaction was a pre-[EIP-658] transaction.
38    ///
39    /// [EIP-658]: https://eips.ethereum.org/EIPS/eip-658
40    pub const fn is_post_state(&self) -> bool {
41        matches!(self, Self::PostState(_))
42    }
43
44    /// Returns true if the transaction was a post-[EIP-658] transaction.
45    pub const fn is_eip658(&self) -> bool {
46        !matches!(self, Self::PostState(_))
47    }
48
49    /// Fallibly convert to the post state.
50    pub const fn as_post_state(&self) -> Option<B256> {
51        match self {
52            Self::PostState(state) => Some(*state),
53            _ => None,
54        }
55    }
56
57    /// Fallibly convert to the [EIP-658] status code.
58    ///
59    /// [EIP-658]: https://eips.ethereum.org/EIPS/eip-658
60    pub const fn as_eip658(&self) -> Option<bool> {
61        match self {
62            Self::Eip658(status) => Some(*status),
63            _ => None,
64        }
65    }
66}
67
68impl From<bool> for Eip658Value {
69    fn from(status: bool) -> Self {
70        Self::Eip658(status)
71    }
72}
73
74impl From<B256> for Eip658Value {
75    fn from(state: B256) -> Self {
76        Self::PostState(state)
77    }
78}
79
80// NB: default to success
81impl Default for Eip658Value {
82    fn default() -> Self {
83        Self::Eip658(true)
84    }
85}
86
87#[cfg(feature = "serde")]
88mod serde_eip658 {
89    //! Serde implementation for [`Eip658Value`]. Serializes [`Eip658Value::Eip658`] as `status`
90    //! key, and [`Eip658Value::PostState`] as `root` key.
91    //!
92    //! If both are present, prefers `status` key.
93    //!
94    //! Should be used with `#[serde(flatten)]`.
95    use super::*;
96    use serde::{Deserialize, Serialize};
97
98    #[derive(serde::Serialize, serde::Deserialize)]
99    #[serde(untagged)]
100    enum SerdeHelper {
101        Eip658 {
102            #[serde(with = "alloy_serde::quantity")]
103            status: bool,
104        },
105        PostState {
106            root: B256,
107        },
108    }
109
110    impl Serialize for Eip658Value {
111        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
112        where
113            S: serde::Serializer,
114        {
115            match self {
116                Self::Eip658(status) => {
117                    SerdeHelper::Eip658 { status: *status }.serialize(serializer)
118                }
119                Self::PostState(state) => {
120                    SerdeHelper::PostState { root: *state }.serialize(serializer)
121                }
122            }
123        }
124    }
125
126    impl<'de> Deserialize<'de> for Eip658Value {
127        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
128        where
129            D: serde::Deserializer<'de>,
130        {
131            let helper = SerdeHelper::deserialize(deserializer)?;
132            match helper {
133                SerdeHelper::Eip658 { status } => Ok(Self::Eip658(status)),
134                SerdeHelper::PostState { root } => Ok(Self::PostState(root)),
135            }
136        }
137    }
138}
139
140impl Encodable for Eip658Value {
141    fn encode(&self, buf: &mut dyn BufMut) {
142        match self {
143            Self::Eip658(status) => {
144                status.encode(buf);
145            }
146            Self::PostState(state) => {
147                state.encode(buf);
148            }
149        }
150    }
151
152    fn length(&self) -> usize {
153        match self {
154            Self::Eip658(inner) => inner.length(),
155            Self::PostState(inner) => inner.length(),
156        }
157    }
158}
159
160impl Decodable for Eip658Value {
161    fn decode(buf: &mut &[u8]) -> Result<Self, Error> {
162        let h = Header::decode(buf)?;
163
164        match h.payload_length {
165            0 => Ok(Self::Eip658(false)),
166            1 => {
167                let status = buf.get_u8() != 0;
168                Ok(status.into())
169            }
170            32 => {
171                if buf.remaining() < 32 {
172                    return Err(Error::InputTooShort);
173                }
174                let mut state = B256::default();
175                buf.copy_to_slice(state.as_mut_slice());
176                Ok(state.into())
177            }
178            _ => Err(Error::UnexpectedLength),
179        }
180    }
181}
182
183#[cfg(test)]
184mod test {
185    use super::*;
186
187    #[test]
188    fn rlp_sanity() {
189        let mut buf = Vec::new();
190        let status = Eip658Value::Eip658(true);
191        status.encode(&mut buf);
192        assert_eq!(Eip658Value::decode(&mut buf.as_slice()), Ok(status));
193
194        let mut buf = Vec::new();
195        let state = Eip658Value::PostState(B256::default());
196        state.encode(&mut buf);
197        assert_eq!(Eip658Value::decode(&mut buf.as_slice()), Ok(state));
198    }
199
200    #[cfg(feature = "serde")]
201    #[test]
202    fn serde_sanity() {
203        let status: Eip658Value = true.into();
204        let json = serde_json::to_string(&status).unwrap();
205        assert_eq!(json, r#"{"status":"0x1"}"#);
206        assert_eq!(serde_json::from_str::<Eip658Value>(&json).unwrap(), status);
207
208        let state: Eip658Value = false.into();
209        let json = serde_json::to_string(&state).unwrap();
210        assert_eq!(json, r#"{"status":"0x0"}"#);
211
212        let state: Eip658Value = B256::repeat_byte(1).into();
213        let json = serde_json::to_string(&state).unwrap();
214        assert_eq!(
215            json,
216            r#"{"root":"0x0101010101010101010101010101010101010101010101010101010101010101"}"#
217        );
218    }
219}