reth_primitives/transaction/
tx_type.rsuse alloy_consensus::constants::{
EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID,
LEGACY_TX_TYPE_ID,
};
use alloy_primitives::{U64, U8};
use alloy_rlp::{Decodable, Encodable};
use derive_more::Display;
use serde::{Deserialize, Serialize};
#[cfg(any(test, feature = "reth-codec"))]
pub(crate) const COMPACT_IDENTIFIER_LEGACY: usize = 0;
#[cfg(any(test, feature = "reth-codec"))]
pub(crate) const COMPACT_IDENTIFIER_EIP2930: usize = 1;
#[cfg(any(test, feature = "reth-codec"))]
pub(crate) const COMPACT_IDENTIFIER_EIP1559: usize = 2;
#[cfg(any(test, feature = "reth-codec"))]
pub(crate) const COMPACT_EXTENDED_IDENTIFIER_FLAG: usize = 3;
#[cfg(feature = "optimism")]
pub const DEPOSIT_TX_TYPE_ID: u8 = 126;
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Default,
Serialize,
Deserialize,
Hash,
Display,
)]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(compact))]
#[display("tx type: {_variant}")]
pub enum TxType {
#[default]
#[display("legacy (0)")]
Legacy = 0_isize,
#[display("eip2930 (1)")]
Eip2930 = 1_isize,
#[display("eip1559 (2)")]
Eip1559 = 2_isize,
#[display("eip4844 (3)")]
Eip4844 = 3_isize,
#[display("eip7702 (4)")]
Eip7702 = 4_isize,
#[cfg(feature = "optimism")]
#[display("deposit (126)")]
Deposit = 126_isize,
}
impl TxType {
pub const MAX_RESERVED_EIP: Self = Self::Eip7702;
pub const fn has_access_list(&self) -> bool {
match self {
Self::Legacy => false,
Self::Eip2930 | Self::Eip1559 | Self::Eip4844 | Self::Eip7702 => true,
#[cfg(feature = "optimism")]
Self::Deposit => false,
}
}
}
impl From<TxType> for u8 {
fn from(value: TxType) -> Self {
match value {
TxType::Legacy => LEGACY_TX_TYPE_ID,
TxType::Eip2930 => EIP2930_TX_TYPE_ID,
TxType::Eip1559 => EIP1559_TX_TYPE_ID,
TxType::Eip4844 => EIP4844_TX_TYPE_ID,
TxType::Eip7702 => EIP7702_TX_TYPE_ID,
#[cfg(feature = "optimism")]
TxType::Deposit => DEPOSIT_TX_TYPE_ID,
}
}
}
impl From<TxType> for U8 {
fn from(value: TxType) -> Self {
Self::from(u8::from(value))
}
}
impl TryFrom<u8> for TxType {
type Error = &'static str;
fn try_from(value: u8) -> Result<Self, Self::Error> {
#[cfg(feature = "optimism")]
if value == Self::Deposit {
return Ok(Self::Deposit)
}
if value == Self::Legacy {
return Ok(Self::Legacy)
} else if value == Self::Eip2930 {
return Ok(Self::Eip2930)
} else if value == Self::Eip1559 {
return Ok(Self::Eip1559)
} else if value == Self::Eip4844 {
return Ok(Self::Eip4844)
} else if value == Self::Eip7702 {
return Ok(Self::Eip7702)
}
Err("invalid tx type")
}
}
impl TryFrom<u64> for TxType {
type Error = &'static str;
fn try_from(value: u64) -> Result<Self, Self::Error> {
let value: u8 = value.try_into().map_err(|_| "invalid tx type")?;
Self::try_from(value)
}
}
impl TryFrom<U64> for TxType {
type Error = &'static str;
fn try_from(value: U64) -> Result<Self, Self::Error> {
value.to::<u64>().try_into()
}
}
#[cfg(any(test, feature = "reth-codec"))]
impl reth_codecs::Compact for TxType {
fn to_compact<B>(&self, buf: &mut B) -> usize
where
B: bytes::BufMut + AsMut<[u8]>,
{
match self {
Self::Legacy => COMPACT_IDENTIFIER_LEGACY,
Self::Eip2930 => COMPACT_IDENTIFIER_EIP2930,
Self::Eip1559 => COMPACT_IDENTIFIER_EIP1559,
Self::Eip4844 => {
buf.put_u8(EIP4844_TX_TYPE_ID);
COMPACT_EXTENDED_IDENTIFIER_FLAG
}
Self::Eip7702 => {
buf.put_u8(EIP7702_TX_TYPE_ID);
COMPACT_EXTENDED_IDENTIFIER_FLAG
}
#[cfg(feature = "optimism")]
Self::Deposit => {
buf.put_u8(DEPOSIT_TX_TYPE_ID);
COMPACT_EXTENDED_IDENTIFIER_FLAG
}
}
}
fn from_compact(mut buf: &[u8], identifier: usize) -> (Self, &[u8]) {
use bytes::Buf;
(
match identifier {
COMPACT_IDENTIFIER_LEGACY => Self::Legacy,
COMPACT_IDENTIFIER_EIP2930 => Self::Eip2930,
COMPACT_IDENTIFIER_EIP1559 => Self::Eip1559,
COMPACT_EXTENDED_IDENTIFIER_FLAG => {
let extended_identifier = buf.get_u8();
match extended_identifier {
EIP4844_TX_TYPE_ID => Self::Eip4844,
EIP7702_TX_TYPE_ID => Self::Eip7702,
#[cfg(feature = "optimism")]
DEPOSIT_TX_TYPE_ID => Self::Deposit,
_ => panic!("Unsupported TxType identifier: {extended_identifier}"),
}
}
_ => panic!("Unknown identifier for TxType: {identifier}"),
},
buf,
)
}
}
impl PartialEq<u8> for TxType {
fn eq(&self, other: &u8) -> bool {
*self as u8 == *other
}
}
impl PartialEq<TxType> for u8 {
fn eq(&self, other: &TxType) -> bool {
*self == *other as Self
}
}
impl Encodable for TxType {
fn encode(&self, out: &mut dyn bytes::BufMut) {
(*self as u8).encode(out);
}
fn length(&self) -> usize {
1
}
}
impl Decodable for TxType {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
let ty = u8::decode(buf)?;
Self::try_from(ty).map_err(alloy_rlp::Error::Custom)
}
}
#[cfg(test)]
mod tests {
use alloy_primitives::hex;
use reth_codecs::Compact;
use rstest::rstest;
use super::*;
#[rstest]
#[case(U64::from(LEGACY_TX_TYPE_ID), Ok(TxType::Legacy))]
#[case(U64::from(EIP2930_TX_TYPE_ID), Ok(TxType::Eip2930))]
#[case(U64::from(EIP1559_TX_TYPE_ID), Ok(TxType::Eip1559))]
#[case(U64::from(EIP4844_TX_TYPE_ID), Ok(TxType::Eip4844))]
#[case(U64::from(EIP7702_TX_TYPE_ID), Ok(TxType::Eip7702))]
#[cfg_attr(feature = "optimism", case(U64::from(DEPOSIT_TX_TYPE_ID), Ok(TxType::Deposit)))]
#[case(U64::MAX, Err("invalid tx type"))]
fn test_u64_to_tx_type(#[case] input: U64, #[case] expected: Result<TxType, &'static str>) {
let tx_type_result = TxType::try_from(input);
assert_eq!(tx_type_result, expected);
}
#[rstest]
#[case(TxType::Legacy, COMPACT_IDENTIFIER_LEGACY, vec![])]
#[case(TxType::Eip2930, COMPACT_IDENTIFIER_EIP2930, vec![])]
#[case(TxType::Eip1559, COMPACT_IDENTIFIER_EIP1559, vec![])]
#[case(TxType::Eip4844, COMPACT_EXTENDED_IDENTIFIER_FLAG, vec![EIP4844_TX_TYPE_ID])]
#[case(TxType::Eip7702, COMPACT_EXTENDED_IDENTIFIER_FLAG, vec![EIP7702_TX_TYPE_ID])]
#[cfg_attr(feature = "optimism", case(TxType::Deposit, COMPACT_EXTENDED_IDENTIFIER_FLAG, vec![DEPOSIT_TX_TYPE_ID]))]
fn test_txtype_to_compact(
#[case] tx_type: TxType,
#[case] expected_identifier: usize,
#[case] expected_buf: Vec<u8>,
) {
let mut buf = vec![];
let identifier = tx_type.to_compact(&mut buf);
assert_eq!(identifier, expected_identifier, "Unexpected identifier for TxType {tx_type:?}",);
assert_eq!(buf, expected_buf, "Unexpected buffer for TxType {tx_type:?}",);
}
#[rstest]
#[case(TxType::Legacy, COMPACT_IDENTIFIER_LEGACY, vec![])]
#[case(TxType::Eip2930, COMPACT_IDENTIFIER_EIP2930, vec![])]
#[case(TxType::Eip1559, COMPACT_IDENTIFIER_EIP1559, vec![])]
#[case(TxType::Eip4844, COMPACT_EXTENDED_IDENTIFIER_FLAG, vec![EIP4844_TX_TYPE_ID])]
#[case(TxType::Eip7702, COMPACT_EXTENDED_IDENTIFIER_FLAG, vec![EIP7702_TX_TYPE_ID])]
#[cfg_attr(feature = "optimism", case(TxType::Deposit, COMPACT_EXTENDED_IDENTIFIER_FLAG, vec![DEPOSIT_TX_TYPE_ID]))]
fn test_txtype_from_compact(
#[case] expected_type: TxType,
#[case] identifier: usize,
#[case] buf: Vec<u8>,
) {
let (actual_type, remaining_buf) = TxType::from_compact(&buf, identifier);
assert_eq!(actual_type, expected_type, "Unexpected TxType for identifier {identifier}");
assert!(remaining_buf.is_empty(), "Buffer not fully consumed for identifier {identifier}");
}
#[rstest]
#[case(&hex!("80"), Ok(TxType::Legacy))]
#[case(&[EIP2930_TX_TYPE_ID], Ok(TxType::Eip2930))]
#[case(&[EIP1559_TX_TYPE_ID], Ok(TxType::Eip1559))]
#[case(&[EIP4844_TX_TYPE_ID], Ok(TxType::Eip4844))]
#[case(&[EIP7702_TX_TYPE_ID], Ok(TxType::Eip7702))]
#[case(&[u8::MAX], Err(alloy_rlp::Error::InputTooShort))]
#[cfg_attr(feature = "optimism", case(&[DEPOSIT_TX_TYPE_ID], Ok(TxType::Deposit)))]
fn decode_tx_type(#[case] input: &[u8], #[case] expected: Result<TxType, alloy_rlp::Error>) {
let tx_type_result = TxType::decode(&mut &input[..]);
assert_eq!(tx_type_result, expected)
}
}