Skip to content

feat: implement TransactionEnvelope derive macro #2585

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

Merged
merged 17 commits into from
Jun 17, 2025
Merged

Conversation

klkvr
Copy link
Member

@klkvr klkvr commented Jun 15, 2025

Motivation

We already maintain two almost identical transaction envelope implementations for OpTxEnvelope and EthereumTxEnvelope which implement same traits on two different enums. Right now any user that wants to define their own transaction envelope would need to implement similar amount of boilerplate which affects ergonomics.

Solution

This PR introduces TransactionEnvelope derive macro which allows us to deduplicate existing code and for new users to avoid writing it while also having a way to easily wrap an internal built-in type into a new envelope.

The API is as following:

#[derive(Debug, Clone, TransactionEnvelope)]
#[envelope(alloy_consensus = crate, tx_type_name = MyTxType)]
enum MyEnvelope {
#[envelope(flatten)]
Ethereum(TxEnvelope),
#[envelope(ty = 10)]
MyTx(Signed<TxEip1559>),
#[envelope(ty = 11)]
AnotherMyTx(Signed<TxEip1559>),
}

Expansion for EthereumTxEnvelope:

// Recursive expansion of TransactionEnvelope macro
// =================================================

use crate::private::alloy_eips::{Decodable2718 as _, Encodable2718 as _};
#[doc = r" Transaction types supported by [`#input_type_name`]."]
#[repr(u8)]
#[derive(
    Clone,
    Copy,
    Debug,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    Hash,
    crate::private::serde::Serialize,
    crate::private::serde::Deserialize,
)]
#[serde(
    into = "crate :: private :: alloy_primitives :: U8",
    try_from = "crate :: private :: alloy_primitives :: U64"
)]
pub enum TxType {
    #[doc = "Transaction type of `Signed < TxLegacy >`."]
    Legacy = 0u8,
    #[doc = "Transaction type of `Signed < TxEip2930 >`."]
    Eip2930 = 1u8,
    #[doc = "Transaction type of `Signed < TxEip1559 >`."]
    Eip1559 = 2u8,
    #[doc = "Transaction type of `Signed < Eip4844 >`."]
    Eip4844 = 3u8,
    #[doc = "Transaction type of `Signed < TxEip7702 >`."]
    Eip7702 = 4u8,
}
impl From<TxType> for u8 {
    fn from(value: TxType) -> Self {
        match value {
            TxType::Legacy => 0u8,
            TxType::Eip2930 => 1u8,
            TxType::Eip1559 => 2u8,
            TxType::Eip4844 => 3u8,
            TxType::Eip7702 => 4u8,
        }
    }
}
impl From<TxType> for crate::private::alloy_primitives::U8 {
    fn from(value: TxType) -> Self {
        Self::from(u8::from(value))
    }
}
impl TryFrom<u8> for TxType {
    type Error = crate::private::alloy_eips::eip2718::Eip2718Error;
    fn try_from(value: u8) -> Result<Self, Self::Error> {
        if value == 0u8 {
            return Ok(Self::Legacy);
        };
        if value == 1u8 {
            return Ok(Self::Eip2930);
        };
        if value == 2u8 {
            return Ok(Self::Eip1559);
        };
        if value == 3u8 {
            return Ok(Self::Eip4844);
        };
        if value == 4u8 {
            return Ok(Self::Eip7702);
        }
        return Err(crate::private::alloy_eips::eip2718::Eip2718Error::UnexpectedType(value));
    }
}
impl TryFrom<u64> for TxType {
    type Error = &'static str;
    fn try_from(value: u64) -> Result<Self, Self::Error> {
        let err = || "invalid tx type";
        let value: u8 = value.try_into().map_err(|_| err())?;
        Self::try_from(value).map_err(|_| err())
    }
}
impl TryFrom<crate::private::alloy_primitives::U8> for TxType {
    type Error = crate::private::alloy_eips::eip2718::Eip2718Error;
    fn try_from(value: crate::private::alloy_primitives::U8) -> Result<Self, Self::Error> {
        value.to::<u8>().try_into()
    }
}
impl TryFrom<crate::private::alloy_primitives::U64> for TxType {
    type Error = &'static str;
    fn try_from(value: crate::private::alloy_primitives::U64) -> Result<Self, Self::Error> {
        value.to::<u64>().try_into()
    }
}
impl crate::Typed2718 for TxType {
    fn ty(&self) -> u8 {
        match self {
            Self::Legacy => 0u8,
            Self::Eip2930 => 1u8,
            Self::Eip1559 => 2u8,
            Self::Eip4844 => 3u8,
            Self::Eip7702 => 4u8,
        }
    }
}
impl crate::private::alloy_eips::eip2718::IsTyped2718 for TxType {
    fn is_type(type_id: u8) -> bool {
        Self::try_from(type_id).is_ok()
    }
}
impl PartialEq<u8> for TxType {
    fn eq(&self, other: &u8) -> bool {
        u8::from(*self) == *other
    }
}
impl PartialEq<TxType> for u8 {
    fn eq(&self, other: &TxType) -> bool {
        *self == u8::from(*other)
    }
}
impl crate::private::alloy_rlp::Encodable for TxType {
    fn encode(&self, out: &mut dyn crate::private::alloy_rlp::BufMut) {
        u8::from(*self).encode(out);
    }
    fn length(&self) -> usize {
        u8::from(*self).length()
    }
}
impl crate::private::alloy_rlp::Decodable for TxType {
    fn decode(buf: &mut &[u8]) -> crate::private::alloy_rlp::Result<Self> {
        let ty = u8::decode(buf)?;
        Self::try_from(ty)
            .map_err(|_| crate::private::alloy_rlp::Error::Custom("invalid transaction type"))
    }
}
impl<Eip4844> PartialEq for EthereumTxEnvelope<Eip4844>
where
    Signed<TxLegacy>: PartialEq,
    Signed<TxEip2930>: PartialEq,
    Signed<TxEip1559>: PartialEq,
    Signed<Eip4844>: PartialEq,
    Signed<TxEip7702>: PartialEq,
{
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::Legacy(tx), Self::Legacy(other)) => tx.eq(other),
            (Self::Eip2930(tx), Self::Eip2930(other)) => tx.eq(other),
            (Self::Eip1559(tx), Self::Eip1559(other)) => tx.eq(other),
            (Self::Eip4844(tx), Self::Eip4844(other)) => tx.eq(other),
            (Self::Eip7702(tx), Self::Eip7702(other)) => tx.eq(other),
            _ => false,
        }
    }
}
impl<Eip4844> Eq for EthereumTxEnvelope<Eip4844>
where
    Signed<TxLegacy>: PartialEq,
    Signed<TxEip2930>: PartialEq,
    Signed<TxEip1559>: PartialEq,
    Signed<Eip4844>: PartialEq,
    Signed<TxEip7702>: PartialEq,
{
}

impl<Eip4844> core::hash::Hash for EthereumTxEnvelope<Eip4844>
where
    Self: crate::private::alloy_eips::Encodable2718,
{
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
        self.trie_hash().hash(state);
    }
}
impl<Eip4844> crate::Transaction for EthereumTxEnvelope<Eip4844>
where
    Self: core::fmt::Debug,
    Signed<TxLegacy>: crate::Transaction,
    Signed<TxEip2930>: crate::Transaction,
    Signed<TxEip1559>: crate::Transaction,
    Signed<Eip4844>: crate::Transaction,
    Signed<TxEip7702>: crate::Transaction,
{
    #[inline]
    fn chain_id(&self) -> Option<u64> {
        match self {
            Self::Legacy(tx) => tx.chain_id(),
            Self::Eip2930(tx) => tx.chain_id(),
            Self::Eip1559(tx) => tx.chain_id(),
            Self::Eip4844(tx) => tx.chain_id(),
            Self::Eip7702(tx) => tx.chain_id(),
        }
    }
    #[inline]
    fn nonce(&self) -> u64 {
        match self {
            Self::Legacy(tx) => tx.nonce(),
            Self::Eip2930(tx) => tx.nonce(),
            Self::Eip1559(tx) => tx.nonce(),
            Self::Eip4844(tx) => tx.nonce(),
            Self::Eip7702(tx) => tx.nonce(),
        }
    }
    #[inline]
    fn gas_limit(&self) -> u64 {
        match self {
            Self::Legacy(tx) => tx.gas_limit(),
            Self::Eip2930(tx) => tx.gas_limit(),
            Self::Eip1559(tx) => tx.gas_limit(),
            Self::Eip4844(tx) => tx.gas_limit(),
            Self::Eip7702(tx) => tx.gas_limit(),
        }
    }
    #[inline]
    fn gas_price(&self) -> Option<u128> {
        match self {
            Self::Legacy(tx) => tx.gas_price(),
            Self::Eip2930(tx) => tx.gas_price(),
            Self::Eip1559(tx) => tx.gas_price(),
            Self::Eip4844(tx) => tx.gas_price(),
            Self::Eip7702(tx) => tx.gas_price(),
        }
    }
    #[inline]
    fn max_fee_per_gas(&self) -> u128 {
        match self {
            Self::Legacy(tx) => tx.max_fee_per_gas(),
            Self::Eip2930(tx) => tx.max_fee_per_gas(),
            Self::Eip1559(tx) => tx.max_fee_per_gas(),
            Self::Eip4844(tx) => tx.max_fee_per_gas(),
            Self::Eip7702(tx) => tx.max_fee_per_gas(),
        }
    }
    #[inline]
    fn max_priority_fee_per_gas(&self) -> Option<u128> {
        match self {
            Self::Legacy(tx) => tx.max_priority_fee_per_gas(),
            Self::Eip2930(tx) => tx.max_priority_fee_per_gas(),
            Self::Eip1559(tx) => tx.max_priority_fee_per_gas(),
            Self::Eip4844(tx) => tx.max_priority_fee_per_gas(),
            Self::Eip7702(tx) => tx.max_priority_fee_per_gas(),
        }
    }
    #[inline]
    fn max_fee_per_blob_gas(&self) -> Option<u128> {
        match self {
            Self::Legacy(tx) => tx.max_fee_per_blob_gas(),
            Self::Eip2930(tx) => tx.max_fee_per_blob_gas(),
            Self::Eip1559(tx) => tx.max_fee_per_blob_gas(),
            Self::Eip4844(tx) => tx.max_fee_per_blob_gas(),
            Self::Eip7702(tx) => tx.max_fee_per_blob_gas(),
        }
    }
    #[inline]
    fn priority_fee_or_price(&self) -> u128 {
        match self {
            Self::Legacy(tx) => tx.priority_fee_or_price(),
            Self::Eip2930(tx) => tx.priority_fee_or_price(),
            Self::Eip1559(tx) => tx.priority_fee_or_price(),
            Self::Eip4844(tx) => tx.priority_fee_or_price(),
            Self::Eip7702(tx) => tx.priority_fee_or_price(),
        }
    }
    #[inline]
    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
        match self {
            Self::Legacy(tx) => tx.effective_gas_price(base_fee),
            Self::Eip2930(tx) => tx.effective_gas_price(base_fee),
            Self::Eip1559(tx) => tx.effective_gas_price(base_fee),
            Self::Eip4844(tx) => tx.effective_gas_price(base_fee),
            Self::Eip7702(tx) => tx.effective_gas_price(base_fee),
        }
    }
    #[inline]
    fn is_dynamic_fee(&self) -> bool {
        match self {
            Self::Legacy(tx) => tx.is_dynamic_fee(),
            Self::Eip2930(tx) => tx.is_dynamic_fee(),
            Self::Eip1559(tx) => tx.is_dynamic_fee(),
            Self::Eip4844(tx) => tx.is_dynamic_fee(),
            Self::Eip7702(tx) => tx.is_dynamic_fee(),
        }
    }
    #[inline]
    fn kind(&self) -> crate::private::alloy_primitives::TxKind {
        match self {
            Self::Legacy(tx) => tx.kind(),
            Self::Eip2930(tx) => tx.kind(),
            Self::Eip1559(tx) => tx.kind(),
            Self::Eip4844(tx) => tx.kind(),
            Self::Eip7702(tx) => tx.kind(),
        }
    }
    #[inline]
    fn is_create(&self) -> bool {
        match self {
            Self::Legacy(tx) => tx.is_create(),
            Self::Eip2930(tx) => tx.is_create(),
            Self::Eip1559(tx) => tx.is_create(),
            Self::Eip4844(tx) => tx.is_create(),
            Self::Eip7702(tx) => tx.is_create(),
        }
    }
    #[inline]
    fn value(&self) -> crate::private::alloy_primitives::U256 {
        match self {
            Self::Legacy(tx) => tx.value(),
            Self::Eip2930(tx) => tx.value(),
            Self::Eip1559(tx) => tx.value(),
            Self::Eip4844(tx) => tx.value(),
            Self::Eip7702(tx) => tx.value(),
        }
    }
    #[inline]
    fn input(&self) -> &crate::private::alloy_primitives::Bytes {
        match self {
            Self::Legacy(tx) => tx.input(),
            Self::Eip2930(tx) => tx.input(),
            Self::Eip1559(tx) => tx.input(),
            Self::Eip4844(tx) => tx.input(),
            Self::Eip7702(tx) => tx.input(),
        }
    }
    #[inline]
    fn access_list(&self) -> Option<&crate::private::alloy_eips::eip2930::AccessList> {
        match self {
            Self::Legacy(tx) => tx.access_list(),
            Self::Eip2930(tx) => tx.access_list(),
            Self::Eip1559(tx) => tx.access_list(),
            Self::Eip4844(tx) => tx.access_list(),
            Self::Eip7702(tx) => tx.access_list(),
        }
    }
    #[inline]
    fn blob_versioned_hashes(&self) -> Option<&[crate::private::alloy_primitives::B256]> {
        match self {
            Self::Legacy(tx) => tx.blob_versioned_hashes(),
            Self::Eip2930(tx) => tx.blob_versioned_hashes(),
            Self::Eip1559(tx) => tx.blob_versioned_hashes(),
            Self::Eip4844(tx) => tx.blob_versioned_hashes(),
            Self::Eip7702(tx) => tx.blob_versioned_hashes(),
        }
    }
    #[inline]
    fn authorization_list(
        &self,
    ) -> Option<&[crate::private::alloy_eips::eip7702::SignedAuthorization]> {
        match self {
            Self::Legacy(tx) => tx.authorization_list(),
            Self::Eip2930(tx) => tx.authorization_list(),
            Self::Eip1559(tx) => tx.authorization_list(),
            Self::Eip4844(tx) => tx.authorization_list(),
            Self::Eip7702(tx) => tx.authorization_list(),
        }
    }
}
impl<Eip4844> crate::private::alloy_eips::eip2718::IsTyped2718 for EthereumTxEnvelope<Eip4844> {
    fn is_type(type_id: u8) -> bool {
        <TxType as crate::private::alloy_eips::eip2718::IsTyped2718>::is_type(type_id)
    }
}
impl<Eip4844> crate::Typed2718 for EthereumTxEnvelope<Eip4844>
where
    Signed<TxLegacy>: crate::private::alloy_eips::eip2718::Typed2718,
    Signed<TxEip2930>: crate::private::alloy_eips::eip2718::Typed2718,
    Signed<TxEip1559>: crate::private::alloy_eips::eip2718::Typed2718,
    Signed<Eip4844>: crate::private::alloy_eips::eip2718::Typed2718,
    Signed<TxEip7702>: crate::private::alloy_eips::eip2718::Typed2718,
{
    fn ty(&self) -> u8 {
        match self {
            Self::Legacy(tx) => tx.ty(),
            Self::Eip2930(tx) => tx.ty(),
            Self::Eip1559(tx) => tx.ty(),
            Self::Eip4844(tx) => tx.ty(),
            Self::Eip7702(tx) => tx.ty(),
        }
    }
}
impl<Eip4844> crate::private::alloy_eips::Encodable2718 for EthereumTxEnvelope<Eip4844>
where
    Signed<TxLegacy>: crate::private::alloy_eips::Encodable2718,
    Signed<TxEip2930>: crate::private::alloy_eips::Encodable2718,
    Signed<TxEip1559>: crate::private::alloy_eips::Encodable2718,
    Signed<Eip4844>: crate::private::alloy_eips::Encodable2718,
    Signed<TxEip7702>: crate::private::alloy_eips::Encodable2718,
{
    fn encode_2718_len(&self) -> usize {
        match self {
            Self::Legacy(tx) => tx.encode_2718_len(),
            Self::Eip2930(tx) => tx.encode_2718_len(),
            Self::Eip1559(tx) => tx.encode_2718_len(),
            Self::Eip4844(tx) => tx.encode_2718_len(),
            Self::Eip7702(tx) => tx.encode_2718_len(),
        }
    }
    fn encode_2718(&self, out: &mut dyn crate::private::alloy_rlp::BufMut) {
        match self {
            Self::Legacy(tx) => tx.encode_2718(out),
            Self::Eip2930(tx) => tx.encode_2718(out),
            Self::Eip1559(tx) => tx.encode_2718(out),
            Self::Eip4844(tx) => tx.encode_2718(out),
            Self::Eip7702(tx) => tx.encode_2718(out),
        }
    }
    fn trie_hash(&self) -> crate::private::alloy_primitives::B256 {
        match self {
            Self::Legacy(tx) => tx.trie_hash(),
            Self::Eip2930(tx) => tx.trie_hash(),
            Self::Eip1559(tx) => tx.trie_hash(),
            Self::Eip4844(tx) => tx.trie_hash(),
            Self::Eip7702(tx) => tx.trie_hash(),
        }
    }
}
impl<Eip4844> crate::private::alloy_rlp::Encodable for EthereumTxEnvelope<Eip4844>
where
    Signed<TxLegacy>: crate::private::alloy_eips::Encodable2718,
    Signed<TxEip2930>: crate::private::alloy_eips::Encodable2718,
    Signed<TxEip1559>: crate::private::alloy_eips::Encodable2718,
    Signed<Eip4844>: crate::private::alloy_eips::Encodable2718,
    Signed<TxEip7702>: crate::private::alloy_eips::Encodable2718,
{
    fn encode(&self, out: &mut dyn crate::private::alloy_rlp::BufMut) {
        <Self as crate::private::alloy_eips::Encodable2718>::network_encode(self, out)
    }
    fn length(&self) -> usize {
        <Self as crate::private::alloy_eips::Encodable2718>::network_len(self)
    }
}
impl<Eip4844> crate::private::alloy_eips::Decodable2718 for EthereumTxEnvelope<Eip4844>
where
    Signed<TxLegacy>: crate::private::alloy_eips::Decodable2718,
    Signed<TxEip2930>: crate::private::alloy_eips::Decodable2718,
    Signed<TxEip1559>: crate::private::alloy_eips::Decodable2718,
    Signed<Eip4844>: crate::private::alloy_eips::Decodable2718,
    Signed<TxEip7702>: crate::private::alloy_eips::Decodable2718,
{
    fn typed_decode(
        ty: u8,
        buf: &mut &[u8],
    ) -> crate::private::alloy_eips::eip2718::Eip2718Result<Self> {
        match ty.try_into().map_err(|_| alloy_rlp::Error::Custom("unexpected tx type"))? {
            TxType::Legacy => {
                Ok(Self::Legacy(crate::private::alloy_eips::Decodable2718::typed_decode(ty, buf)?))
            }
            TxType::Eip2930 => {
                Ok(Self::Eip2930(crate::private::alloy_eips::Decodable2718::typed_decode(ty, buf)?))
            }
            TxType::Eip1559 => {
                Ok(Self::Eip1559(crate::private::alloy_eips::Decodable2718::typed_decode(ty, buf)?))
            }
            TxType::Eip4844 => {
                Ok(Self::Eip4844(crate::private::alloy_eips::Decodable2718::typed_decode(ty, buf)?))
            }
            TxType::Eip7702 => {
                Ok(Self::Eip7702(crate::private::alloy_eips::Decodable2718::typed_decode(ty, buf)?))
            }
        }
    }
    fn fallback_decode(
        buf: &mut &[u8],
    ) -> crate::private::alloy_eips::eip2718::Eip2718Result<Self> {
        if let Ok(tx) = crate::private::alloy_eips::Decodable2718::fallback_decode(buf) {
            return Ok(Self::Legacy(tx));
        }
        if let Ok(tx) = crate::private::alloy_eips::Decodable2718::fallback_decode(buf) {
            return Ok(Self::Eip2930(tx));
        }
        if let Ok(tx) = crate::private::alloy_eips::Decodable2718::fallback_decode(buf) {
            return Ok(Self::Eip1559(tx));
        }
        if let Ok(tx) = crate::private::alloy_eips::Decodable2718::fallback_decode(buf) {
            return Ok(Self::Eip4844(tx));
        }
        if let Ok(tx) = crate::private::alloy_eips::Decodable2718::fallback_decode(buf) {
            return Ok(Self::Eip7702(tx));
        }
        return Err(crate::private::alloy_eips::eip2718::Eip2718Error::UnexpectedType(0));
    }
}
impl<Eip4844> crate::private::alloy_rlp::Decodable for EthereumTxEnvelope<Eip4844>
where
    Signed<TxLegacy>: crate::private::alloy_eips::Decodable2718,
    Signed<TxEip2930>: crate::private::alloy_eips::Decodable2718,
    Signed<TxEip1559>: crate::private::alloy_eips::Decodable2718,
    Signed<Eip4844>: crate::private::alloy_eips::Decodable2718,
    Signed<TxEip7702>: crate::private::alloy_eips::Decodable2718,
{
    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
        Ok(<Self as crate::private::alloy_eips::Decodable2718>::network_decode(buf)?)
    }
}
impl<Eip4844> crate::TransactionEnvelope for EthereumTxEnvelope<Eip4844>
where
    Self: crate::Transaction,
{
    type TxType = TxType;
}
const _: () = {
    #[derive(Debug, serde::Serialize, serde::Deserialize)]
    #[serde(
        tag = "type",
        bound = "EthereumTxEnvelope < Eip4844 > : Clone , Signed < TxLegacy > : serde :: Serialize + serde :: de :: DeserializeOwned , Signed < TxEip2930 > : serde :: Serialize + serde :: de :: DeserializeOwned , Signed < TxEip1559 > : serde :: Serialize + serde :: de :: DeserializeOwned , Signed < Eip4844 > : serde :: Serialize + serde :: de :: DeserializeOwned , Signed < TxEip7702 > : serde :: Serialize + serde :: de :: DeserializeOwned"
    )]
    enum TaggedTxTypes<Eip4844> {
        #[serde(
            rename = "0x0",
            alias = "0x00",
            with = "crate :: transaction :: signed_legacy_serde"
        )]
        Legacy(Signed<TxLegacy>),
        #[serde(rename = "0x1", alias = "0x01")]
        Eip2930(Signed<TxEip2930>),
        #[serde(rename = "0x2", alias = "0x02")]
        Eip1559(Signed<TxEip1559>),
        #[serde(rename = "0x3", alias = "0x03")]
        Eip4844(Signed<Eip4844>),
        #[serde(rename = "0x4", alias = "0x04")]
        Eip7702(Signed<TxEip7702>),
    }
    impl<Eip4844> From<TaggedTxTypes<Eip4844>> for EthereumTxEnvelope<Eip4844> {
        fn from(value: TaggedTxTypes<Eip4844>) -> Self {
            match value {
                TaggedTxTypes::<Eip4844>::Legacy(value) => Self::Legacy(value),
                TaggedTxTypes::<Eip4844>::Eip2930(value) => Self::Eip2930(value),
                TaggedTxTypes::<Eip4844>::Eip1559(value) => Self::Eip1559(value),
                TaggedTxTypes::<Eip4844>::Eip4844(value) => Self::Eip4844(value),
                TaggedTxTypes::<Eip4844>::Eip7702(value) => Self::Eip7702(value),
            }
        }
    }
    #[derive(crate::private::serde::Serialize)]
    #[serde(
        untagged,
        bound = "EthereumTxEnvelope < Eip4844 > : Clone , Signed < TxLegacy > : serde :: Serialize + serde :: de :: DeserializeOwned , Signed < TxEip2930 > : serde :: Serialize + serde :: de :: DeserializeOwned , Signed < TxEip1559 > : serde :: Serialize + serde :: de :: DeserializeOwned , Signed < Eip4844 > : serde :: Serialize + serde :: de :: DeserializeOwned , Signed < TxEip7702 > : serde :: Serialize + serde :: de :: DeserializeOwned"
    )]
    pub(crate) enum UntaggedTxTypes<Eip4844> {
        Tagged(TaggedTxTypes<Eip4844>),
        UntaggedLegacy(Signed<TxLegacy>),
    }
    impl<'de, Eip4844> crate::private::serde::Deserialize<'de> for UntaggedTxTypes<Eip4844>
    where
        EthereumTxEnvelope<Eip4844>: Clone,
        Signed<TxLegacy>: serde::Serialize + serde::de::DeserializeOwned,
        Signed<TxEip2930>: serde::Serialize + serde::de::DeserializeOwned,
        Signed<TxEip1559>: serde::Serialize + serde::de::DeserializeOwned,
        Signed<Eip4844>: serde::Serialize + serde::de::DeserializeOwned,
        Signed<TxEip7702>: serde::Serialize + serde::de::DeserializeOwned,
    {
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where
            D: crate::private::serde::Deserializer<'de>,
        {
            let content = crate::private::serde::__private::de::Content::deserialize(deserializer)?;
            let deserializer = crate::private::serde::__private::de::ContentRefDeserializer::<
                D::Error,
            >::new(&content);
            let tagged_res = TaggedTxTypes::<Eip4844>::deserialize(deserializer).map(Self::Tagged);
            if tagged_res.is_ok() {
                return tagged_res;
            }
            if let Ok(val) = crate::transaction::untagged_legacy_serde::deserialize(deserializer)
                .map(Self::UntaggedLegacy)
            {
                return Ok(val);
            }
            tagged_res
        }
    }
    impl<Eip4844> From<UntaggedTxTypes<Eip4844>> for EthereumTxEnvelope<Eip4844> {
        fn from(value: UntaggedTxTypes<Eip4844>) -> Self {
            match value {
                UntaggedTxTypes::Tagged(value) => value.into(),
                UntaggedTxTypes::UntaggedLegacy(tx) => Self::Legacy(tx),
            }
        }
    }
    impl<Eip4844> From<EthereumTxEnvelope<Eip4844>> for UntaggedTxTypes<Eip4844> {
        fn from(value: EthereumTxEnvelope<Eip4844>) -> Self {
            match value {
                EthereumTxEnvelope::<Eip4844>::Legacy(value) => {
                    Self::Tagged(TaggedTxTypes::Legacy(value))
                }
                EthereumTxEnvelope::<Eip4844>::Eip2930(value) => {
                    Self::Tagged(TaggedTxTypes::Eip2930(value))
                }
                EthereumTxEnvelope::<Eip4844>::Eip1559(value) => {
                    Self::Tagged(TaggedTxTypes::Eip1559(value))
                }
                EthereumTxEnvelope::<Eip4844>::Eip4844(value) => {
                    Self::Tagged(TaggedTxTypes::Eip4844(value))
                }
                EthereumTxEnvelope::<Eip4844>::Eip7702(value) => {
                    Self::Tagged(TaggedTxTypes::Eip7702(value))
                }
            }
        }
    }
    impl<Eip4844> crate::private::serde::Serialize for EthereumTxEnvelope<Eip4844>
    where
        EthereumTxEnvelope<Eip4844>: Clone,
        Signed<TxLegacy>: serde::Serialize + serde::de::DeserializeOwned,
        Signed<TxEip2930>: serde::Serialize + serde::de::DeserializeOwned,
        Signed<TxEip1559>: serde::Serialize + serde::de::DeserializeOwned,
        Signed<Eip4844>: serde::Serialize + serde::de::DeserializeOwned,
        Signed<TxEip7702>: serde::Serialize + serde::de::DeserializeOwned,
    {
        fn serialize<S: crate::private::serde::Serializer>(
            &self,
            serializer: S,
        ) -> Result<S::Ok, S::Error> {
            UntaggedTxTypes::<Eip4844>::from(self.clone()).serialize(serializer)
        }
    }
    impl<'de, Eip4844> crate::private::serde::Deserialize<'de> for EthereumTxEnvelope<Eip4844>
    where
        EthereumTxEnvelope<Eip4844>: Clone,
        Signed<TxLegacy>: serde::Serialize + serde::de::DeserializeOwned,
        Signed<TxEip2930>: serde::Serialize + serde::de::DeserializeOwned,
        Signed<TxEip1559>: serde::Serialize + serde::de::DeserializeOwned,
        Signed<Eip4844>: serde::Serialize + serde::de::DeserializeOwned,
        Signed<TxEip7702>: serde::Serialize + serde::de::DeserializeOwned,
    {
        fn deserialize<D: crate::private::serde::Deserializer<'de>>(
            deserializer: D,
        ) -> Result<Self, D::Error> {
            UntaggedTxTypes::<Eip4844>::deserialize(deserializer).map(Into::into)
        }
    }
};
impl crate::private::arbitrary::Arbitrary<'_> for TxType {
    fn arbitrary(
        u: &mut crate::private::arbitrary::Unstructured<'_>,
    ) -> crate::private::arbitrary::Result<Self> {
        match u.int_in_range(0..=5usize - 1)? {
            0usize => Ok(Self::Legacy),
            1usize => Ok(Self::Eip2930),
            2usize => Ok(Self::Eip1559),
            3usize => Ok(Self::Eip4844),
            4usize => Ok(Self::Eip7702),
            _ => unreachable!(),
        }
    }
}
impl<Eip4844> crate::private::arbitrary::Arbitrary<'_> for EthereumTxEnvelope<Eip4844>
where
    Signed<TxLegacy>: for<'a> crate::private::arbitrary::Arbitrary<'a>,
    Signed<TxEip2930>: for<'a> crate::private::arbitrary::Arbitrary<'a>,
    Signed<TxEip1559>: for<'a> crate::private::arbitrary::Arbitrary<'a>,
    Signed<Eip4844>: for<'a> crate::private::arbitrary::Arbitrary<'a>,
    Signed<TxEip7702>: for<'a> crate::private::arbitrary::Arbitrary<'a>,
{
    fn arbitrary(
        u: &mut crate::private::arbitrary::Unstructured<'_>,
    ) -> crate::private::arbitrary::Result<Self> {
        match u.int_in_range(0..=5usize - 1)? {
            0usize => Ok(Self::Legacy(u.arbitrary()?)),
            1usize => Ok(Self::Eip2930(u.arbitrary()?)),
            2usize => Ok(Self::Eip1559(u.arbitrary()?)),
            3usize => Ok(Self::Eip4844(u.arbitrary()?)),
            4usize => Ok(Self::Eip7702(u.arbitrary()?)),
            _ => unreachable!(),
        }
    }
}

PR Checklist

  • Added Tests
  • Added Documentation
  • Breaking changes

@klkvr klkvr force-pushed the klkvr/tx-envelope-macro branch from 84f8844 to 59d0743 Compare June 15, 2025 23:14
Copy link
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

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

very nice,

a few nits,

before we merge this, can we prepare a patch pr to reth?

@@ -263,6 +268,187 @@ impl<'a, T: SignableTransaction<Signature> + arbitrary::Arbitrary<'a>> arbitrary
}
}

impl<T> Typed2718 for Signed<T>
Copy link
Member

Choose a reason for hiding this comment

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

ah yeah, we can easily add these trait impls, because Signed isn't deref so this shouldn't conflict

@github-project-automation github-project-automation bot moved this to In Progress in Alloy Jun 16, 2025
@klkvr klkvr force-pushed the klkvr/tx-envelope-macro branch from 0ee585f to 501fa10 Compare June 16, 2025 14:10
@klkvr klkvr force-pushed the klkvr/tx-envelope-macro branch from f554ed6 to 997b6ce Compare June 16, 2025 17:13
@klkvr klkvr force-pushed the klkvr/tx-envelope-macro branch from 997b6ce to 7f8c746 Compare June 16, 2025 17:19
@klkvr klkvr force-pushed the klkvr/tx-envelope-macro branch from 242dd84 to 200d54c Compare June 16, 2025 20:32
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Copy link
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

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

lgtm

@github-project-automation github-project-automation bot moved this from In Progress to Reviewed in Alloy Jun 17, 2025
@mattsse mattsse merged commit 38c0436 into main Jun 17, 2025
28 checks passed
@mattsse mattsse deleted the klkvr/tx-envelope-macro branch June 17, 2025 10:31
@github-project-automation github-project-automation bot moved this from Reviewed to Done in Alloy Jun 17, 2025
@klkvr klkvr self-assigned this Jun 17, 2025
@grandizzy grandizzy moved this from Done to Completed in Alloy Jun 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Completed
Development

Successfully merging this pull request may close these issues.

2 participants