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 opstack
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    fn recover_unchecked_with_buf(&self, buf: &mut Vec<u8>) -> Result<Address, RecoveryError> {
154        delegate!(self => tx.recover_unchecked_with_buf(buf))
155    }
156}
157
158impl<B, T> SignedTransaction for Extended<B, T>
159where
160    B: SignedTransaction + IsTyped2718,
161    T: SignedTransaction,
162{
163    fn tx_hash(&self) -> &TxHash {
164        match self {
165            Self::BuiltIn(tx) => tx.tx_hash(),
166            Self::Other(tx) => tx.tx_hash(),
167        }
168    }
169}
170
171impl<B, T> Typed2718 for Extended<B, T>
172where
173    B: Typed2718,
174    T: Typed2718,
175{
176    fn ty(&self) -> u8 {
177        match self {
178            Self::BuiltIn(tx) => tx.ty(),
179            Self::Other(tx) => tx.ty(),
180        }
181    }
182}
183
184impl<B, T> Decodable2718 for Extended<B, T>
185where
186    B: Decodable2718 + IsTyped2718,
187    T: Decodable2718,
188{
189    fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
190        if B::is_type(ty) {
191            let envelope = B::typed_decode(ty, buf)?;
192            Ok(Self::BuiltIn(envelope))
193        } else {
194            let other = T::typed_decode(ty, buf)?;
195            Ok(Self::Other(other))
196        }
197    }
198    fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
199        if buf.is_empty() {
200            return Err(Eip2718Error::RlpError(alloy_rlp::Error::InputTooShort));
201        }
202        B::fallback_decode(buf).map(Self::BuiltIn)
203    }
204}
205
206impl<B, T> Encodable2718 for Extended<B, T>
207where
208    B: Encodable2718,
209    T: Encodable2718,
210{
211    fn encode_2718_len(&self) -> usize {
212        match self {
213            Self::BuiltIn(envelope) => envelope.encode_2718_len(),
214            Self::Other(tx) => tx.encode_2718_len(),
215        }
216    }
217
218    fn encode_2718(&self, out: &mut dyn BufMut) {
219        match self {
220            Self::BuiltIn(envelope) => envelope.encode_2718(out),
221            Self::Other(tx) => tx.encode_2718(out),
222        }
223    }
224}
225
226impl<B, T> Encodable for Extended<B, T>
227where
228    B: Encodable,
229    T: Encodable,
230{
231    fn encode(&self, out: &mut dyn BufMut) {
232        match self {
233            Self::BuiltIn(envelope) => envelope.encode(out),
234            Self::Other(tx) => tx.encode(out),
235        }
236    }
237
238    fn length(&self) -> usize {
239        match self {
240            Self::BuiltIn(envelope) => envelope.length(),
241            Self::Other(tx) => tx.length(),
242        }
243    }
244}
245
246impl<B, T> Decodable for Extended<B, T>
247where
248    B: Decodable,
249    T: Decodable,
250{
251    fn decode(buf: &mut &[u8]) -> RlpResult<Self> {
252        let original = *buf;
253
254        match B::decode(buf) {
255            Ok(tx) => Ok(Self::BuiltIn(tx)),
256            Err(_) => {
257                *buf = original;
258                T::decode(buf).map(Self::Other)
259            }
260        }
261    }
262}
263
264impl<Eip4844, Tx> From<EthereumTxEnvelope<Eip4844>> for Extended<EthereumTxEnvelope<Eip4844>, Tx> {
265    fn from(value: EthereumTxEnvelope<Eip4844>) -> Self {
266        Self::BuiltIn(value)
267    }
268}
269
270#[cfg(feature = "op")]
271mod op {
272    use crate::Extended;
273    use alloy_consensus::error::ValueError;
274    use alloy_primitives::{Sealed, Signature, B256};
275    use op_alloy_consensus::{OpPooledTransaction, OpTransaction, OpTxEnvelope, TxDeposit};
276
277    impl<B, T> OpTransaction for Extended<B, T>
278    where
279        B: OpTransaction,
280        T: OpTransaction,
281    {
282        fn is_deposit(&self) -> bool {
283            match self {
284                Self::BuiltIn(b) => b.is_deposit(),
285                Self::Other(t) => t.is_deposit(),
286            }
287        }
288
289        fn as_deposit(&self) -> Option<&Sealed<TxDeposit>> {
290            match self {
291                Self::BuiltIn(b) => b.as_deposit(),
292                Self::Other(t) => t.as_deposit(),
293            }
294        }
295    }
296
297    impl<Tx> TryFrom<Extended<OpTxEnvelope, Tx>> for Extended<OpPooledTransaction, Tx> {
298        type Error = <OpPooledTransaction as TryFrom<OpTxEnvelope>>::Error;
299
300        fn try_from(value: Extended<OpTxEnvelope, Tx>) -> Result<Self, Self::Error> {
301            match value {
302                Extended::BuiltIn(tx) => {
303                    let converted_tx: OpPooledTransaction = tx.try_into()?;
304                    Ok(Self::BuiltIn(converted_tx))
305                }
306                Extended::Other(tx) => Ok(Self::Other(tx)),
307            }
308        }
309    }
310
311    impl<Tx> From<OpPooledTransaction> for Extended<OpTxEnvelope, Tx> {
312        fn from(tx: OpPooledTransaction) -> Self {
313            Self::BuiltIn(tx.into())
314        }
315    }
316
317    impl<Tx> From<Extended<OpPooledTransaction, Tx>> for Extended<OpTxEnvelope, Tx> {
318        fn from(tx: Extended<OpPooledTransaction, Tx>) -> Self {
319            match tx {
320                Extended::BuiltIn(tx) => Self::BuiltIn(tx.into()),
321                Extended::Other(tx) => Self::Other(tx),
322            }
323        }
324    }
325
326    impl<Tx> TryFrom<Extended<OpTxEnvelope, Tx>> for OpPooledTransaction {
327        type Error = ValueError<OpTxEnvelope>;
328
329        fn try_from(_tx: Extended<OpTxEnvelope, Tx>) -> Result<Self, Self::Error> {
330            match _tx {
331                Extended::BuiltIn(inner) => inner.try_into(),
332                Extended::Other(_tx) => Err(ValueError::new(
333                    OpTxEnvelope::Legacy(alloy_consensus::Signed::new_unchecked(
334                        alloy_consensus::TxLegacy::default(),
335                        Signature::decode_rlp_vrs(&mut &[0u8; 65][..], |_| Ok(false)).unwrap(),
336                        B256::default(),
337                    )),
338                    "Cannot convert custom transaction to OpPooledTransaction",
339                )),
340            }
341        }
342    }
343
344    impl<Tx> From<OpTxEnvelope> for Extended<OpTxEnvelope, Tx> {
345        fn from(value: OpTxEnvelope) -> Self {
346            Self::BuiltIn(value)
347        }
348    }
349}
350
351#[cfg(feature = "serde-bincode-compat")]
352mod serde_bincode_compat {
353    use super::*;
354    use crate::serde_bincode_compat::SerdeBincodeCompat;
355
356    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
357    #[derive(Debug)]
358    pub enum ExtendedRepr<'a, B: SerdeBincodeCompat, T: SerdeBincodeCompat> {
359        BuiltIn(B::BincodeRepr<'a>),
360        Other(T::BincodeRepr<'a>),
361    }
362
363    impl<B, T> SerdeBincodeCompat for Extended<B, T>
364    where
365        B: SerdeBincodeCompat + core::fmt::Debug,
366        T: SerdeBincodeCompat + core::fmt::Debug,
367    {
368        type BincodeRepr<'a> = ExtendedRepr<'a, B, T>;
369
370        fn as_repr(&self) -> Self::BincodeRepr<'_> {
371            match self {
372                Self::BuiltIn(tx) => ExtendedRepr::BuiltIn(tx.as_repr()),
373                Self::Other(tx) => ExtendedRepr::Other(tx.as_repr()),
374            }
375        }
376
377        fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
378            match repr {
379                ExtendedRepr::BuiltIn(tx_repr) => Self::BuiltIn(B::from_repr(tx_repr)),
380                ExtendedRepr::Other(tx_repr) => Self::Other(T::from_repr(tx_repr)),
381            }
382        }
383    }
384}
385
386#[cfg(feature = "reth-codec")]
387use alloy_primitives::bytes::Buf;
388
389#[cfg(feature = "reth-codec")]
390impl<B, T> reth_codecs::Compact for Extended<B, T>
391where
392    B: Transaction + IsTyped2718 + reth_codecs::Compact,
393    T: Transaction + reth_codecs::Compact,
394{
395    fn to_compact<Buf>(&self, buf: &mut Buf) -> usize
396    where
397        Buf: alloy_rlp::bytes::BufMut + AsMut<[u8]>,
398    {
399        buf.put_u8(self.ty());
400        match self {
401            Self::BuiltIn(tx) => tx.to_compact(buf),
402            Self::Other(tx) => tx.to_compact(buf),
403        }
404    }
405
406    fn from_compact(mut buf: &[u8], len: usize) -> (Self, &[u8]) {
407        let type_byte = buf.get_u8();
408
409        if <B as IsTyped2718>::is_type(type_byte) {
410            let (tx, remaining) = B::from_compact(buf, len);
411            return (Self::BuiltIn(tx), remaining);
412        }
413
414        let (tx, remaining) = T::from_compact(buf, len);
415        (Self::Other(tx), remaining)
416    }
417}