1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381
//! Transaction types.
use crate::Signed;
use alloc::vec::Vec;
use alloy_eips::{eip2930::AccessList, eip7702::SignedAuthorization};
use alloy_primitives::{keccak256, Address, Bytes, ChainId, TxKind, B256, U256};
use core::{any, fmt};
mod eip1559;
pub use eip1559::TxEip1559;
mod eip2930;
pub use eip2930::TxEip2930;
mod eip7702;
pub use eip7702::TxEip7702;
/// [EIP-4844] constants, helpers, and types.
pub mod eip4844;
pub mod pooled;
pub use pooled::PooledTransaction;
use alloy_eips::eip4844::DATA_GAS_PER_BLOB;
pub use alloy_eips::eip4844::{
builder::{SidecarBuilder, SidecarCoder, SimpleCoder},
utils as eip4844_utils, Blob, BlobTransactionSidecar, Bytes48,
};
#[cfg(feature = "kzg")]
pub use eip4844::BlobTransactionValidationError;
pub use eip4844::{TxEip4844, TxEip4844Variant, TxEip4844WithSidecar};
mod envelope;
pub use envelope::{TxEnvelope, TxType};
mod legacy;
pub use legacy::{from_eip155_value, to_eip155_value, TxLegacy};
mod rlp;
#[doc(hidden)]
pub use rlp::RlpEcdsaTx;
mod typed;
pub use typed::TypedTransaction;
mod meta;
pub use meta::{TransactionInfo, TransactionMeta};
mod recovered;
pub use recovered::{Recovered, SignerRecoverable};
#[cfg(feature = "serde")]
pub use legacy::signed_legacy_serde;
/// Bincode-compatible serde implementations for transaction types.
#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
pub mod serde_bincode_compat {
pub use super::{
eip1559::serde_bincode_compat::*, eip2930::serde_bincode_compat::*,
eip7702::serde_bincode_compat::*, legacy::serde_bincode_compat::*,
};
}
/// Represents a minimal EVM transaction.
#[doc(alias = "Tx")]
#[auto_impl::auto_impl(&, Arc)]
pub trait Transaction: Typed2718 + fmt::Debug + any::Any + Send + Sync + 'static {
/// Get `chain_id`.
fn chain_id(&self) -> Option<ChainId>;
/// Get `nonce`.
fn nonce(&self) -> u64;
/// Get `gas_limit`.
fn gas_limit(&self) -> u64;
/// Get `gas_price`.
fn gas_price(&self) -> Option<u128>;
/// Returns the EIP-1559 the maximum fee per gas the caller is willing to pay.
///
/// For legacy transactions this is `gas_price`.
///
/// This is also commonly referred to as the "Gas Fee Cap".
fn max_fee_per_gas(&self) -> u128;
/// Returns the EIP-1559 Priority fee the caller is paying to the block author.
///
/// This will return `None` for non-EIP1559 transactions
fn max_priority_fee_per_gas(&self) -> Option<u128>;
/// Max fee per blob gas for EIP-4844 transaction.
///
/// Returns `None` for non-eip4844 transactions.
///
/// This is also commonly referred to as the "Blob Gas Fee Cap".
fn max_fee_per_blob_gas(&self) -> Option<u128>;
/// Return the max priority fee per gas if the transaction is an EIP-1559 transaction, and
/// otherwise return the gas price.
///
/// # Warning
///
/// This is different than the `max_priority_fee_per_gas` method, which returns `None` for
/// non-EIP-1559 transactions.
fn priority_fee_or_price(&self) -> u128;
/// Returns the effective gas price for the given base fee.
///
/// If the transaction is a legacy or EIP2930 transaction, the gas price is returned.
fn effective_gas_price(&self, base_fee: Option<u64>) -> u128;
/// Returns the effective tip for this transaction.
///
/// For EIP-1559 transactions: `min(max_fee_per_gas - base_fee, max_priority_fee_per_gas)`.
/// For legacy transactions: `gas_price - base_fee`.
fn effective_tip_per_gas(&self, base_fee: u64) -> Option<u128> {
let base_fee = base_fee as u128;
let max_fee_per_gas = self.max_fee_per_gas();
// Check if max_fee_per_gas is less than base_fee
if max_fee_per_gas < base_fee {
return None;
}
// Calculate the difference between max_fee_per_gas and base_fee
let fee = max_fee_per_gas - base_fee;
// Compare the fee with max_priority_fee_per_gas (or gas price for non-EIP1559 transactions)
self.max_priority_fee_per_gas()
.map_or(Some(fee), |priority_fee| Some(fee.min(priority_fee)))
}
/// Returns `true` if the transaction supports dynamic fees.
fn is_dynamic_fee(&self) -> bool;
/// Returns the transaction kind.
fn kind(&self) -> TxKind;
/// Returns true if the transaction is a contract creation.
/// We don't provide a default implementation via `kind` as it copies the 21-byte
/// [`TxKind`] for this simple check. A proper implementation shouldn't allocate.
fn is_create(&self) -> bool;
/// Get the transaction's address of the contract that will be called, or the address that will
/// receive the transfer.
///
/// Returns `None` if this is a `CREATE` transaction.
fn to(&self) -> Option<Address> {
self.kind().to().copied()
}
/// Get `value`.
fn value(&self) -> U256;
/// Get `data`.
fn input(&self) -> &Bytes;
/// Returns the EIP-2930 `access_list` for the particular transaction type. Returns `None` for
/// older transaction types.
fn access_list(&self) -> Option<&AccessList>;
/// Blob versioned hashes for eip4844 transaction. For previous transaction types this is
/// `None`.
fn blob_versioned_hashes(&self) -> Option<&[B256]>;
/// Returns the total gas for all blobs in this transaction.
///
/// Returns `None` for non-eip4844 transactions.
#[inline]
fn blob_gas_used(&self) -> Option<u64> {
// SAFETY: we don't expect u64::MAX / DATA_GAS_PER_BLOB hashes in a single transaction
self.blob_versioned_hashes().map(|blobs| blobs.len() as u64 * DATA_GAS_PER_BLOB)
}
/// Returns the [`SignedAuthorization`] list of the transaction.
///
/// Returns `None` if this transaction is not EIP-7702.
fn authorization_list(&self) -> Option<&[SignedAuthorization]>;
}
/// A signable transaction.
///
/// A transaction can have multiple signature types. This is usually
/// [`alloy_primitives::PrimitiveSignature`], however, it may be different for future EIP-2718
/// transaction types, or in other networks. For example, in Optimism, the deposit transaction
/// signature is the unit type `()`.
#[doc(alias = "SignableTx", alias = "TxSignable")]
pub trait SignableTransaction<Signature>: Transaction {
/// Sets `chain_id`.
///
/// Prefer [`set_chain_id_checked`](Self::set_chain_id_checked).
fn set_chain_id(&mut self, chain_id: ChainId);
/// Set `chain_id` if it is not already set. Checks that the provided `chain_id` matches the
/// existing `chain_id` if it is already set, returning `false` if they do not match.
fn set_chain_id_checked(&mut self, chain_id: ChainId) -> bool {
match self.chain_id() {
Some(tx_chain_id) => {
if tx_chain_id != chain_id {
return false;
}
self.set_chain_id(chain_id);
}
None => {
self.set_chain_id(chain_id);
}
}
true
}
/// RLP-encodes the transaction for signing.
fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut);
/// Outputs the length of the signature RLP encoding for the transaction.
fn payload_len_for_signature(&self) -> usize;
/// RLP-encodes the transaction for signing it. Used to calculate `signature_hash`.
///
/// See [`SignableTransaction::encode_for_signing`].
fn encoded_for_signing(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(self.payload_len_for_signature());
self.encode_for_signing(&mut buf);
buf
}
/// Calculate the signing hash for the transaction.
fn signature_hash(&self) -> B256 {
keccak256(self.encoded_for_signing())
}
/// Convert to a signed transaction by adding a signature and computing the
/// hash.
fn into_signed(self, signature: Signature) -> Signed<Self, Signature>
where
Self: Sized;
}
// TODO: Remove in favor of dyn trait upcasting (TBD, see https://github.com/rust-lang/rust/issues/65991#issuecomment-1903120162)
#[doc(hidden)]
impl<S: 'static> dyn SignableTransaction<S> {
pub fn __downcast_ref<T: any::Any>(&self) -> Option<&T> {
if any::Any::type_id(self) == any::TypeId::of::<T>() {
unsafe { Some(&*(self as *const _ as *const T)) }
} else {
None
}
}
}
#[cfg(feature = "serde")]
impl<T: Typed2718> Typed2718 for alloy_serde::WithOtherFields<T> {
#[inline]
fn ty(&self) -> u8 {
self.inner.ty()
}
}
#[cfg(feature = "serde")]
impl<T: Transaction> Transaction for alloy_serde::WithOtherFields<T> {
#[inline]
fn chain_id(&self) -> Option<ChainId> {
self.inner.chain_id()
}
#[inline]
fn nonce(&self) -> u64 {
self.inner.nonce()
}
#[inline]
fn gas_limit(&self) -> u64 {
self.inner.gas_limit()
}
#[inline]
fn gas_price(&self) -> Option<u128> {
self.inner.gas_price()
}
#[inline]
fn max_fee_per_gas(&self) -> u128 {
self.inner.max_fee_per_gas()
}
#[inline]
fn max_priority_fee_per_gas(&self) -> Option<u128> {
self.inner.max_priority_fee_per_gas()
}
#[inline]
fn max_fee_per_blob_gas(&self) -> Option<u128> {
self.inner.max_fee_per_blob_gas()
}
#[inline]
fn priority_fee_or_price(&self) -> u128 {
self.inner.priority_fee_or_price()
}
fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
self.inner.effective_gas_price(base_fee)
}
#[inline]
fn is_dynamic_fee(&self) -> bool {
self.inner.is_dynamic_fee()
}
#[inline]
fn kind(&self) -> TxKind {
self.inner.kind()
}
#[inline]
fn is_create(&self) -> bool {
self.inner.is_create()
}
#[inline]
fn value(&self) -> U256 {
self.inner.value()
}
#[inline]
fn input(&self) -> &Bytes {
self.inner.input()
}
#[inline]
fn access_list(&self) -> Option<&AccessList> {
self.inner.access_list()
}
#[inline]
fn blob_versioned_hashes(&self) -> Option<&[B256]> {
self.inner.blob_versioned_hashes()
}
#[inline]
fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
self.inner.authorization_list()
}
}
/// A trait that helps to determine the type of the transaction.
#[auto_impl::auto_impl(&)]
pub trait Typed2718 {
/// Returns the EIP-2718 type flag.
fn ty(&self) -> u8;
/// Returns true if the type matches the given type.
fn is_type(&self, ty: u8) -> bool {
self.ty() == ty
}
/// Returns true if the type is a legacy transaction.
fn is_legacy(&self) -> bool {
self.ty() == 0
}
/// Returns true if the type is an EIP-2930 transaction.
fn is_eip2930(&self) -> bool {
self.ty() == 1
}
/// Returns true if the type is an EIP-1559 transaction.
fn is_eip1559(&self) -> bool {
self.ty() == 2
}
/// Returns true if the type is an EIP-4844 transaction.
fn is_eip4844(&self) -> bool {
self.ty() == 3
}
/// Returns true if the type is an EIP-7702 transaction.
fn is_eip7702(&self) -> bool {
self.ty() == 4
}
}