Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: receipt status serde #1608

Merged
merged 3 commits into from
Nov 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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))]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah right alias doesn't quite work here

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"}"#
);
}
}