diff --git a/Cargo.lock b/Cargo.lock index 0c64ed9a9101..8b004617aea5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8325,8 +8325,14 @@ name = "reth-optimism-primitives" version = "1.1.1" dependencies = [ "alloy-consensus", + "alloy-eips", "alloy-primitives", + "alloy-rlp", + "bytes", + "derive_more 1.0.0", + "op-alloy-consensus", "reth-primitives", + "reth-primitives-traits", ] [[package]] diff --git a/crates/optimism/primitives/Cargo.toml b/crates/optimism/primitives/Cargo.toml index a2d4c20a8b72..bc11c3585046 100644 --- a/crates/optimism/primitives/Cargo.toml +++ b/crates/optimism/primitives/Cargo.toml @@ -13,5 +13,11 @@ workspace = true [dependencies] reth-primitives.workspace = true +reth-primitives-traits.workspace = true alloy-primitives.workspace = true alloy-consensus.workspace = true +op-alloy-consensus.workspace = true +alloy-eips.workspace = true +alloy-rlp.workspace = true +derive_more.workspace = true +bytes.workspace = true diff --git a/crates/optimism/primitives/src/lib.rs b/crates/optimism/primitives/src/lib.rs index 659900b9adbb..f8d8e511498b 100644 --- a/crates/optimism/primitives/src/lib.rs +++ b/crates/optimism/primitives/src/lib.rs @@ -8,3 +8,4 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] pub mod bedrock; +pub mod op_tx_type; diff --git a/crates/optimism/primitives/src/op_tx_type.rs b/crates/optimism/primitives/src/op_tx_type.rs new file mode 100644 index 000000000000..b317bb05c9c5 --- /dev/null +++ b/crates/optimism/primitives/src/op_tx_type.rs @@ -0,0 +1,189 @@ +//! newtype pattern on `op_alloy_consensus::OpTxType`. +//! `OpTxType` implements `reth_primitives_traits::TxType`. +//! This type is required because a `Compact` impl is needed on the deposit tx type. + +use alloy_primitives::{U64, U8}; +use alloy_rlp::{Decodable, Encodable, Error}; +use bytes::BufMut; +use core::fmt::Debug; +use derive_more::{ + derive::{From, Into}, + Display, +}; +use op_alloy_consensus::OpTxType as AlloyOpTxType; +use std::convert::TryFrom; + +/// Wrapper type for `AlloyOpTxType` to implement `TxType` trait. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Display, Ord, Hash, From, Into)] +#[into(u8)] +pub struct OpTxType(AlloyOpTxType); + +impl From for U8 { + fn from(tx_type: OpTxType) -> Self { + Self::from(u8::from(tx_type)) + } +} + +impl TryFrom for OpTxType { + type Error = Error; + + fn try_from(value: u8) -> Result { + AlloyOpTxType::try_from(value) + .map(OpTxType) + .map_err(|_| Error::Custom("Invalid transaction type")) + } +} + +impl Default for OpTxType { + fn default() -> Self { + Self(AlloyOpTxType::Legacy) + } +} + +impl PartialEq for OpTxType { + fn eq(&self, other: &u8) -> bool { + let self_as_u8: u8 = (*self).into(); + &self_as_u8 == other + } +} + +impl TryFrom for OpTxType { + type Error = Error; + + fn try_from(value: u64) -> Result { + if value > u8::MAX as u64 { + return Err(Error::Custom("value out of range")); + } + Self::try_from(value as u8) + } +} + +impl TryFrom for OpTxType { + type Error = Error; + + fn try_from(value: U64) -> Result { + let u64_value: u64 = value.try_into().map_err(|_| Error::Custom("value out of range"))?; + Self::try_from(u64_value) + } +} + +impl Encodable for OpTxType { + fn length(&self) -> usize { + let value: u8 = (*self).into(); + value.length() + } + + fn encode(&self, out: &mut dyn BufMut) { + let value: u8 = (*self).into(); + value.encode(out); + } +} + +impl Decodable for OpTxType { + fn decode(buf: &mut &[u8]) -> Result { + // Decode the u8 value from RLP + let value = if buf.is_empty() { + return Err(alloy_rlp::Error::InputTooShort); + } else if buf[0] == 0x80 { + 0 // Special case: RLP encoding for integer 0 is `b"\x80"` + } else { + u8::decode(buf)? + }; + + Self::try_from(value).map_err(|_| alloy_rlp::Error::Custom("Invalid transaction type")) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bytes::BytesMut; + + #[test] + fn test_from_alloy_op_tx_type() { + let alloy_tx = AlloyOpTxType::Legacy; + let op_tx: OpTxType = OpTxType::from(alloy_tx); + assert_eq!(op_tx, OpTxType(AlloyOpTxType::Legacy)); + } + + #[test] + fn test_from_op_tx_type_to_u8() { + let op_tx = OpTxType(AlloyOpTxType::Legacy); + let tx_type_u8: u8 = op_tx.into(); + assert_eq!(tx_type_u8, AlloyOpTxType::Legacy as u8); + } + + #[test] + fn test_from_op_tx_type_to_u8_u8() { + let op_tx = OpTxType(AlloyOpTxType::Legacy); + let tx_type_u8: U8 = op_tx.into(); + assert_eq!(tx_type_u8, U8::from(AlloyOpTxType::Legacy as u8)); + } + + #[test] + fn test_try_from_u8() { + let op_tx = OpTxType::try_from(AlloyOpTxType::Legacy as u8).unwrap(); + assert_eq!(op_tx, OpTxType(AlloyOpTxType::Legacy)); + } + + #[test] + fn test_try_from_invalid_u8() { + let invalid_value: u8 = 255; + let result = OpTxType::try_from(invalid_value); + assert_eq!(result, Err(Error::Custom("Invalid transaction type"))); + } + + #[test] + fn test_try_from_u64() { + let op_tx = OpTxType::try_from(AlloyOpTxType::Legacy as u64).unwrap(); + assert_eq!(op_tx, OpTxType(AlloyOpTxType::Legacy)); + } + + #[test] + fn test_try_from_u64_out_of_range() { + let result = OpTxType::try_from(u64::MAX); + assert_eq!(result, Err(Error::Custom("value out of range"))); + } + + #[test] + fn test_try_from_u64_within_range() { + let valid_value: U64 = U64::from(AlloyOpTxType::Legacy as u64); + let op_tx = OpTxType::try_from(valid_value).unwrap(); + assert_eq!(op_tx, OpTxType(AlloyOpTxType::Legacy)); + } + + #[test] + fn test_default() { + let default_tx = OpTxType::default(); + assert_eq!(default_tx, OpTxType(AlloyOpTxType::Legacy)); + } + + #[test] + fn test_partial_eq_u8() { + let op_tx = OpTxType(AlloyOpTxType::Legacy); + assert_eq!(op_tx, AlloyOpTxType::Legacy as u8); + } + + #[test] + fn test_encodable() { + let op_tx = OpTxType(AlloyOpTxType::Legacy); + let mut buf = BytesMut::new(); + op_tx.encode(&mut buf); + assert_eq!(buf, BytesMut::from(&[0x80][..])); + } + + #[test] + fn test_decodable_success() { + // Using the RLP-encoded form of 0, which is `b"\x80"` + let mut buf: &[u8] = &[0x80]; + let decoded_tx = OpTxType::decode(&mut buf).unwrap(); + assert_eq!(decoded_tx, OpTxType(AlloyOpTxType::Legacy)); + } + + #[test] + fn test_decodable_invalid() { + let mut buf: &[u8] = &[255]; + let result = OpTxType::decode(&mut buf); + assert!(result.is_err()); + } +} diff --git a/crates/primitives-traits/src/tx_type.rs b/crates/primitives-traits/src/tx_type.rs index a25a7d659bde..6ca55879442b 100644 --- a/crates/primitives-traits/src/tx_type.rs +++ b/crates/primitives-traits/src/tx_type.rs @@ -26,3 +26,25 @@ pub trait TxType: + Decodable { } + +impl TxType for T where + T: Send + + Sync + + Unpin + + Clone + + Copy + + Default + + fmt::Debug + + fmt::Display + + PartialEq + + Eq + + PartialEq + + Into + + Into + + TryFrom + + TryFrom + + TryFrom + + Encodable + + Decodable +{ +}