diff --git a/crates/consensus/src/receipt/receipts.rs b/crates/consensus/src/receipt/receipts.rs index 4a3b83fdc9b..b8250856f86 100644 --- a/crates/consensus/src/receipt/receipts.rs +++ b/crates/consensus/src/receipt/receipts.rs @@ -7,7 +7,7 @@ use derive_more::{DerefMut, From, IntoIterator}; /// Receipt containing result of transaction execution. #[derive(Clone, Debug, Default, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[doc(alias = "TransactionReceipt", alias = "TxReceipt")] @@ -15,7 +15,7 @@ pub struct Receipt { /// If transaction is executed successfully. /// /// This is the `statusCode` - #[cfg_attr(feature = "serde", serde(alias = "root"))] + #[cfg_attr(feature = "serde", serde(flatten))] pub status: Eip658Value, /// Gas used #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] @@ -24,34 +24,6 @@ pub struct Receipt { pub logs: Vec, } -#[cfg(feature = "serde")] -impl serde::Serialize for Receipt -where - T: serde::Serialize, -{ - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - use serde::ser::SerializeStruct; - - let mut s = serializer.serialize_struct("Receipt", 3)?; - - // If the status is EIP-658, serialize the status field. - // Otherwise, serialize the root field. - let key = if self.status.is_eip658() { "status" } else { "root" }; - s.serialize_field(key, &self.status)?; - - s.serialize_field( - "cumulativeGasUsed", - &alloy_primitives::U128::from(self.cumulative_gas_used), - )?; - s.serialize_field("logs", &self.logs)?; - - s.end() - } -} - impl Receipt where T: Borrow, diff --git a/crates/consensus/src/receipt/status.rs b/crates/consensus/src/receipt/status.rs index b012a3e445c..9cbddfdb581 100644 --- a/crates/consensus/src/receipt/status.rs +++ b/crates/consensus/src/receipt/status.rs @@ -75,62 +75,55 @@ impl Default for Eip658Value { } #[cfg(feature = "serde")] -impl serde::Serialize for Eip658Value { - fn serialize(&self, serializer: S) -> Result { - match self { - Self::Eip658(status) => alloy_serde::quantity::serialize(status, serializer), - Self::PostState(state) => state.serialize(serializer), - } +mod serde_eip658 { + //! Serde implementation for [`Eip658Value`]. Serializes [`Eip658Value::Eip658`] as `status` + //! key, and [`Eip658Value::PostState`] as `root` key. + //! + //! If both are present, prefers `status` key. + //! + //! Should be used with `#[serde(flatten)]`. + use super::*; + use serde::{Deserialize, Serialize}; + + #[derive(serde::Serialize, serde::Deserialize)] + #[serde(untagged)] + enum SerdeHelper { + Eip658 { + #[serde(with = "alloy_serde::quantity")] + status: bool, + }, + PostState { + root: B256, + }, } -} - -#[cfg(feature = "serde")] -// NB: some visit methods partially or wholly copied from alloy-primitives -impl<'de> serde::Deserialize<'de> for Eip658Value { - fn deserialize>(deserializer: D) -> Result { - use serde::de; - struct Visitor; - - impl<'de> de::Visitor<'de> for Visitor { - type Value = Eip658Value; - - fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - formatter.write_str("a boolean or a 32-byte hash") - } - - fn visit_bool(self, v: bool) -> Result { - Ok(Eip658Value::Eip658(v)) - } - fn visit_str(self, v: &str) -> Result { - match v { - "0x" | "0x0" | "false" => Ok(Eip658Value::Eip658(false)), - "0x1" | "true" => Ok(Eip658Value::Eip658(true)), - _ => v.parse::().map(Eip658Value::PostState).map_err(de::Error::custom), + impl Serialize for Eip658Value { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + Self::Eip658(status) => { + SerdeHelper::Eip658 { status: *status }.serialize(serializer) } - } - - fn visit_bytes(self, v: &[u8]) -> Result { - B256::try_from(v).map(Eip658Value::PostState).map_err(de::Error::custom) - } - - fn visit_seq>(self, mut seq: A) -> Result { - let len_error = |i| de::Error::invalid_length(i, &"exactly 32 bytes"); - let mut bytes = [0u8; 32]; - - for (i, byte) in bytes.iter_mut().enumerate() { - *byte = seq.next_element()?.ok_or_else(|| len_error(i))?; + Self::PostState(state) => { + SerdeHelper::PostState { root: *state }.serialize(serializer) } - - if let Ok(Some(_)) = seq.next_element::() { - return Err(len_error(33)); - } - - Ok(Eip658Value::PostState(bytes.into())) } } + } - deserializer.deserialize_any(Visitor) + impl<'de> Deserialize<'de> for Eip658Value { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let helper = SerdeHelper::deserialize(deserializer)?; + match helper { + SerdeHelper::Eip658 { status } => Ok(Self::Eip658(status)), + SerdeHelper::PostState { root } => Ok(Self::PostState(root)), + } + } } } @@ -148,8 +141,8 @@ impl Encodable for Eip658Value { fn length(&self) -> usize { match self { - Self::Eip658(_) => 1, - Self::PostState(_) => 32, + Self::Eip658(inner) => inner.length(), + Self::PostState(inner) => inner.length(), } } } @@ -199,15 +192,18 @@ mod test { fn serde_sanity() { let status: Eip658Value = true.into(); let json = serde_json::to_string(&status).unwrap(); - assert_eq!(json, r#""0x1""#); + assert_eq!(json, r#"{"status":"0x1"}"#); assert_eq!(serde_json::from_str::(&json).unwrap(), status); let state: Eip658Value = false.into(); let json = serde_json::to_string(&state).unwrap(); - assert_eq!(json, r#""0x0""#); + assert_eq!(json, r#"{"status":"0x0"}"#); let state: Eip658Value = B256::repeat_byte(1).into(); let json = serde_json::to_string(&state).unwrap(); - assert_eq!(json, r#""0x0101010101010101010101010101010101010101010101010101010101010101""#); + assert_eq!( + json, + r#"{"root":"0x0101010101010101010101010101010101010101010101010101010101010101"}"# + ); } }