use crate::{
transaction::{RlpEcdsaTx, TxEip1559, TxEip2930, TxEip4844, TxLegacy},
SignableTransaction, Signed, Transaction, TxEip4844WithSidecar, TxEip7702, TxEnvelope, TxType,
Typed2718,
};
use alloy_eips::{
eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718},
eip2930::AccessList,
eip7702::SignedAuthorization,
};
use alloy_primitives::{
bytes, Bytes, ChainId, PrimitiveSignature as Signature, TxHash, TxKind, B256, U256,
};
use alloy_rlp::{Decodable, Encodable, Header};
use core::hash::{Hash, Hasher};
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(all(any(test, feature = "arbitrary"), feature = "k256"), derive(arbitrary::Arbitrary))]
pub enum PooledTransaction {
Legacy(Signed<TxLegacy>),
Eip2930(Signed<TxEip2930>),
Eip1559(Signed<TxEip1559>),
Eip4844(Signed<TxEip4844WithSidecar>),
Eip7702(Signed<TxEip7702>),
}
impl PooledTransaction {
pub fn signature_hash(&self) -> B256 {
match self {
Self::Legacy(tx) => tx.signature_hash(),
Self::Eip2930(tx) => tx.signature_hash(),
Self::Eip1559(tx) => tx.signature_hash(),
Self::Eip7702(tx) => tx.signature_hash(),
Self::Eip4844(tx) => tx.signature_hash(),
}
}
pub const fn hash(&self) -> &TxHash {
match self {
Self::Legacy(tx) => tx.hash(),
Self::Eip2930(tx) => tx.hash(),
Self::Eip1559(tx) => tx.hash(),
Self::Eip7702(tx) => tx.hash(),
Self::Eip4844(tx) => tx.hash(),
}
}
pub const fn signature(&self) -> &Signature {
match self {
Self::Legacy(tx) => tx.signature(),
Self::Eip2930(tx) => tx.signature(),
Self::Eip1559(tx) => tx.signature(),
Self::Eip7702(tx) => tx.signature(),
Self::Eip4844(tx) => tx.signature(),
}
}
fn network_len(&self) -> usize {
let mut payload_length = self.encode_2718_len();
if !Encodable2718::is_legacy(self) {
payload_length += Header { list: false, payload_length }.length();
}
payload_length
}
#[cfg(feature = "k256")]
pub fn recover_signer(
&self,
) -> Result<alloy_primitives::Address, alloy_primitives::SignatureError> {
match self {
Self::Legacy(tx) => tx.recover_signer(),
Self::Eip2930(tx) => tx.recover_signer(),
Self::Eip1559(tx) => tx.recover_signer(),
Self::Eip4844(tx) => tx.recover_signer(),
Self::Eip7702(tx) => tx.recover_signer(),
}
}
pub fn encode_for_signing(&self, out: &mut dyn bytes::BufMut) {
match self {
Self::Legacy(tx) => tx.tx().encode_for_signing(out),
Self::Eip2930(tx) => tx.tx().encode_for_signing(out),
Self::Eip1559(tx) => tx.tx().encode_for_signing(out),
Self::Eip4844(tx) => tx.tx().encode_for_signing(out),
Self::Eip7702(tx) => tx.tx().encode_for_signing(out),
}
}
pub fn into_envelope(self) -> TxEnvelope {
match self {
Self::Legacy(tx) => tx.into(),
Self::Eip2930(tx) => tx.into(),
Self::Eip1559(tx) => tx.into(),
Self::Eip7702(tx) => tx.into(),
Self::Eip4844(tx) => tx.into(),
}
}
pub const fn as_legacy(&self) -> Option<&TxLegacy> {
match self {
Self::Legacy(tx) => Some(tx.tx()),
_ => None,
}
}
pub const fn as_eip2930(&self) -> Option<&TxEip2930> {
match self {
Self::Eip2930(tx) => Some(tx.tx()),
_ => None,
}
}
pub const fn as_eip1559(&self) -> Option<&TxEip1559> {
match self {
Self::Eip1559(tx) => Some(tx.tx()),
_ => None,
}
}
pub const fn as_eip4844_with_sidecar(&self) -> Option<&TxEip4844WithSidecar> {
match self {
Self::Eip4844(tx) => Some(tx.tx()),
_ => None,
}
}
pub const fn as_eip4844(&self) -> Option<&TxEip4844> {
match self {
Self::Eip4844(tx) => Some(tx.tx().tx()),
_ => None,
}
}
pub const fn as_eip7702(&self) -> Option<&TxEip7702> {
match self {
Self::Eip7702(tx) => Some(tx.tx()),
_ => None,
}
}
pub fn try_into_legacy(self) -> Result<Signed<TxLegacy>, Self> {
match self {
Self::Legacy(tx) => Ok(tx),
tx => Err(tx),
}
}
pub fn try_into_eip2930(self) -> Result<Signed<TxEip2930>, Self> {
match self {
Self::Eip2930(tx) => Ok(tx),
tx => Err(tx),
}
}
pub fn try_into_eip1559(self) -> Result<Signed<TxEip1559>, Self> {
match self {
Self::Eip1559(tx) => Ok(tx),
tx => Err(tx),
}
}
pub fn try_into_eip4844(self) -> Result<Signed<TxEip4844WithSidecar>, Self> {
match self {
Self::Eip4844(tx) => Ok(tx),
tx => Err(tx),
}
}
pub fn try_into_eip7702(self) -> Result<Signed<TxEip7702>, Self> {
match self {
Self::Eip7702(tx) => Ok(tx),
tx => Err(tx),
}
}
}
impl From<Signed<TxLegacy>> for PooledTransaction {
fn from(v: Signed<TxLegacy>) -> Self {
Self::Legacy(v)
}
}
impl From<Signed<TxEip2930>> for PooledTransaction {
fn from(v: Signed<TxEip2930>) -> Self {
Self::Eip2930(v)
}
}
impl From<Signed<TxEip1559>> for PooledTransaction {
fn from(v: Signed<TxEip1559>) -> Self {
Self::Eip1559(v)
}
}
impl From<Signed<TxEip4844WithSidecar>> for PooledTransaction {
fn from(v: Signed<TxEip4844WithSidecar>) -> Self {
let (tx, signature, hash) = v.into_parts();
Self::Eip4844(Signed::new_unchecked(tx, signature, hash))
}
}
impl From<Signed<TxEip7702>> for PooledTransaction {
fn from(v: Signed<TxEip7702>) -> Self {
Self::Eip7702(v)
}
}
impl Hash for PooledTransaction {
fn hash<H: Hasher>(&self, state: &mut H) {
self.trie_hash().hash(state);
}
}
impl Encodable for PooledTransaction {
fn encode(&self, out: &mut dyn bytes::BufMut) {
self.network_encode(out);
}
fn length(&self) -> usize {
self.network_len()
}
}
impl Decodable for PooledTransaction {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
Ok(Self::network_decode(buf)?)
}
}
impl Encodable2718 for PooledTransaction {
fn type_flag(&self) -> Option<u8> {
match self {
Self::Legacy(_) => None,
Self::Eip2930(_) => Some(0x01),
Self::Eip1559(_) => Some(0x02),
Self::Eip4844(_) => Some(0x03),
Self::Eip7702(_) => Some(0x04),
}
}
fn encode_2718_len(&self) -> usize {
match self {
Self::Legacy(tx) => tx.eip2718_encoded_length(),
Self::Eip2930(tx) => tx.eip2718_encoded_length(),
Self::Eip1559(tx) => tx.eip2718_encoded_length(),
Self::Eip7702(tx) => tx.eip2718_encoded_length(),
Self::Eip4844(tx) => tx.eip2718_encoded_length(),
}
}
fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) {
match self {
Self::Legacy(tx) => tx.eip2718_encode(out),
Self::Eip2930(tx) => tx.eip2718_encode(out),
Self::Eip1559(tx) => tx.eip2718_encode(out),
Self::Eip7702(tx) => tx.eip2718_encode(out),
Self::Eip4844(tx) => tx.eip2718_encode(out),
}
}
fn trie_hash(&self) -> B256 {
*self.hash()
}
}
impl Decodable2718 for PooledTransaction {
fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
match ty.try_into().map_err(|_| alloy_rlp::Error::Custom("unexpected tx type"))? {
TxType::Eip2930 => Ok(TxEip2930::rlp_decode_signed(buf)?.into()),
TxType::Eip1559 => Ok(TxEip1559::rlp_decode_signed(buf)?.into()),
TxType::Eip4844 => Ok(TxEip4844WithSidecar::rlp_decode_signed(buf)?.into()),
TxType::Eip7702 => Ok(TxEip7702::rlp_decode_signed(buf)?.into()),
TxType::Legacy => Err(Eip2718Error::UnexpectedType(0)),
}
}
fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
TxLegacy::rlp_decode_signed(buf).map(Into::into).map_err(Into::into)
}
}
impl Transaction for PooledTransaction {
fn chain_id(&self) -> Option<ChainId> {
match self {
Self::Legacy(tx) => tx.tx().chain_id(),
Self::Eip2930(tx) => tx.tx().chain_id(),
Self::Eip1559(tx) => tx.tx().chain_id(),
Self::Eip7702(tx) => tx.tx().chain_id(),
Self::Eip4844(tx) => tx.tx().chain_id(),
}
}
fn nonce(&self) -> u64 {
match self {
Self::Legacy(tx) => tx.tx().nonce(),
Self::Eip2930(tx) => tx.tx().nonce(),
Self::Eip1559(tx) => tx.tx().nonce(),
Self::Eip7702(tx) => tx.tx().nonce(),
Self::Eip4844(tx) => tx.tx().nonce(),
}
}
fn gas_limit(&self) -> u64 {
match self {
Self::Legacy(tx) => tx.tx().gas_limit(),
Self::Eip2930(tx) => tx.tx().gas_limit(),
Self::Eip1559(tx) => tx.tx().gas_limit(),
Self::Eip7702(tx) => tx.tx().gas_limit(),
Self::Eip4844(tx) => tx.tx().gas_limit(),
}
}
fn gas_price(&self) -> Option<u128> {
match self {
Self::Legacy(tx) => tx.tx().gas_price(),
Self::Eip2930(tx) => tx.tx().gas_price(),
Self::Eip1559(tx) => tx.tx().gas_price(),
Self::Eip7702(tx) => tx.tx().gas_price(),
Self::Eip4844(tx) => tx.tx().gas_price(),
}
}
fn max_fee_per_gas(&self) -> u128 {
match self {
Self::Legacy(tx) => tx.tx().max_fee_per_gas(),
Self::Eip2930(tx) => tx.tx().max_fee_per_gas(),
Self::Eip1559(tx) => tx.tx().max_fee_per_gas(),
Self::Eip7702(tx) => tx.tx().max_fee_per_gas(),
Self::Eip4844(tx) => tx.tx().max_fee_per_gas(),
}
}
fn max_priority_fee_per_gas(&self) -> Option<u128> {
match self {
Self::Legacy(tx) => tx.tx().max_priority_fee_per_gas(),
Self::Eip2930(tx) => tx.tx().max_priority_fee_per_gas(),
Self::Eip1559(tx) => tx.tx().max_priority_fee_per_gas(),
Self::Eip7702(tx) => tx.tx().max_priority_fee_per_gas(),
Self::Eip4844(tx) => tx.tx().max_priority_fee_per_gas(),
}
}
fn max_fee_per_blob_gas(&self) -> Option<u128> {
match self {
Self::Legacy(tx) => tx.tx().max_fee_per_blob_gas(),
Self::Eip2930(tx) => tx.tx().max_fee_per_blob_gas(),
Self::Eip1559(tx) => tx.tx().max_fee_per_blob_gas(),
Self::Eip7702(tx) => tx.tx().max_fee_per_blob_gas(),
Self::Eip4844(tx) => tx.tx().max_fee_per_blob_gas(),
}
}
fn priority_fee_or_price(&self) -> u128 {
match self {
Self::Legacy(tx) => tx.tx().priority_fee_or_price(),
Self::Eip2930(tx) => tx.tx().priority_fee_or_price(),
Self::Eip1559(tx) => tx.tx().priority_fee_or_price(),
Self::Eip7702(tx) => tx.tx().priority_fee_or_price(),
Self::Eip4844(tx) => tx.tx().priority_fee_or_price(),
}
}
fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
match self {
Self::Legacy(tx) => tx.tx().effective_gas_price(base_fee),
Self::Eip2930(tx) => tx.tx().effective_gas_price(base_fee),
Self::Eip1559(tx) => tx.tx().effective_gas_price(base_fee),
Self::Eip7702(tx) => tx.tx().effective_gas_price(base_fee),
Self::Eip4844(tx) => tx.tx().effective_gas_price(base_fee),
}
}
fn is_dynamic_fee(&self) -> bool {
match self {
Self::Legacy(tx) => tx.tx().is_dynamic_fee(),
Self::Eip2930(tx) => tx.tx().is_dynamic_fee(),
Self::Eip1559(tx) => tx.tx().is_dynamic_fee(),
Self::Eip7702(tx) => tx.tx().is_dynamic_fee(),
Self::Eip4844(tx) => tx.tx().is_dynamic_fee(),
}
}
fn kind(&self) -> TxKind {
match self {
Self::Legacy(tx) => tx.tx().kind(),
Self::Eip2930(tx) => tx.tx().kind(),
Self::Eip1559(tx) => tx.tx().kind(),
Self::Eip7702(tx) => tx.tx().kind(),
Self::Eip4844(tx) => tx.tx().kind(),
}
}
fn is_create(&self) -> bool {
match self {
Self::Legacy(tx) => tx.tx().is_create(),
Self::Eip2930(tx) => tx.tx().is_create(),
Self::Eip1559(tx) => tx.tx().is_create(),
Self::Eip7702(tx) => tx.tx().is_create(),
Self::Eip4844(tx) => tx.tx().is_create(),
}
}
fn value(&self) -> U256 {
match self {
Self::Legacy(tx) => tx.tx().value(),
Self::Eip2930(tx) => tx.tx().value(),
Self::Eip1559(tx) => tx.tx().value(),
Self::Eip7702(tx) => tx.tx().value(),
Self::Eip4844(tx) => tx.tx().value(),
}
}
fn input(&self) -> &Bytes {
match self {
Self::Legacy(tx) => tx.tx().input(),
Self::Eip2930(tx) => tx.tx().input(),
Self::Eip1559(tx) => tx.tx().input(),
Self::Eip7702(tx) => tx.tx().input(),
Self::Eip4844(tx) => tx.tx().input(),
}
}
fn access_list(&self) -> Option<&AccessList> {
match self {
Self::Legacy(tx) => tx.tx().access_list(),
Self::Eip2930(tx) => tx.tx().access_list(),
Self::Eip1559(tx) => tx.tx().access_list(),
Self::Eip7702(tx) => tx.tx().access_list(),
Self::Eip4844(tx) => tx.tx().access_list(),
}
}
fn blob_versioned_hashes(&self) -> Option<&[B256]> {
match self {
Self::Legacy(tx) => tx.tx().blob_versioned_hashes(),
Self::Eip2930(tx) => tx.tx().blob_versioned_hashes(),
Self::Eip1559(tx) => tx.tx().blob_versioned_hashes(),
Self::Eip7702(tx) => tx.tx().blob_versioned_hashes(),
Self::Eip4844(tx) => tx.tx().blob_versioned_hashes(),
}
}
fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
match self {
Self::Legacy(tx) => tx.tx().authorization_list(),
Self::Eip2930(tx) => tx.tx().authorization_list(),
Self::Eip1559(tx) => tx.tx().authorization_list(),
Self::Eip7702(tx) => tx.tx().authorization_list(),
Self::Eip4844(tx) => tx.tx().authorization_list(),
}
}
}
impl Typed2718 for PooledTransaction {
fn ty(&self) -> u8 {
match self {
Self::Legacy(tx) => tx.tx().ty(),
Self::Eip2930(tx) => tx.tx().ty(),
Self::Eip1559(tx) => tx.tx().ty(),
Self::Eip7702(tx) => tx.tx().ty(),
Self::Eip4844(tx) => tx.tx().ty(),
}
}
}
impl From<PooledTransaction> for TxEnvelope {
fn from(tx: PooledTransaction) -> Self {
tx.into_envelope()
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::{address, hex};
use bytes::Bytes;
#[test]
fn invalid_legacy_pooled_decoding_input_too_short() {
let input_too_short = [
&hex!("d90b0280808bc5cd028083c5cdfd9e407c56565656")[..],
&hex!("c10b02808083c5cd028883c5cdfd9e407c56565656"),
&hex!("c10b0280808bc5cd028083c5cdfd9e407c56565656"),
&hex!("d40b02808083c5cdeb8783c5acfd9e407c5656565656"),
&hex!("d30102808083c5cd02887dc5cdfd9e64fd9e407c56"),
];
for hex_data in &input_too_short {
let input_rlp = &mut &hex_data[..];
let res = PooledTransaction::decode(input_rlp);
assert!(
res.is_err(),
"expected err after decoding rlp input: {:x?}",
Bytes::copy_from_slice(hex_data)
);
let input_rlp = &mut &hex_data[..];
let res = PooledTransaction::decode_2718(input_rlp);
assert!(
res.is_err(),
"expected err after decoding enveloped rlp input: {:x?}",
Bytes::copy_from_slice(hex_data)
);
}
}
#[test]
fn decode_eip1559_enveloped() {
let data = hex!("02f903d382426882ba09832dc6c0848674742682ed9694714b6a4ea9b94a8a7d9fd362ed72630688c8898c80b90364492d24749189822d8512430d3f3ff7a2ede675ac08265c08e2c56ff6fdaa66dae1cdbe4a5d1d7809f3e99272d067364e597542ac0c369d69e22a6399c3e9bee5da4b07e3f3fdc34c32c3d88aa2268785f3e3f8086df0934b10ef92cfffc2e7f3d90f5e83302e31382e302d64657600000000000000000000000000000000000000000000569e75fc77c1a856f6daaf9e69d8a9566ca34aa47f9133711ce065a571af0cfd000000000000000000000000e1e210594771824dad216568b91c9cb4ceed361c00000000000000000000000000000000000000000000000000000000000546e00000000000000000000000000000000000000000000000000000000000e4e1c00000000000000000000000000000000000000000000000000000000065d6750c00000000000000000000000000000000000000000000000000000000000f288000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002cf600000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000000f1628e56fa6d8c50e5b984a58c0df14de31c7b857ce7ba499945b99252976a93d06dcda6776fc42167fbe71cb59f978f5ef5b12577a90b132d14d9c6efa528076f0161d7bf03643cfc5490ec5084f4a041db7f06c50bd97efa08907ba79ddcac8b890f24d12d8db31abbaaf18985d54f400449ee0559a4452afe53de5853ce090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000064ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000c080a01428023fc54a27544abc421d5d017b9a7c5936ad501cbdecd0d9d12d04c1a033a0753104bbf1c87634d6ff3f0ffa0982710612306003eb022363b57994bdef445a"
);
let res = PooledTransaction::decode_2718(&mut &data[..]).unwrap();
assert_eq!(res.to(), Some(address!("714b6a4ea9b94a8a7d9fd362ed72630688c8898c")));
}
#[test]
fn legacy_valid_pooled_decoding() {
let data = &hex!("d30b02808083c5cdeb8783c5acfd9e407c565656")[..];
let input_rlp = &mut &data[..];
let res = PooledTransaction::decode(input_rlp);
assert!(res.is_ok());
assert!(input_rlp.is_empty());
let res = PooledTransaction::decode_2718(&mut &data[..]);
assert!(res.is_ok());
}
}