Skip to main content

alloy_consensus/receipt/
status.rs

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