Repository: paradigmxyz/reth (language: rust)

Issue:
User receipt Serdebincodecompat for chain type serde
### Describe the feature

this currently uses the receipt as is for the outcome:

https://github.com/paradigmxyz/reth/blob/81cff4a90b00cd5fbbb1ee122611266c14e86ed4/crates/evm/execution-types/src/chain.rs#L558-L565

because the receipt can be bincode incompat we need to enforce `Receipt: SerdeBincodeCompat` here:

https://github.com/paradigmxyz/reth/blob/81cff4a90b00cd5fbbb1ee122611266c14e86ed4/crates/evm/execution-types/src/chain.rs#L560-L562

and use the `BincodeRepr` for the receipt 

https://github.com/paradigmxyz/reth/blob/81cff4a90b00cd5fbbb1ee122611266c14e86ed4/crates/evm/execution-types/src/chain.rs#L565-L565


## TODO

* use receipt Serdebincodecompat for chain deserde

we can either use another bincodecompat type for `ExecutionOutcome` or we flatten all fields in:
https://github.com/paradigmxyz/reth/blob/81cff4a90b00cd5fbbb1ee122611266c14e86ed4/crates/evm/execution-types/src/chain.rs#L565-L565

### Additional context

for reference:

https://github.com/paradigmxyz/reth/blob/81cff4a90b00cd5fbbb1ee122611266c14e86ed4/crates/primitives-traits/src/serde_bincode_compat.rs#L145-L149


Discussion / hints:
@mattsse can I take this one ? 
@mattsse  

Correct me if I am wrong, is this implementation correct?  

```rust
#[derive(Debug, Serialize, Deserialize)]
pub struct Chain<'a, N = EthPrimitives>
where
    N: NodePrimitives<
        Block: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static,
        Receipt: SerdeBincodeCompat,
    >,
    <N::Receipt as SerdeBincodeCompat>::BincodeRepr<'a>: Clone,
{
    blocks: RecoveredBlocks<'a, N::Block>,
    execution_outcome: Cow<'a, ExecutionOutcome<<N::Receipt as SerdeBincodeCompat>::BincodeRepr<'a>>>,
    trie_updates: Option<TrieUpdates<'a>>,
}


Fix patch:
diff --git a/crates/evm/execution-types/Cargo.toml b/crates/evm/execution-types/Cargo.toml
index b860dd893b6f..6b76faab42b2 100644
--- a/crates/evm/execution-types/Cargo.toml
+++ b/crates/evm/execution-types/Cargo.toml
@@ -30,10 +30,12 @@ serde_with = { workspace = true, optional = true }
 derive_more.workspace = true
 
 [dev-dependencies]
+reth-primitives-traits = { workspace = true, features = ["test-utils", "arbitrary"] }
+reth-ethereum-primitives = { workspace = true, features = ["arbitrary"] }
+alloy-primitives = { workspace = true, features = ["rand", "arbitrary"] }
 arbitrary.workspace = true
 bincode.workspace = true
 rand.workspace = true
-reth-ethereum-primitives.workspace = true
 
 [features]
 default = ["std"]
diff --git a/crates/evm/execution-types/src/chain.rs b/crates/evm/execution-types/src/chain.rs
index 5fb436930e64..ccd35f2bd8e8 100644
--- a/crates/evm/execution-types/src/chain.rs
+++ b/crates/evm/execution-types/src/chain.rs
@@ -526,7 +526,7 @@ pub enum ChainSplit<N: NodePrimitives = reth_ethereum_primitives::EthPrimitives>
 /// Bincode-compatible [`Chain`] serde implementation.
 #[cfg(feature = "serde-bincode-compat")]
 pub(super) mod serde_bincode_compat {
-    use crate::ExecutionOutcome;
+    use crate::{serde_bincode_compat, ExecutionOutcome};
     use alloc::borrow::Cow;
     use alloy_primitives::BlockNumber;
     use reth_ethereum_primitives::EthPrimitives;
@@ -562,7 +562,7 @@ pub(super) mod serde_bincode_compat {
         >,
     {
         blocks: RecoveredBlocks<'a, N::Block>,
-        execution_outcome: Cow<'a, ExecutionOutcome<N::Receipt>>,
+        execution_outcome: serde_bincode_compat::ExecutionOutcome<'a, N::Receipt>,
         trie_updates: Option<TrieUpdates<'a>>,
     }
 
@@ -615,7 +615,7 @@ pub(super) mod serde_bincode_compat {
         fn from(value: &'a super::Chain<N>) -> Self {
             Self {
                 blocks: RecoveredBlocks(Cow::Borrowed(&value.blocks)),
-                execution_outcome: Cow::Borrowed(&value.execution_outcome),
+                execution_outcome: value.execution_outcome.as_repr(),
                 trie_updates: value.trie_updates.as_ref().map(Into::into),
             }
         }
@@ -630,7 +630,7 @@ pub(super) mod serde_bincode_compat {
         fn from(value: Chain<'a, N>) -> Self {
             Self {
                 blocks: value.blocks.0.into_owned(),
-                execution_outcome: value.execution_outcome.into_owned(),
+                execution_outcome: ExecutionOutcome::from_repr(value.execution_outcome),
                 trie_updates: value.trie_updates.map(Into::into),
             }
         }
diff --git a/crates/evm/execution-types/src/execution_outcome.rs b/crates/evm/execution-types/src/execution_outcome.rs
index fff6d201993c..88a1891c6cdc 100644
--- a/crates/evm/execution-types/src/execution_outcome.rs
+++ b/crates/evm/execution-types/src/execution_outcome.rs
@@ -7,6 +7,16 @@ use reth_trie_common::{HashedPostState, KeyHasher};
 use revm::state::AccountInfo;
 use revm_database::{states::BundleState, BundleAccount};
 
+/// Type used to initialize revms bundle state.
+pub type BundleStateInit =
+    HashMap<Address, (Option<Account>, Option<Account>, HashMap<B256, (U256, U256)>)>;
+
+/// Types used inside `RevertsInit` to initialize revms reverts.
+pub type AccountRevertInit = (Option<Option<Account>>, Vec<StorageEntry>);
+
+/// Type used to initialize revms reverts.
+pub type RevertsInit = HashMap<BlockNumber, HashMap<Address, AccountRevertInit>>;
+
 /// Represents a changed account
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub struct ChangedAccount {
@@ -60,16 +70,6 @@ impl<T> Default for ExecutionOutcome<T> {
     }
 }
 
-/// Type used to initialize revms bundle state.
-pub type BundleStateInit =
-    HashMap<Address, (Option<Account>, Option<Account>, HashMap<B256, (U256, U256)>)>;
-
-/// Types used inside `RevertsInit` to initialize revms reverts.
-pub type AccountRevertInit = (Option<Option<Account>>, Vec<StorageEntry>);
-
-/// Type used to initialize revms reverts.
-pub type RevertsInit = HashMap<BlockNumber, HashMap<Address, AccountRevertInit>>;
-
 impl<T> ExecutionOutcome<T> {
     /// Creates a new `ExecutionOutcome`.
     ///
@@ -399,6 +399,155 @@ impl<T> From<(BlockExecutionOutput<T>, BlockNumber)> for ExecutionOutcome<T> {
     }
 }
 
+#[cfg(feature = "serde-bincode-compat")]
+pub(super) mod serde_bincode_compat {
+    use alloc::borrow::Cow;
+    use alloy_eips::eip7685::Requests;
+    use alloy_primitives::BlockNumber;
+    use reth_primitives_traits::serde_bincode_compat::SerdeBincodeCompat;
+    use revm_database::BundleState;
+    use serde::{Deserialize, Deserializer, Serialize, Serializer};
+    use serde_with::{DeserializeAs, SerializeAs};
+
+    /// Bincode-compatible [`super::ExecutionOutcome`] serde implementation.
+    ///
+    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
+    /// ```rust
+    /// use reth_execution_types::{serde_bincode_compat, ExecutionOutcome};
+    /// ///
+    /// use reth_primitives_traits::serde_bincode_compat::SerdeBincodeCompat;
+    /// use serde::{Deserialize, Serialize};
+    /// use serde_with::serde_as;
+    ///
+    /// #[serde_as]
+    /// #[derive(Serialize, Deserialize)]
+    /// struct Data<T: SerdeBincodeCompat + core::fmt::Debug> {
+    ///     #[serde_as(as = "serde_bincode_compat::ExecutionOutcome<'_, T>")]
+    ///     chain: ExecutionOutcome<T>,
+    /// }
+    /// ```
+    #[derive(Debug, Serialize, Deserialize)]
+    pub struct ExecutionOutcome<'a, T>
+    where
+        T: SerdeBincodeCompat + core::fmt::Debug,
+    {
+        bundle: Cow<'a, BundleState>,
+        receipts: Vec<Vec<T::BincodeRepr<'a>>>,
+        first_block: BlockNumber,
+        requests: Cow<'a, Vec<Requests>>,
+    }
+
+    impl<'a, T> From<&'a super::ExecutionOutcome<T>> for ExecutionOutcome<'a, T>
+    where
+        T: SerdeBincodeCompat + core::fmt::Debug,
+    {
+        fn from(value: &'a super::ExecutionOutcome<T>) -> Self {
+            ExecutionOutcome {
+                bundle: Cow::Borrowed(&value.bundle),
+                receipts: value
+                    .receipts
+                    .iter()
+                    .map(|vec| vec.iter().map(|receipt| T::as_repr(receipt)).collect())
+                    .collect(),
+                first_block: value.first_block,
+                requests: Cow::Borrowed(&value.requests),
+            }
+        }
+    }
+
+    impl<'a, T> From<ExecutionOutcome<'a, T>> for super::ExecutionOutcome<T>
+    where
+        T: SerdeBincodeCompat + core::fmt::Debug,
+    {
+        fn from(value: ExecutionOutcome<'a, T>) -> Self {
+            Self {
+                bundle: value.bundle.into_owned(),
+                receipts: value
+                    .receipts
+                    .into_iter()
+                    .map(|vec| vec.into_iter().map(|receipt| T::from_repr(receipt)).collect())
+                    .collect(),
+                first_block: value.first_block,
+                requests: value.requests.into_owned(),
+            }
+        }
+    }
+
+    impl<T> SerializeAs<super::ExecutionOutcome<T>> for ExecutionOutcome<'_, T>
+    where
+        T: SerdeBincodeCompat + core::fmt::Debug,
+    {
+        fn serialize_as<S>(
+            source: &super::ExecutionOutcome<T>,
+            serializer: S,
+        ) -> Result<S::Ok, S::Error>
+        where
+            S: Serializer,
+        {
+            ExecutionOutcome::from(source).serialize(serializer)
+        }
+    }
+
+    impl<'de, T> DeserializeAs<'de, super::ExecutionOutcome<T>> for ExecutionOutcome<'de, T>
+    where
+        T: SerdeBincodeCompat + core::fmt::Debug,
+    {
+        fn deserialize_as<D>(deserializer: D) -> Result<super::ExecutionOutcome<T>, D::Error>
+        where
+            D: Deserializer<'de>,
+        {
+            ExecutionOutcome::deserialize(deserializer).map(Into::into)
+        }
+    }
+
+    impl<T: SerdeBincodeCompat + core::fmt::Debug> SerdeBincodeCompat for super::ExecutionOutcome<T> {
+        type BincodeRepr<'a> = ExecutionOutcome<'a, T>;
+
+        fn as_repr(&self) -> Self::BincodeRepr<'_> {
+            self.into()
+        }
+
+        fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
+            repr.into()
+        }
+    }
+
+    #[cfg(test)]
+    mod tests {
+        use super::super::{serde_bincode_compat, ExecutionOutcome};
+        use rand::Rng;
+        use reth_ethereum_primitives::Receipt;
+        use reth_primitives_traits::serde_bincode_compat::SerdeBincodeCompat;
+        use serde::{Deserialize, Serialize};
+        use serde_with::serde_as;
+
+        #[test]
+        fn test_chain_bincode_roundtrip() {
+            #[serde_as]
+            #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
+            struct Data<T: SerdeBincodeCompat + core::fmt::Debug> {
+                #[serde_as(as = "serde_bincode_compat::ExecutionOutcome<'_, T>")]
+                data: ExecutionOutcome<T>,
+            }
+
+            let mut bytes = [0u8; 1024];
+            rand::thread_rng().fill(bytes.as_mut_slice());
+            let data = Data {
+                data: ExecutionOutcome {
+                    bundle: Default::default(),
+                    receipts: vec![],
+                    first_block: 0,
+                    requests: vec![],
+                },
+            };
+
+            let encoded = bincode::serialize(&data).unwrap();
+            let decoded = bincode::deserialize::<Data<Receipt>>(&encoded).unwrap();
+            assert_eq!(decoded, data);
+        }
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/crates/evm/execution-types/src/lib.rs b/crates/evm/execution-types/src/lib.rs
index 15e6d3097e03..04dd84731347 100644
--- a/crates/evm/execution-types/src/lib.rs
+++ b/crates/evm/execution-types/src/lib.rs
@@ -29,5 +29,5 @@ pub use execution_outcome::*;
 /// Read more: <https://github.com/bincode-org/bincode/issues/326>
 #[cfg(feature = "serde-bincode-compat")]
 pub mod serde_bincode_compat {
-    pub use super::chain::serde_bincode_compat::*;
+    pub use super::{chain::serde_bincode_compat::*, execution_outcome::serde_bincode_compat::*};
 }


Test patch: