Skip to content

Commit

Permalink
fix: receipt status serde (alloy-rs#1608)
Browse files Browse the repository at this point in the history
* fix: receipt status serde

* fmt

* fix test
  • Loading branch information
klkvr authored Nov 2, 2024
1 parent 18da132 commit 46af931
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 84 deletions.
32 changes: 2 additions & 30 deletions crates/consensus/src/receipt/receipts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ 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")]
pub struct Receipt<T = Log> {
/// 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"))]
Expand All @@ -24,34 +24,6 @@ pub struct Receipt<T = Log> {
pub logs: Vec<T>,
}

#[cfg(feature = "serde")]
impl<T> serde::Serialize for Receipt<T>
where
T: serde::Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<T> Receipt<T>
where
T: Borrow<Log>,
Expand Down
104 changes: 50 additions & 54 deletions crates/consensus/src/receipt/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,62 +75,55 @@ impl Default for Eip658Value {
}

#[cfg(feature = "serde")]
impl serde::Serialize for Eip658Value {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
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<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
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<E: de::Error>(self, v: bool) -> Result<Self::Value, E> {
Ok(Eip658Value::Eip658(v))
}

fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
match v {
"0x" | "0x0" | "false" => Ok(Eip658Value::Eip658(false)),
"0x1" | "true" => Ok(Eip658Value::Eip658(true)),
_ => v.parse::<B256>().map(Eip658Value::PostState).map_err(de::Error::custom),
impl Serialize for Eip658Value {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
Self::Eip658(status) => {
SerdeHelper::Eip658 { status: *status }.serialize(serializer)
}
}

fn visit_bytes<E: de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
B256::try_from(v).map(Eip658Value::PostState).map_err(de::Error::custom)
}

fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
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::<u8>() {
return Err(len_error(33));
}

Ok(Eip658Value::PostState(bytes.into()))
}
}
}

deserializer.deserialize_any(Visitor)
impl<'de> Deserialize<'de> for Eip658Value {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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)),
}
}
}
}

Expand All @@ -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(),
}
}
}
Expand Down Expand Up @@ -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::<Eip658Value>(&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"}"#
);
}
}

0 comments on commit 46af931

Please sign in to comment.