alloy_consensus/transaction/
mod.rs

1//! Transaction types.
2
3use crate::Signed;
4use alloc::vec::Vec;
5use alloy_eips::{eip2930::AccessList, eip7702::SignedAuthorization};
6use alloy_primitives::{keccak256, Address, Bytes, ChainId, Selector, TxKind, B256, U256};
7use core::{any, fmt};
8
9mod eip1559;
10pub use eip1559::TxEip1559;
11
12mod eip2930;
13pub use eip2930::TxEip2930;
14
15mod eip7702;
16pub use eip7702::TxEip7702;
17
18/// [EIP-4844] constants, helpers, and types.
19pub mod eip4844;
20pub mod pooled;
21pub use pooled::PooledTransaction;
22
23use alloy_eips::eip4844::DATA_GAS_PER_BLOB;
24pub use alloy_eips::eip4844::{
25    builder::{SidecarBuilder, SidecarCoder, SimpleCoder},
26    utils as eip4844_utils, Blob, BlobTransactionSidecar, Bytes48,
27};
28#[cfg(feature = "kzg")]
29pub use eip4844::BlobTransactionValidationError;
30pub use eip4844::{TxEip4844, TxEip4844Variant, TxEip4844WithSidecar};
31
32/// Re-export for convenience
33pub use either::Either;
34
35mod envelope;
36pub use envelope::{EthereumTxEnvelope, TxEnvelope, TxType};
37
38mod legacy;
39pub use legacy::{from_eip155_value, to_eip155_value, TxLegacy};
40
41mod rlp;
42#[doc(hidden)]
43pub use rlp::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx, RlpEcdsaTx};
44
45mod typed;
46pub use typed::{EthereumTypedTransaction, TypedTransaction};
47
48mod meta;
49pub use meta::{TransactionInfo, TransactionMeta};
50
51mod recovered;
52pub use recovered::{Recovered, SignerRecoverable};
53
54#[cfg(feature = "serde")]
55pub use legacy::signed_legacy_serde;
56
57/// Bincode-compatible serde implementations for transaction types.
58#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
59pub mod serde_bincode_compat {
60    pub use super::{
61        eip1559::serde_bincode_compat::*, eip2930::serde_bincode_compat::*,
62        eip7702::serde_bincode_compat::*, legacy::serde_bincode_compat::*,
63    };
64}
65
66use alloy_eips::Typed2718;
67
68/// Represents a minimal EVM transaction.
69/// Currently, EIP-1559, EIP-4844, and EIP-7702 support dynamic fees.
70/// We call these transactions "dynamic fee transactions".
71/// We call non dynamic fee transactions(EIP-155, EIP-2930) "legacy fee transactions".
72#[doc(alias = "Tx")]
73#[auto_impl::auto_impl(&, Arc)]
74pub trait Transaction: Typed2718 + fmt::Debug + any::Any + Send + Sync + 'static {
75    /// Get `chain_id`.
76    fn chain_id(&self) -> Option<ChainId>;
77
78    /// Get `nonce`.
79    fn nonce(&self) -> u64;
80
81    /// Get `gas_limit`.
82    fn gas_limit(&self) -> u64;
83
84    /// Get `gas_price`.
85    fn gas_price(&self) -> Option<u128>;
86
87    /// For dynamic fee transactions returns the maximum fee per gas the caller is willing to pay.
88    ///
89    /// For legacy fee transactions this is `gas_price`.
90    ///
91    /// This is also commonly referred to as the "Gas Fee Cap".
92    fn max_fee_per_gas(&self) -> u128;
93
94    /// For dynamic fee transactions returns the Priority fee the caller is paying to the block
95    /// author.
96    ///
97    /// This will return `None` for legacy fee transactions
98    fn max_priority_fee_per_gas(&self) -> Option<u128>;
99
100    /// Max fee per blob gas for EIP-4844 transaction.
101    ///
102    /// Returns `None` for non-eip4844 transactions.
103    ///
104    /// This is also commonly referred to as the "Blob Gas Fee Cap".
105    fn max_fee_per_blob_gas(&self) -> Option<u128>;
106
107    /// Return the max priority fee per gas if the transaction is an dynamic fee transaction, and
108    /// otherwise return the gas price.
109    ///
110    /// # Warning
111    ///
112    /// This is different than the `max_priority_fee_per_gas` method, which returns `None` for
113    /// legacy fee transactions.
114    fn priority_fee_or_price(&self) -> u128;
115
116    /// Returns the effective gas price for the given base fee.
117    ///
118    /// If the transaction is a legacy fee transaction, the gas price is returned.
119    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128;
120
121    /// Returns the effective tip for this transaction.
122    ///
123    /// For dynamic fee transactions: `min(max_fee_per_gas - base_fee, max_priority_fee_per_gas)`.
124    /// For legacy fee transactions: `gas_price - base_fee`.
125    fn effective_tip_per_gas(&self, base_fee: u64) -> Option<u128> {
126        let base_fee = base_fee as u128;
127
128        let max_fee_per_gas = self.max_fee_per_gas();
129
130        // Check if max_fee_per_gas is less than base_fee
131        if max_fee_per_gas < base_fee {
132            return None;
133        }
134
135        // Calculate the difference between max_fee_per_gas and base_fee
136        let fee = max_fee_per_gas - base_fee;
137
138        // Compare the fee with max_priority_fee_per_gas (or gas price for legacy fee transactions)
139        self.max_priority_fee_per_gas()
140            .map_or(Some(fee), |priority_fee| Some(fee.min(priority_fee)))
141    }
142
143    /// Returns `true` if the transaction supports dynamic fees.
144    fn is_dynamic_fee(&self) -> bool;
145
146    /// Returns the transaction kind.
147    fn kind(&self) -> TxKind;
148
149    /// Returns true if the transaction is a contract creation.
150    /// We don't provide a default implementation via `kind` as it copies the 21-byte
151    /// [`TxKind`] for this simple check. A proper implementation shouldn't allocate.
152    fn is_create(&self) -> bool;
153
154    /// Get the transaction's address of the contract that will be called, or the address that will
155    /// receive the transfer.
156    ///
157    /// Returns `None` if this is a `CREATE` transaction.
158    fn to(&self) -> Option<Address> {
159        self.kind().to().copied()
160    }
161
162    /// Get `value`.
163    fn value(&self) -> U256;
164
165    /// Get `data`.
166    fn input(&self) -> &Bytes;
167
168    /// Returns the first 4bytes of the calldata for a function call.
169    ///
170    /// The selector specifies the function to be called.
171    fn function_selector(&self) -> Option<&Selector> {
172        if self.kind().is_call() {
173            self.input().get(..4).and_then(|s| TryFrom::try_from(s).ok())
174        } else {
175            None
176        }
177    }
178
179    /// Returns the EIP-2930 `access_list` for the particular transaction type. Returns `None` for
180    /// older transaction types.
181    fn access_list(&self) -> Option<&AccessList>;
182
183    /// Blob versioned hashes for eip4844 transaction. For previous transaction types this is
184    /// `None`.
185    fn blob_versioned_hashes(&self) -> Option<&[B256]>;
186
187    /// Returns the number of blobs of this transaction.
188    ///
189    /// This is convenience function for `len(blob_versioned_hashes)`.
190    ///
191    /// Returns `None` for non-eip4844 transactions.
192    fn blob_count(&self) -> Option<u64> {
193        self.blob_versioned_hashes().map(|h| h.len() as u64)
194    }
195
196    /// Returns the total gas for all blobs in this transaction.
197    ///
198    /// Returns `None` for non-eip4844 transactions.
199    #[inline]
200    fn blob_gas_used(&self) -> Option<u64> {
201        // SAFETY: we don't expect u64::MAX / DATA_GAS_PER_BLOB hashes in a single transaction
202        self.blob_count().map(|blobs| blobs * DATA_GAS_PER_BLOB)
203    }
204
205    /// Returns the [`SignedAuthorization`] list of the transaction.
206    ///
207    /// Returns `None` if this transaction is not EIP-7702.
208    fn authorization_list(&self) -> Option<&[SignedAuthorization]>;
209
210    /// Returns the number of blobs of [`SignedAuthorization`] in this transactions
211    ///
212    /// This is convenience function for `len(authorization_list)`.
213    ///
214    /// Returns `None` for non-eip7702 transactions.
215    fn authorization_count(&self) -> Option<u64> {
216        self.authorization_list().map(|auths| auths.len() as u64)
217    }
218}
219
220/// A signable transaction.
221///
222/// A transaction can have multiple signature types. This is usually
223/// [`alloy_primitives::PrimitiveSignature`], however, it may be different for future EIP-2718
224/// transaction types, or in other networks. For example, in Optimism, the deposit transaction
225/// signature is the unit type `()`.
226#[doc(alias = "SignableTx", alias = "TxSignable")]
227pub trait SignableTransaction<Signature>: Transaction {
228    /// Sets `chain_id`.
229    ///
230    /// Prefer [`set_chain_id_checked`](Self::set_chain_id_checked).
231    fn set_chain_id(&mut self, chain_id: ChainId);
232
233    /// Set `chain_id` if it is not already set. Checks that the provided `chain_id` matches the
234    /// existing `chain_id` if it is already set, returning `false` if they do not match.
235    fn set_chain_id_checked(&mut self, chain_id: ChainId) -> bool {
236        match self.chain_id() {
237            Some(tx_chain_id) => {
238                if tx_chain_id != chain_id {
239                    return false;
240                }
241                self.set_chain_id(chain_id);
242            }
243            None => {
244                self.set_chain_id(chain_id);
245            }
246        }
247        true
248    }
249
250    /// RLP-encodes the transaction for signing.
251    fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut);
252
253    /// Outputs the length of the signature RLP encoding for the transaction.
254    fn payload_len_for_signature(&self) -> usize;
255
256    /// RLP-encodes the transaction for signing it. Used to calculate `signature_hash`.
257    ///
258    /// See [`SignableTransaction::encode_for_signing`].
259    fn encoded_for_signing(&self) -> Vec<u8> {
260        let mut buf = Vec::with_capacity(self.payload_len_for_signature());
261        self.encode_for_signing(&mut buf);
262        buf
263    }
264
265    /// Calculate the signing hash for the transaction.
266    fn signature_hash(&self) -> B256 {
267        keccak256(self.encoded_for_signing())
268    }
269
270    /// Convert to a [`Signed`] object.
271    fn into_signed(self, signature: Signature) -> Signed<Self, Signature>
272    where
273        Self: Sized,
274    {
275        Signed::new_unhashed(self, signature)
276    }
277}
278
279// TODO: Remove in favor of dyn trait upcasting (TBD, see https://github.com/rust-lang/rust/issues/65991#issuecomment-1903120162)
280#[doc(hidden)]
281impl<S: 'static> dyn SignableTransaction<S> {
282    pub fn __downcast_ref<T: any::Any>(&self) -> Option<&T> {
283        if any::Any::type_id(self) == any::TypeId::of::<T>() {
284            unsafe { Some(&*(self as *const _ as *const T)) }
285        } else {
286            None
287        }
288    }
289}
290
291#[cfg(feature = "serde")]
292impl<T: Transaction> Transaction for alloy_serde::WithOtherFields<T> {
293    #[inline]
294    fn chain_id(&self) -> Option<ChainId> {
295        self.inner.chain_id()
296    }
297
298    #[inline]
299    fn nonce(&self) -> u64 {
300        self.inner.nonce()
301    }
302
303    #[inline]
304    fn gas_limit(&self) -> u64 {
305        self.inner.gas_limit()
306    }
307
308    #[inline]
309    fn gas_price(&self) -> Option<u128> {
310        self.inner.gas_price()
311    }
312
313    #[inline]
314    fn max_fee_per_gas(&self) -> u128 {
315        self.inner.max_fee_per_gas()
316    }
317
318    #[inline]
319    fn max_priority_fee_per_gas(&self) -> Option<u128> {
320        self.inner.max_priority_fee_per_gas()
321    }
322
323    #[inline]
324    fn max_fee_per_blob_gas(&self) -> Option<u128> {
325        self.inner.max_fee_per_blob_gas()
326    }
327
328    #[inline]
329    fn priority_fee_or_price(&self) -> u128 {
330        self.inner.priority_fee_or_price()
331    }
332
333    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
334        self.inner.effective_gas_price(base_fee)
335    }
336
337    #[inline]
338    fn is_dynamic_fee(&self) -> bool {
339        self.inner.is_dynamic_fee()
340    }
341
342    #[inline]
343    fn kind(&self) -> TxKind {
344        self.inner.kind()
345    }
346
347    #[inline]
348    fn is_create(&self) -> bool {
349        self.inner.is_create()
350    }
351
352    #[inline]
353    fn value(&self) -> U256 {
354        self.inner.value()
355    }
356
357    #[inline]
358    fn input(&self) -> &Bytes {
359        self.inner.input()
360    }
361
362    #[inline]
363    fn access_list(&self) -> Option<&AccessList> {
364        self.inner.access_list()
365    }
366
367    #[inline]
368    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
369        self.inner.blob_versioned_hashes()
370    }
371
372    #[inline]
373    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
374        self.inner.authorization_list()
375    }
376}
377
378impl<L, R> Transaction for either::Either<L, R>
379where
380    L: Transaction,
381    R: Transaction,
382{
383    fn chain_id(&self) -> Option<ChainId> {
384        match self {
385            Self::Left(tx) => tx.chain_id(),
386            Self::Right(tx) => tx.chain_id(),
387        }
388    }
389
390    fn nonce(&self) -> u64 {
391        match self {
392            Self::Left(tx) => tx.nonce(),
393            Self::Right(tx) => tx.nonce(),
394        }
395    }
396
397    fn gas_limit(&self) -> u64 {
398        match self {
399            Self::Left(tx) => tx.gas_limit(),
400            Self::Right(tx) => tx.gas_limit(),
401        }
402    }
403
404    fn gas_price(&self) -> Option<u128> {
405        match self {
406            Self::Left(tx) => tx.gas_price(),
407            Self::Right(tx) => tx.gas_price(),
408        }
409    }
410
411    fn max_fee_per_gas(&self) -> u128 {
412        match self {
413            Self::Left(tx) => tx.max_fee_per_gas(),
414            Self::Right(tx) => tx.max_fee_per_gas(),
415        }
416    }
417
418    fn max_priority_fee_per_gas(&self) -> Option<u128> {
419        match self {
420            Self::Left(tx) => tx.max_priority_fee_per_gas(),
421            Self::Right(tx) => tx.max_priority_fee_per_gas(),
422        }
423    }
424
425    fn max_fee_per_blob_gas(&self) -> Option<u128> {
426        match self {
427            Self::Left(tx) => tx.max_fee_per_blob_gas(),
428            Self::Right(tx) => tx.max_fee_per_blob_gas(),
429        }
430    }
431
432    fn priority_fee_or_price(&self) -> u128 {
433        match self {
434            Self::Left(tx) => tx.priority_fee_or_price(),
435            Self::Right(tx) => tx.priority_fee_or_price(),
436        }
437    }
438
439    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
440        match self {
441            Self::Left(tx) => tx.effective_gas_price(base_fee),
442            Self::Right(tx) => tx.effective_gas_price(base_fee),
443        }
444    }
445
446    fn effective_tip_per_gas(&self, base_fee: u64) -> Option<u128> {
447        match self {
448            Self::Left(tx) => tx.effective_tip_per_gas(base_fee),
449            Self::Right(tx) => tx.effective_tip_per_gas(base_fee),
450        }
451    }
452
453    fn is_dynamic_fee(&self) -> bool {
454        match self {
455            Self::Left(tx) => tx.is_dynamic_fee(),
456            Self::Right(tx) => tx.is_dynamic_fee(),
457        }
458    }
459
460    fn kind(&self) -> TxKind {
461        match self {
462            Self::Left(tx) => tx.kind(),
463            Self::Right(tx) => tx.kind(),
464        }
465    }
466
467    fn is_create(&self) -> bool {
468        match self {
469            Self::Left(tx) => tx.is_create(),
470            Self::Right(tx) => tx.is_create(),
471        }
472    }
473
474    fn to(&self) -> Option<Address> {
475        match self {
476            Self::Left(tx) => tx.to(),
477            Self::Right(tx) => tx.to(),
478        }
479    }
480
481    fn value(&self) -> U256 {
482        match self {
483            Self::Left(tx) => tx.value(),
484            Self::Right(tx) => tx.value(),
485        }
486    }
487
488    fn input(&self) -> &Bytes {
489        match self {
490            Self::Left(tx) => tx.input(),
491            Self::Right(tx) => tx.input(),
492        }
493    }
494
495    fn function_selector(&self) -> Option<&Selector> {
496        match self {
497            Self::Left(tx) => tx.function_selector(),
498            Self::Right(tx) => tx.function_selector(),
499        }
500    }
501
502    fn access_list(&self) -> Option<&AccessList> {
503        match self {
504            Self::Left(tx) => tx.access_list(),
505            Self::Right(tx) => tx.access_list(),
506        }
507    }
508
509    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
510        match self {
511            Self::Left(tx) => tx.blob_versioned_hashes(),
512            Self::Right(tx) => tx.blob_versioned_hashes(),
513        }
514    }
515
516    fn blob_count(&self) -> Option<u64> {
517        match self {
518            Self::Left(tx) => tx.blob_count(),
519            Self::Right(tx) => tx.blob_count(),
520        }
521    }
522
523    fn blob_gas_used(&self) -> Option<u64> {
524        match self {
525            Self::Left(tx) => tx.blob_gas_used(),
526            Self::Right(tx) => tx.blob_gas_used(),
527        }
528    }
529
530    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
531        match self {
532            Self::Left(tx) => tx.authorization_list(),
533            Self::Right(tx) => tx.authorization_list(),
534        }
535    }
536
537    fn authorization_count(&self) -> Option<u64> {
538        match self {
539            Self::Left(tx) => tx.authorization_count(),
540            Self::Right(tx) => tx.authorization_count(),
541        }
542    }
543}