reth_primitives_traits/
extended.rs

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