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