alloy_consensus/
extended.rs

1//! Extended transaction types
2
3use crate::{EthereumTxEnvelope, Transaction};
4use alloy_eips::{
5    eip2718::{Eip2718Error, Eip2718Result, IsTyped2718},
6    eip2930::AccessList,
7    eip7702::SignedAuthorization,
8    Decodable2718, Encodable2718, Typed2718,
9};
10use alloy_primitives::{Bytes, ChainId, TxKind, B256, U256};
11use alloy_rlp::{BufMut, Decodable, Encodable, Result as RlpResult};
12
13macro_rules! delegate {
14    ($self:expr => $tx:ident.$method:ident($($arg:expr),*)) => {
15        match $self {
16            Self::BuiltIn($tx) => $tx.$method($($arg),*),
17            Self::Other($tx) => $tx.$method($($arg),*),
18        }
19    };
20}
21
22/// An enum that combines two different transaction types.
23///
24/// This is intended to be used to extend existing presets, for example the ethereum or opstack
25/// transaction types and receipts
26///
27/// Note: The [`Extended::Other`] variants must not overlap with the builtin one, transaction
28/// types must be unique. For example if [`Extended::BuiltIn`] contains an `EIP-1559` type variant,
29/// [`Extended::Other`] must not include that type.
30#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
31#[derive(Debug, Clone, Hash, Eq, PartialEq)]
32pub enum Extended<BuiltIn, Other> {
33    /// The builtin transaction type.
34    BuiltIn(BuiltIn),
35    /// The other transaction type.
36    Other(Other),
37}
38
39impl<B, T> Extended<B, T> {
40    /// Converts only the built-in transaction type using `From`, leaving the other type unchanged.
41    pub fn convert_builtin<U>(self) -> Extended<U, T>
42    where
43        U: From<B>,
44    {
45        self.map_builtin(U::from)
46    }
47
48    /// Attempts to convert only the built-in transaction type using `TryFrom`, leaving the other
49    /// type unchanged.
50    pub fn try_convert_builtin<U>(self) -> Result<Extended<U, T>, U::Error>
51    where
52        U: TryFrom<B>,
53    {
54        self.try_map_builtin(U::try_from)
55    }
56
57    /// Converts only the other transaction type using `From`, leaving the built-in type unchanged.
58    pub fn convert_other<U>(self) -> Extended<B, U>
59    where
60        U: From<T>,
61    {
62        self.map_other(U::from)
63    }
64
65    /// Attempts to convert only the other transaction type using `TryFrom`, leaving the built-in
66    /// type unchanged.
67    pub fn try_convert_other<U>(self) -> Result<Extended<B, U>, U::Error>
68    where
69        U: TryFrom<T>,
70    {
71        self.try_map_other(U::try_from)
72    }
73
74    /// Maps only the built-in transaction type using the provided function, leaving the other type
75    /// unchanged.
76    pub fn map_builtin<U>(self, f: impl FnOnce(B) -> U) -> Extended<U, T> {
77        match self {
78            Self::BuiltIn(tx) => Extended::BuiltIn(f(tx)),
79            Self::Other(tx) => Extended::Other(tx),
80        }
81    }
82
83    /// Attempts to map only the built-in transaction type using the provided fallible function,
84    /// leaving the other type unchanged. Returns an error if the mapping of the built-in type
85    /// fails.
86    pub fn try_map_builtin<U, E>(
87        self,
88        f: impl FnOnce(B) -> Result<U, E>,
89    ) -> Result<Extended<U, T>, E> {
90        match self {
91            Self::BuiltIn(tx) => f(tx).map(Extended::BuiltIn),
92            Self::Other(tx) => Ok(Extended::Other(tx)),
93        }
94    }
95
96    /// Maps only the other transaction type using the provided function, leaving the built-in type
97    /// unchanged.
98    pub fn map_other<U>(self, f: impl FnOnce(T) -> U) -> Extended<B, U> {
99        match self {
100            Self::BuiltIn(tx) => Extended::BuiltIn(tx),
101            Self::Other(tx) => Extended::Other(f(tx)),
102        }
103    }
104
105    /// Attempts to map only the other transaction type using the provided fallible function,
106    /// leaving the built-in type unchanged. Returns an error if the mapping of the other type
107    /// fails.
108    pub fn try_map_other<U, F>(
109        self,
110        f: impl FnOnce(T) -> Result<U, F>,
111    ) -> Result<Extended<B, U>, F> {
112        match self {
113            Self::BuiltIn(tx) => Ok(Extended::BuiltIn(tx)),
114            Self::Other(tx) => f(tx).map(Extended::Other),
115        }
116    }
117}
118
119impl<B, T> Transaction for Extended<B, T>
120where
121    B: Transaction,
122    T: Transaction,
123{
124    fn chain_id(&self) -> Option<ChainId> {
125        delegate!(self => tx.chain_id())
126    }
127
128    fn nonce(&self) -> u64 {
129        delegate!(self => tx.nonce())
130    }
131
132    fn gas_limit(&self) -> u64 {
133        delegate!(self => tx.gas_limit())
134    }
135
136    fn gas_price(&self) -> Option<u128> {
137        delegate!(self => tx.gas_price())
138    }
139
140    fn max_fee_per_gas(&self) -> u128 {
141        delegate!(self => tx.max_fee_per_gas())
142    }
143
144    fn max_priority_fee_per_gas(&self) -> Option<u128> {
145        delegate!(self => tx.max_priority_fee_per_gas())
146    }
147
148    fn max_fee_per_blob_gas(&self) -> Option<u128> {
149        delegate!(self => tx.max_fee_per_blob_gas())
150    }
151
152    fn priority_fee_or_price(&self) -> u128 {
153        delegate!(self => tx.priority_fee_or_price())
154    }
155
156    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
157        delegate!(self => tx.effective_gas_price(base_fee))
158    }
159
160    fn is_dynamic_fee(&self) -> bool {
161        delegate!(self => tx.is_dynamic_fee())
162    }
163
164    fn kind(&self) -> TxKind {
165        delegate!(self => tx.kind())
166    }
167
168    fn is_create(&self) -> bool {
169        match self {
170            Self::BuiltIn(tx) => tx.is_create(),
171            Self::Other(_tx) => false,
172        }
173    }
174
175    fn value(&self) -> U256 {
176        delegate!(self => tx.value())
177    }
178
179    fn input(&self) -> &Bytes {
180        delegate!(self => tx.input())
181    }
182
183    fn access_list(&self) -> Option<&AccessList> {
184        delegate!(self => tx.access_list())
185    }
186
187    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
188        delegate!(self => tx.blob_versioned_hashes())
189    }
190
191    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
192        delegate!(self => tx.authorization_list())
193    }
194}
195
196impl<B, T> IsTyped2718 for Extended<B, T>
197where
198    B: IsTyped2718,
199    T: IsTyped2718,
200{
201    fn is_type(type_id: u8) -> bool {
202        B::is_type(type_id) || T::is_type(type_id)
203    }
204}
205
206impl<B, T> Typed2718 for Extended<B, T>
207where
208    B: Typed2718,
209    T: Typed2718,
210{
211    fn ty(&self) -> u8 {
212        match self {
213            Self::BuiltIn(tx) => tx.ty(),
214            Self::Other(tx) => tx.ty(),
215        }
216    }
217}
218
219impl<B, T> Decodable2718 for Extended<B, T>
220where
221    B: Decodable2718 + IsTyped2718,
222    T: Decodable2718,
223{
224    fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
225        if B::is_type(ty) {
226            let envelope = B::typed_decode(ty, buf)?;
227            Ok(Self::BuiltIn(envelope))
228        } else {
229            let other = T::typed_decode(ty, buf)?;
230            Ok(Self::Other(other))
231        }
232    }
233    fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
234        if buf.is_empty() {
235            return Err(Eip2718Error::RlpError(alloy_rlp::Error::InputTooShort));
236        }
237        B::fallback_decode(buf).map(Self::BuiltIn)
238    }
239}
240
241impl<B, T> Encodable2718 for Extended<B, T>
242where
243    B: Encodable2718,
244    T: Encodable2718,
245{
246    fn encode_2718_len(&self) -> usize {
247        match self {
248            Self::BuiltIn(envelope) => envelope.encode_2718_len(),
249            Self::Other(tx) => tx.encode_2718_len(),
250        }
251    }
252
253    fn encode_2718(&self, out: &mut dyn BufMut) {
254        match self {
255            Self::BuiltIn(envelope) => envelope.encode_2718(out),
256            Self::Other(tx) => tx.encode_2718(out),
257        }
258    }
259}
260
261impl<B, T> Encodable for Extended<B, T>
262where
263    B: Encodable,
264    T: Encodable,
265{
266    fn encode(&self, out: &mut dyn BufMut) {
267        match self {
268            Self::BuiltIn(envelope) => envelope.encode(out),
269            Self::Other(tx) => tx.encode(out),
270        }
271    }
272
273    fn length(&self) -> usize {
274        match self {
275            Self::BuiltIn(envelope) => envelope.length(),
276            Self::Other(tx) => tx.length(),
277        }
278    }
279}
280
281impl<B, T> Decodable for Extended<B, T>
282where
283    B: Decodable,
284    T: Decodable,
285{
286    fn decode(buf: &mut &[u8]) -> RlpResult<Self> {
287        let original = *buf;
288
289        B::decode(buf).map_or_else(
290            |_| {
291                *buf = original;
292                T::decode(buf).map(Self::Other)
293            },
294            |tx| Ok(Self::BuiltIn(tx)),
295        )
296    }
297}
298
299impl<Eip4844, Tx> From<EthereumTxEnvelope<Eip4844>> for Extended<EthereumTxEnvelope<Eip4844>, Tx> {
300    fn from(value: EthereumTxEnvelope<Eip4844>) -> Self {
301        Self::BuiltIn(value)
302    }
303}