diff --git a/crates/network/src/any/builder.rs b/crates/network/src/any/builder.rs new file mode 100644 index 00000000000..426933e5ddf --- /dev/null +++ b/crates/network/src/any/builder.rs @@ -0,0 +1,118 @@ +use std::ops::{Deref, DerefMut}; + +use alloy_consensus::BlobTransactionSidecar; +use alloy_primitives::U256; +use alloy_rpc_types::{TransactionRequest, WithOtherFields}; + +use crate::{ + any::AnyNetwork, ethereum::build_unsigned, BuilderResult, Network, TransactionBuilder, +}; + +impl TransactionBuilder for WithOtherFields { + fn chain_id(&self) -> Option { + self.deref().chain_id() + } + + fn set_chain_id(&mut self, chain_id: alloy_primitives::ChainId) { + self.deref_mut().set_chain_id(chain_id) + } + + fn nonce(&self) -> Option { + self.deref().nonce() + } + + fn set_nonce(&mut self, nonce: u64) { + self.deref_mut().set_nonce(nonce) + } + + fn input(&self) -> Option<&alloy_primitives::Bytes> { + self.deref().input() + } + + fn set_input(&mut self, input: alloy_primitives::Bytes) { + self.deref_mut().set_input(input); + } + + fn from(&self) -> Option { + self.deref().from() + } + + fn set_from(&mut self, from: alloy_primitives::Address) { + self.deref_mut().set_from(from); + } + + fn to(&self) -> Option { + self.deref().to() + } + + fn set_to(&mut self, to: alloy_primitives::TxKind) { + self.deref_mut().set_to(to) + } + + fn value(&self) -> Option { + self.deref().value() + } + + fn set_value(&mut self, value: alloy_primitives::U256) { + self.deref_mut().set_value(value) + } + + fn gas_price(&self) -> Option { + self.deref().gas_price() + } + + fn set_gas_price(&mut self, gas_price: U256) { + self.deref_mut().set_gas_price(gas_price); + } + + fn max_fee_per_gas(&self) -> Option { + self.deref().max_fee_per_gas() + } + + fn set_max_fee_per_gas(&mut self, max_fee_per_gas: U256) { + self.deref_mut().set_max_fee_per_gas(max_fee_per_gas); + } + + fn max_priority_fee_per_gas(&self) -> Option { + self.deref().max_priority_fee_per_gas() + } + + fn set_max_priority_fee_per_gas(&mut self, max_priority_fee_per_gas: U256) { + self.deref_mut().set_max_priority_fee_per_gas(max_priority_fee_per_gas); + } + + fn max_fee_per_blob_gas(&self) -> Option { + self.deref().max_fee_per_blob_gas() + } + + fn set_max_fee_per_blob_gas(&mut self, max_fee_per_blob_gas: U256) { + self.deref_mut().set_max_fee_per_blob_gas(max_fee_per_blob_gas) + } + + fn gas_limit(&self) -> Option { + self.deref().gas_limit() + } + + fn set_gas_limit(&mut self, gas_limit: U256) { + self.deref_mut().set_gas_limit(gas_limit); + } + + fn build_unsigned(self) -> BuilderResult<::UnsignedTx> { + build_unsigned::(self.inner) + } + + fn get_blob_sidecar(&self) -> Option<&BlobTransactionSidecar> { + self.deref().get_blob_sidecar() + } + + fn set_blob_sidecar(&mut self, sidecar: BlobTransactionSidecar) { + self.deref_mut().set_blob_sidecar(sidecar) + } + + async fn build>( + self, + signer: &S, + ) -> BuilderResult { + Ok(signer.sign_transaction(self.build_unsigned()?).await?) + } +} diff --git a/crates/network/src/any/mod.rs b/crates/network/src/any/mod.rs new file mode 100644 index 00000000000..2fd837ab912 --- /dev/null +++ b/crates/network/src/any/mod.rs @@ -0,0 +1,40 @@ +use crate::{Network, ReceiptResponse}; +use alloy_rpc_types::{ + Header, Transaction, TransactionReceipt, TransactionRequest, WithOtherFields, +}; + +mod builder; + +/// Types for a catch-all network. +/// +/// Essentially just returns the regular Ethereum types + a catch all field. +/// This [`Network`] should be used only when the network is not known at +/// compile time. +#[derive(Debug, Clone, Copy)] +pub struct AnyNetwork { + _private: (), +} + +impl Network for AnyNetwork { + type TxEnvelope = alloy_consensus::TxEnvelope; + + type UnsignedTx = alloy_consensus::TypedTransaction; + + type ReceiptEnvelope = alloy_consensus::ReceiptEnvelope; + + type Header = alloy_consensus::Header; + + type TransactionRequest = WithOtherFields; + + type TransactionResponse = WithOtherFields; + + type ReceiptResponse = WithOtherFields; + + type HeaderResponse = WithOtherFields
; +} + +impl ReceiptResponse for WithOtherFields { + fn contract_address(&self) -> Option { + self.contract_address + } +} diff --git a/crates/network/src/ethereum/builder.rs b/crates/network/src/ethereum/builder.rs index 0001bb8a24e..4b42e6b1c49 100644 --- a/crates/network/src/ethereum/builder.rs +++ b/crates/network/src/ethereum/builder.rs @@ -99,6 +99,10 @@ impl TransactionBuilder for alloy_rpc_types::TransactionRequest { self.gas = Some(gas_limit); } + fn build_unsigned(self) -> BuilderResult<::UnsignedTx> { + build_unsigned::(self) + } + fn get_blob_sidecar(&self) -> Option<&BlobTransactionSidecar> { self.sidecar.as_ref() } @@ -108,36 +112,6 @@ impl TransactionBuilder for alloy_rpc_types::TransactionRequest { self.sidecar = Some(sidecar); } - fn build_unsigned(mut self) -> BuilderResult<::UnsignedTx> { - match ( - self.gas_price.as_ref(), - self.max_fee_per_gas.as_ref(), - self.access_list.as_ref(), - self.max_fee_per_blob_gas.as_ref(), - self.blob_versioned_hashes.as_ref(), - self.sidecar.as_ref(), - ) { - // Legacy transaction - (Some(_), None, None, None, None, None) => build_legacy(self).map(Into::into), - // EIP-2930 - // If only accesslist is set, and there are no EIP-1559 fees - (_, None, Some(_), None, None, None) => build_2930(self).map(Into::into), - // EIP-1559 - // If EIP-4844 fields are missing - (None, _, _, None, None, None) => build_1559(self).map(Into::into), - // EIP-4844 - // All blob fields required - (None, _, _, Some(_), _, Some(sidecar)) => { - if self.blob_versioned_hashes.is_none() { - //if not configured already, set the blob hashes from the sidecar - self.blob_versioned_hashes = Some(sidecar.versioned_hashes().collect()); - } - build_4844(self).map(TxEip4844Variant::from).map(Into::into) - } - _ => build_legacy(self).map(Into::into), - } - } - async fn build>( self, signer: &S, @@ -146,6 +120,37 @@ impl TransactionBuilder for alloy_rpc_types::TransactionRequest { } } +/// Build an unsigned transaction +pub(crate) fn build_unsigned(request: TransactionRequest) -> BuilderResult +where + N: Network, + N::UnsignedTx: From + From + From + From, +{ + match ( + request.gas_price.as_ref(), + request.max_fee_per_gas.as_ref(), + request.access_list.as_ref(), + request.max_fee_per_blob_gas.as_ref(), + request.blob_versioned_hashes.as_ref(), + request.sidecar.as_ref(), + ) { + // Legacy transaction + (Some(_), None, None, None, None, None) => build_legacy(request).map(Into::into), + // EIP-2930 + // If only accesslist is set, and there are no EIP-1559 fees + (_, None, Some(_), None, None, None) => build_2930(request).map(Into::into), + // EIP-1559 + // If EIP-4844 fields are missing + (None, _, _, None, None, None) => build_1559(request).map(Into::into), + // EIP-4844 + // All blob fields required + (None, _, _, Some(_), Some(_), Some(_)) => { + build_4844(request).map(TxEip4844Variant::from).map(Into::into) + } + _ => build_legacy(request).map(Into::into), + } +} + /// Build a legacy transaction. fn build_legacy(request: TransactionRequest) -> Result { Ok(TxLegacy { diff --git a/crates/network/src/ethereum/mod.rs b/crates/network/src/ethereum/mod.rs index 2479ef19cca..c6dd57d6e43 100644 --- a/crates/network/src/ethereum/mod.rs +++ b/crates/network/src/ethereum/mod.rs @@ -1,6 +1,7 @@ use crate::{Network, ReceiptResponse}; mod builder; +pub(crate) use builder::build_unsigned; mod signer; pub use signer::EthereumSigner; diff --git a/crates/network/src/lib.rs b/crates/network/src/lib.rs index 44479b442db..7a1dc54af16 100644 --- a/crates/network/src/lib.rs +++ b/crates/network/src/lib.rs @@ -28,6 +28,9 @@ pub use transaction::{ mod ethereum; pub use ethereum::{Ethereum, EthereumSigner}; +mod any; +pub use any::AnyNetwork; + pub use alloy_eips::eip2718; /// A list of transactions, either hydrated or hashes. diff --git a/crates/rpc-types/src/eth/transaction/mod.rs b/crates/rpc-types/src/eth/transaction/mod.rs index 0dba487a49c..40e8cd93e14 100644 --- a/crates/rpc-types/src/eth/transaction/mod.rs +++ b/crates/rpc-types/src/eth/transaction/mod.rs @@ -127,7 +127,6 @@ impl Transaction { max_fee_per_blob_gas: self.max_fee_per_blob_gas, blob_versioned_hashes: self.blob_versioned_hashes, sidecar: None, - other: OtherFields::default(), } } } diff --git a/crates/rpc-types/src/eth/transaction/receipt.rs b/crates/rpc-types/src/eth/transaction/receipt.rs index b2ae4d6c3b3..a0dede4556a 100644 --- a/crates/rpc-types/src/eth/transaction/receipt.rs +++ b/crates/rpc-types/src/eth/transaction/receipt.rs @@ -1,4 +1,4 @@ -use crate::{other::OtherFields, Log}; +use crate::Log; use alloy_primitives::{Address, Bloom, B256, U128, U256, U64, U8}; use serde::{Deserialize, Serialize}; @@ -53,9 +53,6 @@ pub struct TransactionReceipt { /// For legacy transactions this returns `0`. For EIP-2718 transactions this returns the type. #[serde(rename = "type")] pub transaction_type: U8, - /// Arbitrary extra fields. - #[serde(flatten)] - pub other: OtherFields, } impl TransactionReceipt { diff --git a/crates/rpc-types/src/eth/transaction/request.rs b/crates/rpc-types/src/eth/transaction/request.rs index 4bb2ba3a1c8..0ef12a61c7c 100644 --- a/crates/rpc-types/src/eth/transaction/request.rs +++ b/crates/rpc-types/src/eth/transaction/request.rs @@ -1,8 +1,6 @@ //! Alloy basic Transaction Request type. -use crate::{ - eth::transaction::AccessList, other::OtherFields, BlobTransactionSidecar, Transaction, -}; +use crate::{eth::transaction::AccessList, BlobTransactionSidecar, Transaction}; use alloy_primitives::{Address, Bytes, ChainId, B256, U256, U8}; use serde::{Deserialize, Serialize}; use std::hash::Hash; @@ -52,9 +50,6 @@ pub struct TransactionRequest { /// Blob sidecar for EIP-4844 transactions. #[serde(skip_serializing_if = "Option::is_none")] pub sidecar: Option, - /// Support for arbitrary additional fields. - #[serde(flatten)] - pub other: OtherFields, } impl Hash for TransactionRequest { @@ -74,10 +69,6 @@ impl Hash for TransactionRequest { self.transaction_type.hash(state); self.blob_versioned_hashes.hash(state); self.sidecar.hash(state); - for (k, v) in self.other.iter() { - k.hash(state); - v.to_string().hash(state); - } } } @@ -256,6 +247,7 @@ pub struct TransactionInputError; #[cfg(test)] mod tests { use super::*; + use crate::WithOtherFields; use alloy_primitives::b256; // @@ -294,7 +286,7 @@ mod tests { #[test] fn serde_tx_request_additional_fields() { let s = r#"{"accessList":[],"data":"0x0902f1ac","to":"0xa478c2975ab1ea89e8196811f51a7b7ade33eb11","type":"0x02","sourceHash":"0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a"}"#; - let req = serde_json::from_str::(s).unwrap(); + let req = serde_json::from_str::>(s).unwrap(); assert_eq!( req.other.get_deserialized::("sourceHash").unwrap().unwrap(), b256!("bf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a") diff --git a/crates/rpc-types/src/lib.rs b/crates/rpc-types/src/lib.rs index 578e8dbec91..3632f39256e 100644 --- a/crates/rpc-types/src/lib.rs +++ b/crates/rpc-types/src/lib.rs @@ -23,3 +23,6 @@ mod eth; pub use alloy_serde as serde_helpers; pub use eth::*; + +mod with_other; +pub use with_other::WithOtherFields; diff --git a/crates/rpc-types/src/with_other.rs b/crates/rpc-types/src/with_other.rs new file mode 100644 index 00000000000..8c8cdf60346 --- /dev/null +++ b/crates/rpc-types/src/with_other.rs @@ -0,0 +1,42 @@ +use crate::other::OtherFields; +use serde::{Deserialize, Serialize}; +use std::ops::{Deref, DerefMut}; + +/// Wrapper allowing to catch all fields missing on the inner struct while +/// deserialize. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WithOtherFields { + /// The inner struct. + #[serde(flatten)] + pub inner: T, + /// All fields not present in the inner struct. + #[serde(flatten)] + pub other: OtherFields, +} + +impl WithOtherFields { + /// Create a new `Extra`. + pub fn new(inner: T) -> Self { + Self { inner, other: OtherFields::default() } + } +} + +impl Deref for WithOtherFields { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for WithOtherFields { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl Default for WithOtherFields { + fn default() -> Self { + WithOtherFields::new(T::default()) + } +}