reth_primitives_traits/
extended.rs

1use crate::{
2    size::InMemorySize,
3    transaction::signed::{RecoveryError, SignedTransaction},
4};
5use alloc::vec::Vec;
6use alloy_consensus::{transaction::SignerRecoverable, EthereumTxEnvelope, Transaction};
7use alloy_eips::{
8    eip2718::{Eip2718Error, Eip2718Result, IsTyped2718},
9    eip2930::AccessList,
10    eip7702::SignedAuthorization,
11    Decodable2718, Encodable2718, Typed2718,
12};
13use alloy_primitives::{ChainId, TxHash};
14use alloy_rlp::{BufMut, Decodable, Encodable, Result as RlpResult};
15use revm_primitives::{Address, Bytes, TxKind, B256, U256};
16
17macro_rules! delegate {
18    ($self:expr => $tx:ident.$method:ident($($arg:expr),*)) => {
19        match $self {
20            Self::BuiltIn($tx) => $tx.$method($($arg),*),
21            Self::Other($tx) => $tx.$method($($arg),*),
22        }
23    };
24}
25
26/// An enum that combines two different transaction types.
27///
28/// This is intended to be used to extend existing presets, for example the ethereum or optstack
29/// transaction types and receipts
30///
31/// Note: The [`Extended::Other`] variants must not overlap with the builtin one, transaction
32/// types must be unique. For example if [`Extended::BuiltIn`] contains an `EIP-1559` type variant,
33/// [`Extended::Other`] must not include that type.
34#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
35#[derive(Debug, Clone, Hash, Eq, PartialEq)]
36pub enum Extended<BuiltIn, Other> {
37    /// The builtin transaction type.
38    BuiltIn(BuiltIn),
39    /// The other transaction type.
40    Other(Other),
41}
42
43impl<B, T> Transaction for Extended<B, T>
44where
45    B: Transaction,
46    T: Transaction,
47{
48    fn chain_id(&self) -> Option<ChainId> {
49        delegate!(self => tx.chain_id())
50    }
51
52    fn nonce(&self) -> u64 {
53        delegate!(self => tx.nonce())
54    }
55
56    fn gas_limit(&self) -> u64 {
57        delegate!(self => tx.gas_limit())
58    }
59
60    fn gas_price(&self) -> Option<u128> {
61        delegate!(self => tx.gas_price())
62    }
63
64    fn max_fee_per_gas(&self) -> u128 {
65        delegate!(self => tx.max_fee_per_gas())
66    }
67
68    fn max_priority_fee_per_gas(&self) -> Option<u128> {
69        delegate!(self => tx.max_priority_fee_per_gas())
70    }
71
72    fn max_fee_per_blob_gas(&self) -> Option<u128> {
73        delegate!(self => tx.max_fee_per_blob_gas())
74    }
75
76    fn priority_fee_or_price(&self) -> u128 {
77        delegate!(self => tx.priority_fee_or_price())
78    }
79
80    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
81        delegate!(self => tx.effective_gas_price(base_fee))
82    }
83
84    fn is_dynamic_fee(&self) -> bool {
85        delegate!(self => tx.is_dynamic_fee())
86    }
87
88    fn kind(&self) -> TxKind {
89        delegate!(self => tx.kind())
90    }
91
92    fn is_create(&self) -> bool {
93        match self {
94            Self::BuiltIn(tx) => tx.is_create(),
95            Self::Other(_tx) => false,
96        }
97    }
98
99    fn value(&self) -> U256 {
100        delegate!(self => tx.value())
101    }
102
103    fn input(&self) -> &Bytes {
104        delegate!(self => tx.input())
105    }
106
107    fn access_list(&self) -> Option<&AccessList> {
108        delegate!(self => tx.access_list())
109    }
110
111    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
112        delegate!(self => tx.blob_versioned_hashes())
113    }
114
115    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
116        delegate!(self => tx.authorization_list())
117    }
118}
119
120impl<B, T> IsTyped2718 for Extended<B, T>
121where
122    B: IsTyped2718,
123    T: IsTyped2718,
124{
125    fn is_type(type_id: u8) -> bool {
126        B::is_type(type_id) || T::is_type(type_id)
127    }
128}
129
130impl<B, T> InMemorySize for Extended<B, T>
131where
132    B: InMemorySize,
133    T: InMemorySize,
134{
135    fn size(&self) -> usize {
136        delegate!(self => tx.size())
137    }
138}
139
140impl<B, T> SignerRecoverable for Extended<B, T>
141where
142    B: SignedTransaction + IsTyped2718,
143    T: SignedTransaction,
144{
145    fn recover_signer(&self) -> Result<Address, RecoveryError> {
146        delegate!(self => tx.recover_signer())
147    }
148
149    fn recover_signer_unchecked(&self) -> Result<Address, RecoveryError> {
150        delegate!(self => tx.recover_signer_unchecked())
151    }
152}
153
154impl<B, T> SignedTransaction for Extended<B, T>
155where
156    B: SignedTransaction + IsTyped2718,
157    T: SignedTransaction,
158{
159    fn tx_hash(&self) -> &TxHash {
160        match self {
161            Self::BuiltIn(tx) => tx.tx_hash(),
162            Self::Other(tx) => tx.tx_hash(),
163        }
164    }
165
166    fn recover_signer_unchecked_with_buf(
167        &self,
168        buf: &mut Vec<u8>,
169    ) -> Result<Address, RecoveryError> {
170        delegate!(self => tx.recover_signer_unchecked_with_buf(buf))
171    }
172}
173
174impl<B, T> Typed2718 for Extended<B, T>
175where
176    B: Typed2718,
177    T: Typed2718,
178{
179    fn ty(&self) -> u8 {
180        match self {
181            Self::BuiltIn(tx) => tx.ty(),
182            Self::Other(tx) => tx.ty(),
183        }
184    }
185}
186
187impl<B, T> Decodable2718 for Extended<B, T>
188where
189    B: Decodable2718 + IsTyped2718,
190    T: Decodable2718,
191{
192    fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
193        if B::is_type(ty) {
194            let envelope = B::typed_decode(ty, buf)?;
195            Ok(Self::BuiltIn(envelope))
196        } else {
197            let other = T::typed_decode(ty, buf)?;
198            Ok(Self::Other(other))
199        }
200    }
201    fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
202        if buf.is_empty() {
203            return Err(Eip2718Error::RlpError(alloy_rlp::Error::InputTooShort));
204        }
205        B::fallback_decode(buf).map(Self::BuiltIn)
206    }
207}
208
209impl<B, T> Encodable2718 for Extended<B, T>
210where
211    B: Encodable2718,
212    T: Encodable2718,
213{
214    fn encode_2718_len(&self) -> usize {
215        match self {
216            Self::BuiltIn(envelope) => envelope.encode_2718_len(),
217            Self::Other(tx) => tx.encode_2718_len(),
218        }
219    }
220
221    fn encode_2718(&self, out: &mut dyn BufMut) {
222        match self {
223            Self::BuiltIn(envelope) => envelope.encode_2718(out),
224            Self::Other(tx) => tx.encode_2718(out),
225        }
226    }
227}
228
229impl<B, T> Encodable for Extended<B, T>
230where
231    B: Encodable,
232    T: Encodable,
233{
234    fn encode(&self, out: &mut dyn BufMut) {
235        match self {
236            Self::BuiltIn(envelope) => envelope.encode(out),
237            Self::Other(tx) => tx.encode(out),
238        }
239    }
240
241    fn length(&self) -> usize {
242        match self {
243            Self::BuiltIn(envelope) => envelope.length(),
244            Self::Other(tx) => tx.length(),
245        }
246    }
247}
248
249impl<B, T> Decodable for Extended<B, T>
250where
251    B: Decodable,
252    T: Decodable,
253{
254    fn decode(buf: &mut &[u8]) -> RlpResult<Self> {
255        let original = *buf;
256
257        match B::decode(buf) {
258            Ok(tx) => Ok(Self::BuiltIn(tx)),
259            Err(_) => {
260                *buf = original;
261                T::decode(buf).map(Self::Other)
262            }
263        }
264    }
265}
266
267impl<Eip4844, Tx> From<EthereumTxEnvelope<Eip4844>> for Extended<EthereumTxEnvelope<Eip4844>, Tx> {
268    fn from(value: EthereumTxEnvelope<Eip4844>) -> Self {
269        Self::BuiltIn(value)
270    }
271}
272
273#[cfg(feature = "op")]
274mod op {
275    use crate::Extended;
276    use alloy_consensus::error::ValueError;
277    use alloy_primitives::{Signature, B256};
278    use op_alloy_consensus::{OpPooledTransaction, OpTxEnvelope};
279
280    impl<Tx> TryFrom<Extended<OpTxEnvelope, Tx>> for Extended<OpPooledTransaction, Tx> {
281        type Error = OpTxEnvelope;
282
283        fn try_from(value: Extended<OpTxEnvelope, Tx>) -> Result<Self, Self::Error> {
284            match value {
285                Extended::BuiltIn(tx) => {
286                    let converted_tx: OpPooledTransaction =
287                        tx.clone().try_into().map_err(|_| tx)?;
288                    Ok(Self::BuiltIn(converted_tx))
289                }
290                Extended::Other(tx) => Ok(Self::Other(tx)),
291            }
292        }
293    }
294
295    impl<Tx> From<OpPooledTransaction> for Extended<OpTxEnvelope, Tx> {
296        fn from(tx: OpPooledTransaction) -> Self {
297            Self::BuiltIn(tx.into())
298        }
299    }
300
301    impl<Tx> TryFrom<Extended<OpTxEnvelope, Tx>> for OpPooledTransaction {
302        type Error = ValueError<OpTxEnvelope>;
303
304        fn try_from(_tx: Extended<OpTxEnvelope, Tx>) -> Result<Self, Self::Error> {
305            match _tx {
306                Extended::BuiltIn(inner) => inner.try_into(),
307                Extended::Other(_tx) => Err(ValueError::new(
308                    OpTxEnvelope::Legacy(alloy_consensus::Signed::new_unchecked(
309                        alloy_consensus::TxLegacy::default(),
310                        Signature::decode_rlp_vrs(&mut &[0u8; 65][..], |_| Ok(false)).unwrap(),
311                        B256::default(),
312                    )),
313                    "Cannot convert custom transaction to OpPooledTransaction",
314                )),
315            }
316        }
317    }
318
319    impl<Tx> From<OpTxEnvelope> for Extended<OpTxEnvelope, Tx> {
320        fn from(value: OpTxEnvelope) -> Self {
321            Self::BuiltIn(value)
322        }
323    }
324}
325
326#[cfg(feature = "serde-bincode-compat")]
327mod serde_bincode_compat {
328    use super::*;
329    use crate::serde_bincode_compat::SerdeBincodeCompat;
330
331    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
332    #[derive(Debug)]
333    pub enum ExtendedTxEnvelopeRepr<'a, B: SerdeBincodeCompat, T: SerdeBincodeCompat> {
334        BuiltIn(B::BincodeRepr<'a>),
335        Other(T::BincodeRepr<'a>),
336    }
337
338    impl<B, T> SerdeBincodeCompat for Extended<B, T>
339    where
340        B: SerdeBincodeCompat + core::fmt::Debug,
341        T: SerdeBincodeCompat + core::fmt::Debug,
342    {
343        type BincodeRepr<'a> = ExtendedTxEnvelopeRepr<'a, B, T>;
344
345        fn as_repr(&self) -> Self::BincodeRepr<'_> {
346            match self {
347                Self::BuiltIn(tx) => ExtendedTxEnvelopeRepr::BuiltIn(tx.as_repr()),
348                Self::Other(tx) => ExtendedTxEnvelopeRepr::Other(tx.as_repr()),
349            }
350        }
351
352        fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
353            match repr {
354                ExtendedTxEnvelopeRepr::BuiltIn(tx_repr) => Self::BuiltIn(B::from_repr(tx_repr)),
355                ExtendedTxEnvelopeRepr::Other(tx_repr) => Self::Other(T::from_repr(tx_repr)),
356            }
357        }
358    }
359}
360
361#[cfg(feature = "reth-codec")]
362use alloy_primitives::bytes::Buf;
363
364#[cfg(feature = "reth-codec")]
365impl<B, T> reth_codecs::Compact for Extended<B, T>
366where
367    B: Transaction + IsTyped2718 + reth_codecs::Compact,
368    T: Transaction + reth_codecs::Compact,
369{
370    fn to_compact<Buf>(&self, buf: &mut Buf) -> usize
371    where
372        Buf: alloy_rlp::bytes::BufMut + AsMut<[u8]>,
373    {
374        buf.put_u8(self.ty());
375        match self {
376            Self::BuiltIn(tx) => tx.to_compact(buf),
377            Self::Other(tx) => tx.to_compact(buf),
378        }
379    }
380
381    fn from_compact(mut buf: &[u8], len: usize) -> (Self, &[u8]) {
382        let type_byte = buf.get_u8();
383
384        if <B as IsTyped2718>::is_type(type_byte) {
385            let (tx, remaining) = B::from_compact(buf, len);
386            return (Self::BuiltIn(tx), remaining);
387        }
388
389        let (tx, remaining) = T::from_compact(buf, len);
390        (Self::Other(tx), remaining)
391    }
392}