diff --git a/crates/consensus/src/transaction/envelope.rs b/crates/consensus/src/transaction/envelope.rs index 13faf224343..17a224358d7 100644 --- a/crates/consensus/src/transaction/envelope.rs +++ b/crates/consensus/src/transaction/envelope.rs @@ -370,6 +370,16 @@ impl Encodable2718 for TxEnvelope { } } } + + fn trie_hash(&self) -> B256 { + match self { + Self::Legacy(tx) => *tx.hash(), + Self::Eip2930(tx) => *tx.hash(), + Self::Eip1559(tx) => *tx.hash(), + Self::Eip4844(tx) => *tx.hash(), + Self::Eip7702(tx) => *tx.hash(), + } + } } impl Transaction for TxEnvelope { @@ -1000,6 +1010,7 @@ mod tests { let tx_envelope: TxEnvelope = tx.into_signed(signature).into(); let serialized = serde_json::to_string(&tx_envelope).unwrap(); + let deserialized: TxEnvelope = serde_json::from_str(&serialized).unwrap(); assert_eq!(tx_envelope, deserialized); @@ -1124,4 +1135,19 @@ mod tests { }; test_serde_roundtrip(tx); } + + #[test] + #[cfg(feature = "serde")] + fn serde_tx_from_contract_call() { + let rpc_tx = r#"{"hash":"0x018b2331d461a4aeedf6a1f9cc37463377578244e6a35216057a8370714e798f","nonce":"0x1","blockHash":"0x3ca295f1dcaf8ac073c543dc0eccf18859f411206df181731e374e9917252931","blockNumber":"0x2","transactionIndex":"0x0","from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","to":"0x5fbdb2315678afecb367f032d93f642f64180aa3","value":"0x0","gasPrice":"0x3a29f0f8","gas":"0x1c9c380","maxFeePerGas":"0xba43b7400","maxPriorityFeePerGas":"0x5f5e100","input":"0xd09de08a","r":"0xd309309a59a49021281cb6bb41d164c96eab4e50f0c1bd24c03ca336e7bc2bb7","s":"0x28a7f089143d0a1355ebeb2a1b9f0e5ad9eca4303021c1400d61bc23c9ac5319","v":"0x0","yParity":"0x0","chainId":"0x7a69","accessList":[],"type":"0x2"}"#; + + let te = serde_json::from_str::(rpc_tx).unwrap(); + + assert_eq!( + *te.tx_hash(), + alloy_primitives::b256!( + "018b2331d461a4aeedf6a1f9cc37463377578244e6a35216057a8370714e798f" + ) + ); + } } diff --git a/crates/contract/Cargo.toml b/crates/contract/Cargo.toml index f93159608f6..153ad6e70fd 100644 --- a/crates/contract/Cargo.toml +++ b/crates/contract/Cargo.toml @@ -37,6 +37,7 @@ thiserror.workspace = true alloy-pubsub = { workspace = true, optional = true } [dev-dependencies] +alloy-consensus.workspace = true alloy-rpc-client = { workspace = true, features = ["pubsub", "ws"] } alloy-transport-http.workspace = true alloy-node-bindings.workspace = true diff --git a/crates/contract/src/call.rs b/crates/contract/src/call.rs index f8efa3ee249..e14143518f1 100644 --- a/crates/contract/src/call.rs +++ b/crates/contract/src/call.rs @@ -567,6 +567,7 @@ impl std::fmt::Debug for CallBuilder for WithOtherFields { ) .into_unbuilt(self)); } - Ok(self.inner.build_typed_tx().expect("checked by missing_keys")) + Ok(self.inner.build_typed_tx().expect("checked by missing_keys").into()) } async fn build>( diff --git a/crates/network/src/any/either.rs b/crates/network/src/any/either.rs new file mode 100644 index 00000000000..99c2c492f54 --- /dev/null +++ b/crates/network/src/any/either.rs @@ -0,0 +1,360 @@ +use crate::{UnknownTxEnvelope, UnknownTypedTransaction}; +use alloy_consensus::{Transaction as TransactionTrait, TxEnvelope, TypedTransaction}; +use alloy_eips::{ + eip2718::{Decodable2718, Encodable2718}, + eip7702::SignedAuthorization, +}; +use alloy_primitives::{Bytes, B256, U256}; +use alloy_rpc_types_eth::{AccessList, TransactionRequest}; +use alloy_serde::{OtherFields, WithOtherFields}; + +/// Unsigned transaction type for a catch-all network. +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[serde(untagged)] +#[doc(alias = "AnyTypedTx")] +pub enum AnyTypedTransaction { + /// An Ethereum transaction. + Ethereum(TypedTransaction), + /// A transaction with unknown type. + Unknown(UnknownTypedTransaction), +} + +impl AnyTypedTransaction { + /// Select a field by key and attempt to deserialize it. + /// + /// This method will return `None` if the key is not present in the fields, + /// or if the transaction is already fully deserialized (i.e. it is an + /// Ethereum [`TxEnvelope`]). Otherwise, it will attempt to deserialize the + /// field and return the result wrapped in a `Some`. + pub fn deser_by_key( + &self, + key: &str, + ) -> Option> { + match self { + Self::Ethereum(_) => None, + Self::Unknown(inner) => inner.deser_by_key(key), + } + } +} + +impl From for AnyTypedTransaction { + fn from(value: UnknownTypedTransaction) -> Self { + Self::Unknown(value) + } +} + +impl From for AnyTypedTransaction { + fn from(value: TypedTransaction) -> Self { + Self::Ethereum(value) + } +} + +impl From for AnyTypedTransaction { + fn from(value: AnyTxEnvelope) -> Self { + match value { + AnyTxEnvelope::Ethereum(tx) => Self::Ethereum(tx.into()), + AnyTxEnvelope::Unknown(UnknownTxEnvelope { inner, .. }) => inner.into(), + } + } +} + +impl From for WithOtherFields { + fn from(value: AnyTypedTransaction) -> Self { + match value { + AnyTypedTransaction::Ethereum(tx) => Self::new(tx.into()), + AnyTypedTransaction::Unknown(UnknownTypedTransaction { ty, mut fields, .. }) => { + fields.insert("type".to_string(), serde_json::Value::Number(ty.0.into())); + Self { inner: Default::default(), other: OtherFields::new(fields) } + } + } + } +} + +impl From for WithOtherFields { + fn from(value: AnyTxEnvelope) -> Self { + AnyTypedTransaction::from(value).into() + } +} + +impl TransactionTrait for AnyTypedTransaction { + fn chain_id(&self) -> Option { + match self { + Self::Ethereum(inner) => inner.chain_id(), + Self::Unknown(inner) => inner.chain_id(), + } + } + + fn nonce(&self) -> u64 { + match self { + Self::Ethereum(inner) => inner.nonce(), + Self::Unknown(inner) => inner.nonce(), + } + } + + fn gas_limit(&self) -> u64 { + match self { + Self::Ethereum(inner) => inner.gas_limit(), + Self::Unknown(inner) => inner.gas_limit(), + } + } + + fn gas_price(&self) -> Option { + match self { + Self::Ethereum(inner) => inner.gas_price(), + Self::Unknown(inner) => inner.gas_price(), + } + } + + fn max_fee_per_gas(&self) -> u128 { + match self { + Self::Ethereum(inner) => inner.max_fee_per_gas(), + Self::Unknown(inner) => inner.max_fee_per_gas(), + } + } + + fn max_priority_fee_per_gas(&self) -> Option { + match self { + Self::Ethereum(inner) => inner.max_priority_fee_per_gas(), + Self::Unknown(inner) => inner.max_priority_fee_per_gas(), + } + } + + fn max_fee_per_blob_gas(&self) -> Option { + match self { + Self::Ethereum(inner) => inner.max_fee_per_blob_gas(), + Self::Unknown(inner) => inner.max_fee_per_blob_gas(), + } + } + + fn priority_fee_or_price(&self) -> u128 { + self.max_priority_fee_per_gas().or_else(|| self.gas_price()).unwrap_or_default() + } + + fn kind(&self) -> alloy_primitives::TxKind { + match self { + Self::Ethereum(inner) => inner.kind(), + Self::Unknown(inner) => inner.kind(), + } + } + + fn value(&self) -> U256 { + match self { + Self::Ethereum(inner) => inner.value(), + Self::Unknown(inner) => inner.value(), + } + } + + fn input(&self) -> &Bytes { + match self { + Self::Ethereum(inner) => inner.input(), + Self::Unknown(inner) => inner.input(), + } + } + + fn ty(&self) -> u8 { + match self { + Self::Ethereum(inner) => inner.ty(), + Self::Unknown(inner) => inner.ty(), + } + } + + fn access_list(&self) -> Option<&AccessList> { + match self { + Self::Ethereum(inner) => inner.access_list(), + Self::Unknown(inner) => inner.access_list(), + } + } + + fn blob_versioned_hashes(&self) -> Option<&[B256]> { + match self { + Self::Ethereum(inner) => inner.blob_versioned_hashes(), + Self::Unknown(inner) => inner.blob_versioned_hashes(), + } + } + + fn authorization_list(&self) -> Option<&[SignedAuthorization]> { + match self { + Self::Ethereum(inner) => inner.authorization_list(), + Self::Unknown(inner) => inner.authorization_list(), + } + } +} + +/// Transaction envelope for a catch-all network. +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[serde(untagged)] +#[doc(alias = "AnyTransactionEnvelope")] +pub enum AnyTxEnvelope { + /// An Ethereum transaction. + Ethereum(TxEnvelope), + /// A transaction with unknown type. + Unknown(UnknownTxEnvelope), +} + +impl AnyTxEnvelope { + /// Select a field by key and attempt to deserialize it. + /// + /// This method will return `None` if the key is not present in the fields, + /// or if the transaction is already fully deserialized (i.e. it is an + /// Ethereum [`TxEnvelope`]). Otherwise, it will attempt to deserialize the + /// field and return the result wrapped in a `Some`. + pub fn deser_by_key( + &self, + key: &str, + ) -> Option> { + match self { + Self::Ethereum(_) => None, + Self::Unknown(inner) => inner.inner.deser_by_key(key), + } + } +} + +impl Encodable2718 for AnyTxEnvelope { + fn type_flag(&self) -> Option { + match self { + Self::Ethereum(t) => t.type_flag(), + Self::Unknown(inner) => Some(inner.ty()), + } + } + + fn encode_2718_len(&self) -> usize { + match self { + Self::Ethereum(t) => t.encode_2718_len(), + Self::Unknown(_) => 1, + } + } + + #[track_caller] + fn encode_2718(&self, out: &mut dyn alloy_primitives::bytes::BufMut) { + match self { + Self::Ethereum(t) => t.encode_2718(out), + Self::Unknown(inner) => { + panic!( + "Attempted to encode unknown transaction type: {}. This is not a bug in alloy. To encode or decode unknown transaction types, use a custom Transaction type and a custom Network implementation. See https://docs.rs/alloy-network/latest/alloy_network/ for network documentation.", + inner.as_ref().ty + ) + } + } + } + + fn trie_hash(&self) -> B256 { + match self { + Self::Ethereum(tx) => tx.trie_hash(), + Self::Unknown(inner) => inner.hash, + } + } +} + +impl Decodable2718 for AnyTxEnvelope { + fn typed_decode(ty: u8, buf: &mut &[u8]) -> alloy_eips::eip2718::Eip2718Result { + TxEnvelope::typed_decode(ty, buf).map(Self::Ethereum) + } + + fn fallback_decode(buf: &mut &[u8]) -> alloy_eips::eip2718::Eip2718Result { + TxEnvelope::fallback_decode(buf).map(Self::Ethereum) + } +} + +impl TransactionTrait for AnyTxEnvelope { + fn chain_id(&self) -> Option { + match self { + Self::Ethereum(inner) => inner.chain_id(), + Self::Unknown(inner) => inner.chain_id(), + } + } + + fn nonce(&self) -> u64 { + match self { + Self::Ethereum(inner) => inner.nonce(), + Self::Unknown(inner) => inner.nonce(), + } + } + + fn gas_limit(&self) -> u64 { + match self { + Self::Ethereum(inner) => inner.gas_limit(), + Self::Unknown(inner) => inner.gas_limit(), + } + } + + fn gas_price(&self) -> Option { + match self { + Self::Ethereum(inner) => inner.gas_price(), + Self::Unknown(inner) => inner.gas_price(), + } + } + + fn max_fee_per_gas(&self) -> u128 { + match self { + Self::Ethereum(inner) => inner.max_fee_per_gas(), + Self::Unknown(inner) => inner.max_fee_per_gas(), + } + } + + fn max_priority_fee_per_gas(&self) -> Option { + match self { + Self::Ethereum(inner) => inner.max_priority_fee_per_gas(), + Self::Unknown(inner) => inner.max_priority_fee_per_gas(), + } + } + + fn max_fee_per_blob_gas(&self) -> Option { + match self { + Self::Ethereum(inner) => inner.max_fee_per_blob_gas(), + Self::Unknown(inner) => inner.max_fee_per_blob_gas(), + } + } + + fn priority_fee_or_price(&self) -> u128 { + self.max_priority_fee_per_gas().or_else(|| self.gas_price()).unwrap_or_default() + } + + fn kind(&self) -> alloy_primitives::TxKind { + match self { + Self::Ethereum(inner) => inner.kind(), + Self::Unknown(inner) => inner.kind(), + } + } + + fn value(&self) -> U256 { + match self { + Self::Ethereum(inner) => inner.value(), + Self::Unknown(inner) => inner.value(), + } + } + + fn input(&self) -> &Bytes { + match self { + Self::Ethereum(inner) => inner.input(), + Self::Unknown(inner) => inner.input(), + } + } + + fn ty(&self) -> u8 { + match self { + Self::Ethereum(inner) => inner.ty(), + Self::Unknown(inner) => inner.ty(), + } + } + + fn access_list(&self) -> Option<&AccessList> { + match self { + Self::Ethereum(inner) => inner.access_list(), + Self::Unknown(inner) => inner.access_list(), + } + } + + fn blob_versioned_hashes(&self) -> Option<&[B256]> { + match self { + Self::Ethereum(inner) => inner.blob_versioned_hashes(), + Self::Unknown(inner) => inner.blob_versioned_hashes(), + } + } + + fn authorization_list(&self) -> Option<&[SignedAuthorization]> { + match self { + Self::Ethereum(inner) => inner.authorization_list(), + Self::Unknown(inner) => inner.authorization_list(), + } + } +} diff --git a/crates/network/src/any/mod.rs b/crates/network/src/any/mod.rs index 6ea0ec5c602..3e5891a122d 100644 --- a/crates/network/src/any/mod.rs +++ b/crates/network/src/any/mod.rs @@ -1,56 +1,52 @@ -use crate::Network; -use alloy_consensus::TxType; -use alloy_eips::eip2718::Eip2718Error; -use alloy_rpc_types_eth::{AnyTransactionReceipt, Transaction, TransactionRequest}; -use alloy_serde::WithOtherFields; -use core::fmt; - mod builder; -/// Transaction type for a catch-all network. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[doc(alias = "AnyTransactionType")] -pub struct AnyTxType(u8); - -impl fmt::Display for AnyTxType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "AnyTxType({})", self.0) - } -} - -impl TryFrom for AnyTxType { - type Error = Eip2718Error; +mod either; +pub use either::{AnyTxEnvelope, AnyTypedTransaction}; - fn try_from(value: u8) -> Result { - Ok(Self(value)) - } -} +mod unknowns; +pub use unknowns::{AnyTxType, UnknownTxEnvelope, UnknownTypedTransaction}; -impl From for u8 { - fn from(value: AnyTxType) -> Self { - value.0 - } -} +pub use alloy_consensus::{AnyHeader, AnyReceiptEnvelope}; -impl TryFrom for TxType { - type Error = Eip2718Error; +use crate::Network; +use alloy_rpc_types_eth::{AnyTransactionReceipt, Block, Transaction, TransactionRequest}; +use alloy_serde::WithOtherFields; - fn try_from(value: AnyTxType) -> Result { - value.0.try_into() - } -} +/// A catch-all header type for handling headers on multiple networks. +pub type AnyRpcHeader = alloy_rpc_types_eth::Header; -impl From for AnyTxType { - fn from(value: TxType) -> Self { - Self(value as u8) - } -} +/// A catch-all block type for handling blocks on multiple networks. +pub type AnyRpcBlock = + WithOtherFields>, AnyRpcHeader>>; /// 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. +/// `AnyNetwork`'s associated types allow for many different types of +/// transactions, using catch-all fields. This [`Network`] should be used +/// only when the application needs to support multiple networks via the same +/// codepaths without knowing the networks at compile time. +/// +/// ## Rough Edges +/// +/// Supporting arbitrary unknown types is hard, and users of this network +/// should be aware of the following: +/// +/// - The implementation of [`Decodable2718`] for [`AnyTxEnvelope`] will not work for non-Ethereum +/// transaction types. It will succesfully decode an Ethereum [`TxEnvelope`], but will decode only +/// the type for any unknown transaction type. It will also leave the buffer unconsumed, which +/// will cause further deserialization to produce erroneous results. +/// - The implementation of [`Encodable2718`] for [`AnyTypedTransaction`] will not work for +/// non-Ethereum transaction types. It will encode the type for any unknown transaction type, but +/// will not encode any other fields. This is symmetric with the decoding behavior, but still +/// erroneous. +/// - The [`TransactionRequest`] will build ONLY Ethereum types. It will error when attempting to +/// build any unknown type. +/// - The [`Network::TransactionResponse`] may deserialize unknown metadata fields into the inner +/// [`AnyTxEnvelope`], rather than into the outer [`WithOtherFields`]. +/// +/// [`Decodable2718`]: alloy_eips::eip2718::Decodable2718 +/// [`Encodable2718`]: alloy_eips::eip2718::Encodable2718 +/// [`TxEnvelope`]: alloy_consensus::TxEnvelope #[derive(Clone, Copy, Debug)] pub struct AnyNetwork { _private: (), @@ -59,9 +55,9 @@ pub struct AnyNetwork { impl Network for AnyNetwork { type TxType = AnyTxType; - type TxEnvelope = alloy_consensus::TxEnvelope; + type TxEnvelope = AnyTxEnvelope; - type UnsignedTx = alloy_consensus::TypedTransaction; + type UnsignedTx = AnyTypedTransaction; type ReceiptEnvelope = alloy_consensus::AnyReceiptEnvelope; @@ -69,11 +65,11 @@ impl Network for AnyNetwork { type TransactionRequest = WithOtherFields; - type TransactionResponse = WithOtherFields; + type TransactionResponse = WithOtherFields>; type ReceiptResponse = AnyTransactionReceipt; - type HeaderResponse = alloy_rpc_types_eth::AnyNetworkHeader; + type HeaderResponse = AnyRpcHeader; - type BlockResponse = alloy_rpc_types_eth::AnyNetworkBlock; + type BlockResponse = AnyRpcBlock; } diff --git a/crates/network/src/any/unknowns.rs b/crates/network/src/any/unknowns.rs new file mode 100644 index 00000000000..a23fec3f6a4 --- /dev/null +++ b/crates/network/src/any/unknowns.rs @@ -0,0 +1,274 @@ +use core::fmt; +use std::sync::OnceLock; + +use alloy_consensus::TxType; +use alloy_eips::{eip2718::Eip2718Error, eip7702::SignedAuthorization}; +use alloy_primitives::{Address, Bytes, TxKind, B256}; +use alloy_rpc_types_eth::AccessList; + +/// Transaction type for a catch-all network. +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[doc(alias = "AnyTransactionType")] +pub struct AnyTxType(pub u8); + +impl fmt::Display for AnyTxType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "AnyTxType({})", self.0) + } +} + +impl TryFrom for AnyTxType { + type Error = Eip2718Error; + + fn try_from(value: u8) -> Result { + Ok(Self(value)) + } +} + +impl From<&AnyTxType> for u8 { + fn from(value: &AnyTxType) -> Self { + value.0 + } +} + +impl From for u8 { + fn from(value: AnyTxType) -> Self { + value.0 + } +} + +impl TryFrom for TxType { + type Error = Eip2718Error; + + fn try_from(value: AnyTxType) -> Result { + value.0.try_into() + } +} + +impl From for AnyTxType { + fn from(value: TxType) -> Self { + Self(value as u8) + } +} + +/// Memoization for deserialization of [`UnknownTxEnvelope`], +/// [`UnknownTypedTransaction`] [`AnyTxEnvelope`], [`AnyTypedTransaction`]. +/// Setting these manually is discouraged, however the fields are left public +/// for power users :) +/// +/// [`AnyTxEnvelope`]: crate::AnyTxEnvelope +/// [`AnyTypedTransaction`]: crate::AnyTypedTransaction +#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[allow(unnameable_types)] +pub struct DeserMemo { + pub input: OnceLock, + pub access_list: OnceLock, + pub blob_versioned_hashes: OnceLock>, + pub authorization_list: OnceLock>, +} + +/// A typed transaction of an unknown Network +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[doc(alias = "UnknownTypedTx")] +pub struct UnknownTypedTransaction { + #[serde(rename = "type")] + /// Transaction type. + pub ty: AnyTxType, + + /// Additional fields. + #[serde(flatten)] + pub fields: std::collections::BTreeMap, + + /// Memoization for deserialization. + #[serde(skip, default)] + pub memo: DeserMemo, +} + +impl UnknownTypedTransaction { + /// Select a field by key and attempt to deserialize it. + /// + /// This method will return `None` if the key is not present in the fields, + /// or if the transaction is already fully deserialized (i.e. it is an + /// Ethereum [`TxEnvelope`]). Otherwise, it will attempt to deserialize the + /// field and return the result wrapped in a `Some`. + /// + /// [`TxEnvelope`]: alloy_consensus::TxEnvelope + pub fn deser_by_key( + &self, + key: &str, + ) -> Option> { + self.fields.get(key).cloned().map(serde_json::from_value) + } +} + +impl alloy_consensus::Transaction for UnknownTypedTransaction { + fn chain_id(&self) -> Option { + self.deser_by_key("chainId").and_then(Result::ok) + } + + fn nonce(&self) -> u64 { + self.deser_by_key("nonce").and_then(Result::ok).unwrap_or_default() + } + + fn gas_limit(&self) -> u64 { + self.deser_by_key("gasLimit").and_then(Result::ok).unwrap_or_default() + } + + fn gas_price(&self) -> Option { + self.deser_by_key("gasPrice").and_then(Result::ok) + } + + fn max_fee_per_gas(&self) -> u128 { + self.deser_by_key("maxFeePerGas").and_then(Result::ok).unwrap_or_default() + } + + fn max_priority_fee_per_gas(&self) -> Option { + self.deser_by_key("maxPriorityFeePerGas").and_then(Result::ok) + } + + fn max_fee_per_blob_gas(&self) -> Option { + self.deser_by_key("maxFeePerBlobGas").and_then(Result::ok) + } + + fn priority_fee_or_price(&self) -> u128 { + self.gas_price().or(self.max_priority_fee_per_gas()).unwrap_or_default() + } + + fn kind(&self) -> TxKind { + self.fields + .get("to") + .or(Some(&serde_json::Value::Null)) + .and_then(|v| { + if v.is_null() { + Some(TxKind::Create) + } else { + v.as_str().and_then(|v| v.parse::
().ok().map(Into::into)) + } + }) + .unwrap_or_default() + } + + fn value(&self) -> alloy_primitives::U256 { + self.deser_by_key("value").and_then(Result::ok).unwrap_or_default() + } + + fn input(&self) -> &Bytes { + self.memo + .input + .get_or_init(|| self.deser_by_key("input").and_then(Result::ok).unwrap_or_default()) + } + + fn ty(&self) -> u8 { + self.ty.0 + } + + fn access_list(&self) -> Option<&AccessList> { + if self.fields.contains_key("accessList") { + Some(self.memo.access_list.get_or_init(|| { + self.deser_by_key("accessList").and_then(Result::ok).unwrap_or_default() + })) + } else { + None + } + } + + fn blob_versioned_hashes(&self) -> Option<&[B256]> { + if self.fields.contains_key("blobVersionedHashes") { + Some(self.memo.blob_versioned_hashes.get_or_init(|| { + self.deser_by_key("blobVersionedHashes").and_then(Result::ok).unwrap_or_default() + })) + } else { + None + } + } + + fn authorization_list(&self) -> Option<&[SignedAuthorization]> { + if self.fields.contains_key("authorizationList") { + Some(self.memo.authorization_list.get_or_init(|| { + self.deser_by_key("authorizationList").and_then(Result::ok).unwrap_or_default() + })) + } else { + None + } + } +} + +/// A transaction envelope from an unknown network. +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[doc(alias = "UnknownTransactionEnvelope")] +pub struct UnknownTxEnvelope { + /// Transaction hash. + pub hash: B256, + + /// Transaction type. + #[serde(flatten)] + pub inner: UnknownTypedTransaction, +} + +impl AsRef for UnknownTxEnvelope { + fn as_ref(&self) -> &UnknownTypedTransaction { + &self.inner + } +} + +impl alloy_consensus::Transaction for UnknownTxEnvelope { + fn chain_id(&self) -> Option { + self.inner.chain_id() + } + + fn nonce(&self) -> u64 { + self.inner.nonce() + } + + fn gas_limit(&self) -> u64 { + self.inner.gas_limit() + } + + fn gas_price(&self) -> Option { + self.inner.gas_price() + } + + fn max_fee_per_gas(&self) -> u128 { + self.inner.max_fee_per_gas() + } + + fn max_priority_fee_per_gas(&self) -> Option { + self.inner.max_priority_fee_per_gas() + } + + fn max_fee_per_blob_gas(&self) -> Option { + self.inner.max_fee_per_blob_gas() + } + + fn priority_fee_or_price(&self) -> u128 { + self.inner.priority_fee_or_price() + } + + fn kind(&self) -> TxKind { + self.inner.kind() + } + + fn value(&self) -> alloy_primitives::U256 { + self.inner.value() + } + + fn input(&self) -> &Bytes { + self.inner.input() + } + + fn ty(&self) -> u8 { + self.inner.ty() + } + + fn access_list(&self) -> Option<&AccessList> { + self.inner.access_list() + } + + fn blob_versioned_hashes(&self) -> Option<&[B256]> { + self.inner.blob_versioned_hashes() + } + + fn authorization_list(&self) -> Option<&[SignedAuthorization]> { + self.inner.authorization_list() + } +} diff --git a/crates/network/src/lib.rs b/crates/network/src/lib.rs index ac7d911cb93..0d6873971aa 100644 --- a/crates/network/src/lib.rs +++ b/crates/network/src/lib.rs @@ -21,7 +21,10 @@ mod ethereum; pub use ethereum::{Ethereum, EthereumWallet}; mod any; -pub use any::{AnyNetwork, AnyTxType}; +pub use any::{ + AnyHeader, AnyNetwork, AnyReceiptEnvelope, AnyRpcBlock, AnyRpcHeader, AnyTxEnvelope, AnyTxType, + AnyTypedTransaction, UnknownTxEnvelope, UnknownTypedTransaction, +}; pub use alloy_eips::eip2718; pub use alloy_network_primitives::{ @@ -80,7 +83,7 @@ pub trait Network: Debug + Clone + Copy + Sized + Send + Sync + 'static { /// The JSON body of a transaction response. #[doc(alias = "TxResponse")] - type TransactionResponse: RpcObject + TransactionResponse; + type TransactionResponse: RpcObject + TransactionResponse + AsRef; /// The JSON body of a transaction receipt. #[doc(alias = "TransactionReceiptResponse", alias = "TxReceiptResponse")] diff --git a/crates/provider/src/fillers/gas.rs b/crates/provider/src/fillers/gas.rs index fdcc1921984..e39a38e141e 100644 --- a/crates/provider/src/fillers/gas.rs +++ b/crates/provider/src/fillers/gas.rs @@ -254,7 +254,7 @@ where mod tests { use super::*; use crate::ProviderBuilder; - use alloy_consensus::{SidecarBuilder, SimpleCoder}; + use alloy_consensus::{SidecarBuilder, SimpleCoder, Transaction}; use alloy_eips::eip4844::DATA_GAS_PER_BLOB; use alloy_primitives::{address, U256}; use alloy_rpc_types_eth::TransactionRequest; @@ -317,7 +317,7 @@ mod tests { let tx = provider.get_transaction_by_hash(receipt.transaction_hash).await.unwrap().unwrap(); - assert!(tx.max_fee_per_blob_gas.unwrap() >= BLOB_TX_MIN_BLOB_GASPRICE); + assert!(tx.max_fee_per_blob_gas().unwrap() >= BLOB_TX_MIN_BLOB_GASPRICE); assert_eq!(receipt.gas_used, 21000); assert_eq!( receipt.blob_gas_used.expect("Expected to be EIP-4844 transaction"), @@ -345,7 +345,7 @@ mod tests { let tx = provider.get_transaction_by_hash(receipt.transaction_hash).await.unwrap().unwrap(); - assert!(tx.max_fee_per_blob_gas.unwrap() >= BLOB_TX_MIN_BLOB_GASPRICE); + assert!(tx.max_fee_per_blob_gas().unwrap() >= BLOB_TX_MIN_BLOB_GASPRICE); assert_eq!(receipt.gas_used, 21000); assert_eq!( receipt.blob_gas_used.expect("Expected to be EIP-4844 transaction"), diff --git a/crates/provider/src/fillers/nonce.rs b/crates/provider/src/fillers/nonce.rs index 63f6995d0e2..9267226b737 100644 --- a/crates/provider/src/fillers/nonce.rs +++ b/crates/provider/src/fillers/nonce.rs @@ -172,6 +172,7 @@ impl TxFiller for NonceFiller { mod tests { use super::*; use crate::{ProviderBuilder, WalletProvider}; + use alloy_consensus::Transaction; use alloy_primitives::{address, U256}; use alloy_rpc_types_eth::TransactionRequest; @@ -271,7 +272,7 @@ mod tests { .await .expect("failed to fetch tx") .expect("tx not included"); - assert_eq!(mined_tx.nonce, 0); + assert_eq!(mined_tx.nonce(), 0); let pending = provider.send_transaction(tx).await.unwrap(); let tx_hash = pending.watch().await.unwrap(); @@ -280,6 +281,6 @@ mod tests { .await .expect("fail to fetch tx") .expect("tx didn't finalize"); - assert_eq!(mined_tx.nonce, 1); + assert_eq!(mined_tx.nonce(), 1); } } diff --git a/crates/provider/src/provider/trait.rs b/crates/provider/src/provider/trait.rs index 61d7c2080dc..45cdd09a38e 100644 --- a/crates/provider/src/provider/trait.rs +++ b/crates/provider/src/provider/trait.rs @@ -1084,6 +1084,7 @@ mod tests { use super::*; use crate::{builder, ProviderBuilder, WalletProvider}; + use alloy_consensus::Transaction; use alloy_network::AnyNetwork; use alloy_node_bindings::Anvil; use alloy_primitives::{address, b256, bytes, keccak256}; @@ -1626,7 +1627,7 @@ mod tests { .await .expect("failed to fetch tx") .expect("tx not included"); - assert_eq!(tx.input, bytes!("deadbeef")); + assert_eq!(tx.input(), &bytes!("deadbeef")); } #[tokio::test] diff --git a/crates/rpc-types-eth/src/block.rs b/crates/rpc-types-eth/src/block.rs index ff4c59d99de..de29189b7bc 100644 --- a/crates/rpc-types-eth/src/block.rs +++ b/crates/rpc-types-eth/src/block.rs @@ -15,10 +15,10 @@ use alloy_primitives::{Address, BlockHash, Bytes, Sealable, B256, U256}; use alloy_rlp::Encodable; /// Block representation -#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct Block { +pub struct Block, H = Header> { /// Header of the block. #[cfg_attr(feature = "serde", serde(flatten))] pub header: H, @@ -40,6 +40,18 @@ pub struct Block { pub withdrawals: Option, } +// cannot derive, as the derive impl would constrain `where T: Default` +impl Default for Block { + fn default() -> Self { + Self { + header: Default::default(), + uncles: Default::default(), + transactions: Default::default(), + withdrawals: Default::default(), + } + } +} + impl Block { /// Converts a block with Tx hashes into a full block. pub fn into_full_block(self, txs: Vec) -> Self { diff --git a/crates/rpc-types-eth/src/lib.rs b/crates/rpc-types-eth/src/lib.rs index bdef3bca35a..4b9edcd31a0 100644 --- a/crates/rpc-types-eth/src/lib.rs +++ b/crates/rpc-types-eth/src/lib.rs @@ -13,26 +13,16 @@ extern crate alloc; pub use alloy_eips::eip4895::{Withdrawal, Withdrawals}; +pub use alloy_network_primitives::{ + BlockTransactionHashes, BlockTransactions, BlockTransactionsKind, +}; + mod account; pub use account::*; mod block; pub use block::*; -#[cfg(feature = "serde")] -use alloy_serde::WithOtherFields; - -/// A catch-all header type for handling headers on multiple networks. -pub type AnyNetworkHeader = Header; - -/// A catch-all block type for handling blocks on multiple networks. -#[cfg(feature = "serde")] -pub type AnyNetworkBlock = WithOtherFields, AnyNetworkHeader>>; - -pub use alloy_network_primitives::{ - BlockTransactionHashes, BlockTransactions, BlockTransactionsKind, -}; - mod call; pub use call::{Bundle, EthCallResponse, StateContext, TransactionIndex}; diff --git a/crates/rpc-types-eth/src/transaction/common.rs b/crates/rpc-types-eth/src/transaction/common.rs index f729c0f74f1..b28bc0fd956 100644 --- a/crates/rpc-types-eth/src/transaction/common.rs +++ b/crates/rpc-types-eth/src/transaction/common.rs @@ -2,6 +2,7 @@ //! when working with RPC types, such as [Transaction] use crate::Transaction; +use alloy_network_primitives::TransactionResponse; use alloy_primitives::{BlockHash, TxHash}; /// Additional fields in the context of a block that contains this transaction. @@ -31,7 +32,7 @@ impl TransactionInfo { impl From<&Transaction> for TransactionInfo { fn from(tx: &Transaction) -> Self { Self { - hash: Some(tx.hash), + hash: Some(tx.tx_hash()), index: tx.transaction_index, block_hash: tx.block_hash, block_number: tx.block_number, diff --git a/crates/rpc-types-eth/src/transaction/error.rs b/crates/rpc-types-eth/src/transaction/error.rs index eb4753f5fb3..440afce683b 100644 --- a/crates/rpc-types-eth/src/transaction/error.rs +++ b/crates/rpc-types-eth/src/transaction/error.rs @@ -1,74 +1,9 @@ -use core::num::TryFromIntError; - use alloc::string::String; /// Error variants when converting from [crate::Transaction] to [alloy_consensus::Signed] /// transaction. #[derive(Debug, derive_more::Display)] pub enum ConversionError { - /// Error during EIP-2718 transaction coding. - #[display("{_0}")] - Eip2718Error(alloy_eips::eip2718::Eip2718Error), - /// [`alloy_primitives::SignatureError`]. - #[display("{_0}")] - SignatureError(alloy_primitives::SignatureError), - /// Missing signature for transaction. - #[display("missing signature for transaction")] - MissingSignature, - /// Missing y parity in signature. - #[display("missing y parity in signature")] - MissingYParity, - /// Invalid signature - #[display("invalid signature")] - InvalidSignature, - /// Missing `chainId` field for EIP-1559 transaction. - #[display("missing `chainId` field for EIP-155 transaction")] - MissingChainId, - /// Missing `gasPrice` field for Legacy transaction. - #[display("missing `gasPrice` field for Legacy transaction")] - MissingGasPrice, - /// Missing `accessList` field for EIP-2930 transaction. - #[display("missing `accessList` field for EIP-2930 transaction")] - MissingAccessList, - /// Missing `maxFeePerGas` field for EIP-1559 transaction. - #[display("missing `maxFeePerGas` field for EIP-1559 transaction")] - MissingMaxFeePerGas, - /// Missing `maxPriorityFeePerGas` field for EIP-1559 transaction. - #[display("missing `maxPriorityFeePerGas` field for EIP-1559 transaction")] - MissingMaxPriorityFeePerGas, - /// Missing `maxFeePerBlobGas` field for EIP-1559 transaction. - #[display("missing `maxFeePerBlobGas` field for EIP-1559 transaction")] - MissingMaxFeePerBlobGas, - /// Missing `to` field for EIP-4844 transaction. - #[display("missing `to` field for EIP-4844 transaction")] - MissingTo, - /// Missing `blobVersionedHashes` field for EIP-4844 transaction. - #[display("missing `blobVersionedHashes` field for EIP-4844 transaction")] - MissingBlobVersionedHashes, - /// Missing `authorizationList` field for EIP-7702 transaction. - #[display("missing `authorizationList` field for EIP-7702 transaction")] - MissingAuthorizationList, - /// Missing full transactions required for block decoding - #[display("missing full transactions required for block decoding")] - MissingFullTransactions, - /// Base fee per gas integer conversion error - #[display("base fee per gas integer conversion error: {_0}")] - BaseFeePerGasConversion(TryFromIntError), - /// Gas limit integer conversion error - #[display("gas limit integer conversion error: {_0}")] - GasLimitConversion(TryFromIntError), - /// Gas used integer conversion error - #[display("gas used integer conversion error: {_0}")] - GasUsedConversion(TryFromIntError), - /// Missing block number - #[display("missing block number")] - MissingBlockNumber, - /// Blob gas used integer conversion error - #[display("blob gas used integer conversion error: {_0}")] - BlobGasUsedConversion(TryFromIntError), - /// Excess blob gas integer conversion error - #[display("excess blob gas integer conversion error: {_0}")] - ExcessBlobGasConversion(TryFromIntError), /// A custom Conversion Error that doesn't fit other categories. #[display("conversion error: {_0}")] Custom(String), @@ -77,27 +12,6 @@ pub enum ConversionError { #[cfg(feature = "std")] impl std::error::Error for ConversionError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::Eip2718Error(err) => Some(err), - Self::SignatureError(err) => Some(err), - Self::BaseFeePerGasConversion(err) - | Self::GasLimitConversion(err) - | Self::GasUsedConversion(err) - | Self::BlobGasUsedConversion(err) - | Self::ExcessBlobGasConversion(err) => Some(err), - _ => None, - } - } -} - -impl From for ConversionError { - fn from(err: alloy_eips::eip2718::Eip2718Error) -> Self { - Self::Eip2718Error(err) - } -} - -impl From for ConversionError { - fn from(err: alloy_primitives::SignatureError) -> Self { - Self::SignatureError(err) + None } } diff --git a/crates/rpc-types-eth/src/transaction/mod.rs b/crates/rpc-types-eth/src/transaction/mod.rs index 3c4625290e8..ec753b9accc 100644 --- a/crates/rpc-types-eth/src/transaction/mod.rs +++ b/crates/rpc-types-eth/src/transaction/mod.rs @@ -1,14 +1,11 @@ //! RPC types for transactions use alloy_consensus::{ - SignableTransaction, Signed, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxEip7702, - TxEnvelope, TxLegacy, TxType, + Signed, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxEip7702, TxEnvelope, TxLegacy, }; use alloy_eips::eip7702::SignedAuthorization; use alloy_network_primitives::TransactionResponse; -use alloy_primitives::{Address, BlockHash, Bytes, ChainId, TxHash, TxKind, B256, U256}; - -use alloc::vec::Vec; +use alloy_primitives::{Address, BlockHash, Bytes, ChainId, TxKind, B256, U256}; pub use alloy_consensus::BlobTransactionSidecar; pub use alloy_eips::{ @@ -31,169 +28,63 @@ pub use receipt::AnyTransactionReceipt; pub mod request; pub use request::{TransactionInput, TransactionRequest}; -mod signature; -pub use signature::{Parity, Signature}; - -pub use alloy_consensus::{AnyReceiptEnvelope, Receipt, ReceiptEnvelope, ReceiptWithBloom}; +pub use alloy_consensus::{ + AnyReceiptEnvelope, Receipt, ReceiptEnvelope, ReceiptWithBloom, Transaction as TransactionTrait, +}; /// Transaction object used in RPC #[derive(Clone, Debug, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] +// #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[doc(alias = "Tx")] -pub struct Transaction { - /// Hash - pub hash: TxHash, - /// Nonce - #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] - pub nonce: u64, - /// Block hash +pub struct Transaction { + /// The inner transaction object + #[cfg_attr(feature = "serde", serde(flatten))] + pub inner: T, + + /// Hash of block where transaction was included, `None` if pending #[cfg_attr(feature = "serde", serde(default))] pub block_hash: Option, - /// Block number + + /// Number of block where transaction was included, `None` if pending #[cfg_attr(feature = "serde", serde(default, with = "alloy_serde::quantity::opt"))] pub block_number: Option, + /// Transaction Index #[cfg_attr(feature = "serde", serde(default, with = "alloy_serde::quantity::opt"))] pub transaction_index: Option, + /// Sender pub from: Address, - /// Recipient - pub to: Option
, - /// Transferred value - pub value: U256, - /// Gas Price - #[cfg_attr( - feature = "serde", - serde( - default, - skip_serializing_if = "Option::is_none", - with = "alloy_serde::quantity::opt" - ) - )] - pub gas_price: Option, - /// Gas amount - #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] - pub gas: u64, - /// Max BaseFeePerGas the user is willing to pay. - #[cfg_attr( - feature = "serde", - serde( - default, - skip_serializing_if = "Option::is_none", - with = "alloy_serde::quantity::opt" - ) - )] - pub max_fee_per_gas: Option, - /// The miner's tip. - #[cfg_attr( - feature = "serde", - serde( - default, - skip_serializing_if = "Option::is_none", - with = "alloy_serde::quantity::opt" - ) - )] - pub max_priority_fee_per_gas: Option, - /// Configured max fee per blob gas for eip-4844 transactions - #[cfg_attr( - feature = "serde", - serde( - default, - skip_serializing_if = "Option::is_none", - with = "alloy_serde::quantity::opt" - ) - )] - pub max_fee_per_blob_gas: Option, - /// Data - pub input: Bytes, - /// All _flattened_ fields of the transaction signature. - /// - /// Note: this is an option so special transaction types without a signature (e.g. ) can be supported. - #[cfg_attr(feature = "serde", serde(flatten, skip_serializing_if = "Option::is_none"))] - pub signature: Option, - /// The chain id of the transaction, if any. - #[cfg_attr( - feature = "serde", - serde( - default, - skip_serializing_if = "Option::is_none", - with = "alloy_serde::quantity::opt" - ) - )] - pub chain_id: Option, - /// Contains the blob hashes for eip-4844 transactions. - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub blob_versioned_hashes: Option>, - /// EIP2930 - /// - /// Pre-pay to warm storage access. - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub access_list: Option, - /// EIP2718 - /// - /// Transaction type, - /// Some(4) for EIP-7702 transaction, Some(3) for EIP-4844 transaction, Some(2) for EIP-1559 - /// transaction, Some(1) for AccessList transaction, None or Some(0) for Legacy - #[cfg_attr( - feature = "serde", - serde( - default, - rename = "type", - skip_serializing_if = "Option::is_none", - with = "alloy_serde::quantity::opt" - ) - )] - #[doc(alias = "tx_type")] - pub transaction_type: Option, - /// The signed authorization list is a list of tuples that store the address to code which the - /// signer desires to execute in the context of their EOA and their signature. - #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] - pub authorization_list: Option>, } -impl Transaction { +impl AsRef for Transaction { + fn as_ref(&self) -> &T { + &self.inner + } +} + +impl Transaction +where + T: TransactionTrait, +{ /// Returns true if the transaction is a legacy or 2930 transaction. - pub const fn is_legacy_gas(&self) -> bool { - self.gas_price.is_none() + pub fn is_legacy_gas(&self) -> bool { + self.inner.gas_price().is_some() } +} +impl Transaction +where + T: Into, +{ /// Converts [Transaction] into [TransactionRequest]. /// - /// During this conversion data for [TransactionRequest::sidecar] is not populated as it is not - /// part of [Transaction]. + /// During this conversion data for [TransactionRequest::sidecar] is not + /// populated as it is not part of [Transaction]. pub fn into_request(self) -> TransactionRequest { - let gas_price = match (self.gas_price, self.max_fee_per_gas) { - (Some(gas_price), None) => Some(gas_price), - // EIP-1559 transactions include deprecated `gasPrice` field displaying gas used by - // transaction. - // Setting this field for resulted tx request will result in it being invalid - (_, Some(_)) => None, - // unreachable - (None, None) => None, - }; - - let to = self.to.map(TxKind::Call); - - TransactionRequest { - from: Some(self.from), - to, - gas: Some(self.gas), - gas_price, - value: Some(self.value), - input: self.input.into(), - nonce: Some(self.nonce), - chain_id: self.chain_id, - access_list: self.access_list, - transaction_type: self.transaction_type, - max_fee_per_gas: self.max_fee_per_gas, - max_priority_fee_per_gas: self.max_priority_fee_per_gas, - max_fee_per_blob_gas: self.max_fee_per_blob_gas, - blob_versioned_hashes: self.blob_versioned_hashes, - sidecar: None, - authorization_list: self.authorization_list, - } + self.inner.into() } } @@ -201,18 +92,12 @@ impl TryFrom for Signed { type Error = ConversionError; fn try_from(tx: Transaction) -> Result { - let signature = tx.signature.ok_or(ConversionError::MissingSignature)?.try_into()?; - - let tx = TxLegacy { - chain_id: tx.chain_id, - nonce: tx.nonce, - gas_price: tx.gas_price.ok_or(ConversionError::MissingGasPrice)?, - gas_limit: tx.gas, - to: tx.to.into(), - value: tx.value, - input: tx.input, - }; - Ok(tx.into_signed(signature)) + match tx.inner { + TxEnvelope::Legacy(tx) => Ok(tx), + _ => { + Err(ConversionError::Custom(format!("expected Legacy, got {}", tx.inner.tx_type()))) + } + } } } @@ -220,22 +105,13 @@ impl TryFrom for Signed { type Error = ConversionError; fn try_from(tx: Transaction) -> Result { - let signature = tx.signature.ok_or(ConversionError::MissingSignature)?.try_into()?; - - let tx = TxEip1559 { - chain_id: tx.chain_id.ok_or(ConversionError::MissingChainId)?, - nonce: tx.nonce, - max_fee_per_gas: tx.max_fee_per_gas.ok_or(ConversionError::MissingMaxFeePerGas)?, - max_priority_fee_per_gas: tx - .max_priority_fee_per_gas - .ok_or(ConversionError::MissingMaxPriorityFeePerGas)?, - gas_limit: tx.gas, - to: tx.to.into(), - value: tx.value, - input: tx.input, - access_list: tx.access_list.unwrap_or_default(), - }; - Ok(tx.into_signed(signature)) + match tx.inner { + TxEnvelope::Eip1559(tx) => Ok(tx), + _ => Err(ConversionError::Custom(format!( + "expected Eip1559, got {}", + tx.inner.tx_type() + ))), + } } } @@ -243,19 +119,13 @@ impl TryFrom for Signed { type Error = ConversionError; fn try_from(tx: Transaction) -> Result { - let signature = tx.signature.ok_or(ConversionError::MissingSignature)?.try_into()?; - - let tx = TxEip2930 { - chain_id: tx.chain_id.ok_or(ConversionError::MissingChainId)?, - nonce: tx.nonce, - gas_price: tx.gas_price.ok_or(ConversionError::MissingGasPrice)?, - gas_limit: tx.gas, - to: tx.to.into(), - value: tx.value, - input: tx.input, - access_list: tx.access_list.ok_or(ConversionError::MissingAccessList)?, - }; - Ok(tx.into_signed(signature)) + match tx.inner { + TxEnvelope::Eip2930(tx) => Ok(tx), + _ => Err(ConversionError::Custom(format!( + "expected Eip2930, got {}", + tx.inner.tx_type() + ))), + } } } @@ -263,27 +133,11 @@ impl TryFrom for Signed { type Error = ConversionError; fn try_from(tx: Transaction) -> Result { - let signature = tx.signature.ok_or(ConversionError::MissingSignature)?.try_into()?; - let tx = TxEip4844 { - chain_id: tx.chain_id.ok_or(ConversionError::MissingChainId)?, - nonce: tx.nonce, - max_fee_per_gas: tx.max_fee_per_gas.ok_or(ConversionError::MissingMaxFeePerGas)?, - max_priority_fee_per_gas: tx - .max_priority_fee_per_gas - .ok_or(ConversionError::MissingMaxPriorityFeePerGas)?, - gas_limit: tx.gas, - to: tx.to.ok_or(ConversionError::MissingTo)?, - value: tx.value, - input: tx.input, - access_list: tx.access_list.unwrap_or_default(), - blob_versioned_hashes: tx - .blob_versioned_hashes - .ok_or(ConversionError::MissingBlobVersionedHashes)?, - max_fee_per_blob_gas: tx - .max_fee_per_blob_gas - .ok_or(ConversionError::MissingMaxFeePerBlobGas)?, - }; - Ok(tx.into_signed(signature)) + let tx: Signed = tx.try_into()?; + + let (tx, sig, hash) = tx.into_parts(); + + Ok(Self::new_unchecked(tx.into(), sig, hash)) } } @@ -291,11 +145,13 @@ impl TryFrom for Signed { type Error = ConversionError; fn try_from(tx: Transaction) -> Result { - let tx: Signed = tx.try_into()?; - let (inner, signature, _) = tx.into_parts(); - let tx: TxEip4844Variant = inner.into(); - - Ok(tx.into_signed(signature)) + match tx.inner { + TxEnvelope::Eip4844(tx) => Ok(tx), + _ => Err(ConversionError::Custom(format!( + "expected TxEip4844Variant, got {}", + tx.inner.tx_type() + ))), + } } } @@ -303,110 +159,88 @@ impl TryFrom for Signed { type Error = ConversionError; fn try_from(tx: Transaction) -> Result { - let signature = tx.signature.ok_or(ConversionError::MissingSignature)?.try_into()?; - let tx = TxEip7702 { - chain_id: tx.chain_id.ok_or(ConversionError::MissingChainId)?, - nonce: tx.nonce, - gas_limit: tx.gas, - max_fee_per_gas: tx.max_fee_per_gas.ok_or(ConversionError::MissingMaxFeePerGas)?, - max_priority_fee_per_gas: tx - .max_priority_fee_per_gas - .ok_or(ConversionError::MissingMaxPriorityFeePerGas)?, - to: tx.to.ok_or(ConversionError::MissingTo)?, - value: tx.value, - access_list: tx.access_list.ok_or(ConversionError::MissingAccessList)?, - authorization_list: tx - .authorization_list - .ok_or(ConversionError::MissingAuthorizationList)?, - input: tx.input, - }; - Ok(tx.into_signed(signature)) + match tx.inner { + TxEnvelope::Eip7702(tx) => Ok(tx), + _ => Err(ConversionError::Custom(format!( + "expected Eip7702, got {}", + tx.inner.tx_type() + ))), + } } } -impl TryFrom for TxEnvelope { - type Error = ConversionError; - - fn try_from(tx: Transaction) -> Result { - match tx.transaction_type.unwrap_or_default().try_into()? { - TxType::Legacy => Ok(Self::Legacy(tx.try_into()?)), - TxType::Eip1559 => Ok(Self::Eip1559(tx.try_into()?)), - TxType::Eip2930 => Ok(Self::Eip2930(tx.try_into()?)), - TxType::Eip4844 => Ok(Self::Eip4844(tx.try_into()?)), - TxType::Eip7702 => Ok(Self::Eip7702(tx.try_into()?)), - } +impl From for TxEnvelope { + fn from(tx: Transaction) -> Self { + tx.inner } } -impl alloy_consensus::Transaction for Transaction { +impl TransactionTrait for Transaction { fn chain_id(&self) -> Option { - self.chain_id + self.inner.chain_id() } fn nonce(&self) -> u64 { - self.nonce + self.inner.nonce() } fn gas_limit(&self) -> u64 { - self.gas + self.inner.gas_limit() } fn gas_price(&self) -> Option { - self.gas_price + self.inner.gas_price() } fn max_fee_per_gas(&self) -> u128 { - self.max_fee_per_gas.unwrap_or_else(|| self.gas_price.unwrap_or_default()) + self.inner.max_fee_per_gas() } fn max_priority_fee_per_gas(&self) -> Option { - self.max_priority_fee_per_gas + self.inner.max_priority_fee_per_gas() } fn max_fee_per_blob_gas(&self) -> Option { - self.max_fee_per_blob_gas + self.inner.max_fee_per_blob_gas() } fn priority_fee_or_price(&self) -> u128 { - debug_assert!( - self.max_fee_per_gas.is_some() || self.gas_price.is_some(), - "mutually exclusive fields" - ); - self.max_fee_per_gas.unwrap_or_else(|| self.gas_price.unwrap_or_default()) + self.inner.priority_fee_or_price() } fn kind(&self) -> TxKind { - self.to.into() + self.inner.kind() } fn value(&self) -> U256 { - self.value + self.inner.value() } fn input(&self) -> &Bytes { - &self.input + self.inner.input() } fn ty(&self) -> u8 { - self.transaction_type.unwrap_or_default() + self.inner.ty() } fn access_list(&self) -> Option<&AccessList> { - self.access_list.as_ref() + self.inner.access_list() } fn blob_versioned_hashes(&self) -> Option<&[B256]> { - self.blob_versioned_hashes.as_deref() + self.inner.blob_versioned_hashes() } fn authorization_list(&self) -> Option<&[SignedAuthorization]> { - self.authorization_list.as_deref() + self.inner.authorization_list() } } -impl TransactionResponse for Transaction { +impl TransactionResponse for Transaction { fn tx_hash(&self) -> B256 { - self.hash + Default::default() + // self.hash } fn block_hash(&self) -> Option { @@ -429,128 +263,6 @@ impl TransactionResponse for Transaction { #[cfg(test)] mod tests { use super::*; - use alloy_primitives::Signature as AlloySignature; - use arbitrary::Arbitrary; - use core::str::FromStr; - use rand::Rng; - use similar_asserts::assert_eq; - - #[test] - fn arbitrary_transaction() { - let mut bytes = [0u8; 1024]; - rand::thread_rng().fill(bytes.as_mut_slice()); - let _: Transaction = - Transaction::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap(); - } - - #[test] - #[cfg(feature = "serde")] - fn serde_transaction() { - let transaction = Transaction { - hash: B256::with_last_byte(1), - nonce: 2, - block_hash: Some(B256::with_last_byte(3)), - block_number: Some(4), - transaction_index: Some(5), - from: Address::with_last_byte(6), - to: Some(Address::with_last_byte(7)), - value: U256::from(8), - gas_price: Some(9), - gas: 10, - input: vec![11, 12, 13].into(), - signature: Some(Signature { - v: U256::from(14), - r: U256::from(14), - s: U256::from(14), - y_parity: None, - }), - chain_id: Some(17), - blob_versioned_hashes: None, - access_list: None, - transaction_type: Some(20), - max_fee_per_gas: Some(21), - max_priority_fee_per_gas: Some(22), - max_fee_per_blob_gas: None, - authorization_list: Some(vec![(Authorization { - chain_id: 1, - address: Address::left_padding_from(&[6]), - nonce: 1u64, - }) - .into_signed(AlloySignature::from_str("48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c8041b").unwrap())]), - }; - let serialized = serde_json::to_string(&transaction).unwrap(); - assert_eq!( - serialized, - r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","nonce":"0x2","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000003","blockNumber":"0x4","transactionIndex":"0x5","from":"0x0000000000000000000000000000000000000006","to":"0x0000000000000000000000000000000000000007","value":"0x8","gasPrice":"0x9","gas":"0xa","maxFeePerGas":"0x15","maxPriorityFeePerGas":"0x16","input":"0x0b0c0d","r":"0xe","s":"0xe","v":"0xe","chainId":"0x11","type":"0x14","authorizationList":[{"chainId":"0x1","address":"0x0000000000000000000000000000000000000006","nonce":"0x1","yParity":"0x0","r":"0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353","s":"0xefffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804"}]}"# - ); - let deserialized: Transaction = serde_json::from_str(&serialized).unwrap(); - assert_eq!(transaction, deserialized); - } - - #[test] - #[cfg(feature = "serde")] - fn serde_transaction_with_parity_bit() { - let transaction = Transaction { - hash: B256::with_last_byte(1), - nonce: 2, - block_hash: Some(B256::with_last_byte(3)), - block_number: Some(4), - transaction_index: Some(5), - from: Address::with_last_byte(6), - to: Some(Address::with_last_byte(7)), - value: U256::from(8), - gas_price: Some(9), - gas: 10, - input: vec![11, 12, 13].into(), - signature: Some(Signature { - v: U256::from(14), - r: U256::from(14), - s: U256::from(14), - y_parity: Some(Parity(true)), - }), - chain_id: Some(17), - blob_versioned_hashes: None, - access_list: None, - transaction_type: Some(20), - max_fee_per_gas: Some(21), - max_priority_fee_per_gas: Some(22), - max_fee_per_blob_gas: None, - authorization_list: Some(vec![(Authorization { - chain_id: 1, - address: Address::left_padding_from(&[6]), - nonce: 1u64, - }) - .into_signed(AlloySignature::from_str("48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c8041b").unwrap())]), - }; - let serialized = serde_json::to_string(&transaction).unwrap(); - assert_eq!( - serialized, - r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","nonce":"0x2","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000003","blockNumber":"0x4","transactionIndex":"0x5","from":"0x0000000000000000000000000000000000000006","to":"0x0000000000000000000000000000000000000007","value":"0x8","gasPrice":"0x9","gas":"0xa","maxFeePerGas":"0x15","maxPriorityFeePerGas":"0x16","input":"0x0b0c0d","r":"0xe","s":"0xe","v":"0xe","yParity":"0x1","chainId":"0x11","type":"0x14","authorizationList":[{"chainId":"0x1","address":"0x0000000000000000000000000000000000000006","nonce":"0x1","yParity":"0x0","r":"0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353","s":"0xefffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804"}]}"# - ); - let deserialized: Transaction = serde_json::from_str(&serialized).unwrap(); - assert_eq!(transaction, deserialized); - } - - #[test] - #[cfg(feature = "serde")] - fn serde_minimal_transaction() { - let transaction = Transaction { - hash: B256::with_last_byte(1), - nonce: 2, - from: Address::with_last_byte(6), - value: U256::from(8), - gas: 10, - input: vec![11, 12, 13].into(), - ..Default::default() - }; - let serialized = serde_json::to_string(&transaction).unwrap(); - assert_eq!( - serialized, - r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","nonce":"0x2","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000006","to":null,"value":"0x8","gas":"0xa","input":"0x0b0c0d"}"# - ); - let deserialized: Transaction = serde_json::from_str(&serialized).unwrap(); - assert_eq!(transaction, deserialized); - } #[test] #[cfg(feature = "serde")] @@ -577,4 +289,12 @@ mod tests { assert!(request.gas_price.is_none()); assert!(request.max_fee_per_gas.is_some()); } + + #[test] + fn serde_tx_from_contract_mod() { + let rpc_tx = r#"{"hash":"0x018b2331d461a4aeedf6a1f9cc37463377578244e6a35216057a8370714e798f","nonce":"0x1","blockHash":"0x6e4e53d1de650d5a5ebed19b38321db369ef1dc357904284ecf4d89b8834969c","blockNumber":"0x2","transactionIndex":"0x0","from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","to":"0x5fbdb2315678afecb367f032d93f642f64180aa3","value":"0x0","gasPrice":"0x3a29f0f8","gas":"0x1c9c380","maxFeePerGas":"0xba43b7400","maxPriorityFeePerGas":"0x5f5e100","input":"0xd09de08a","r":"0xd309309a59a49021281cb6bb41d164c96eab4e50f0c1bd24c03ca336e7bc2bb7","s":"0x28a7f089143d0a1355ebeb2a1b9f0e5ad9eca4303021c1400d61bc23c9ac5319","v":"0x0","yParity":"0x0","chainId":"0x7a69","accessList":[],"type":"0x2"}"#; + + let tx = serde_json::from_str::(rpc_tx).unwrap(); + assert_eq!(tx.block_number, Some(2)); + } } diff --git a/crates/rpc-types-eth/src/transaction/signature.rs b/crates/rpc-types-eth/src/transaction/signature.rs deleted file mode 100644 index 9d7cfdb4dd0..00000000000 --- a/crates/rpc-types-eth/src/transaction/signature.rs +++ /dev/null @@ -1,240 +0,0 @@ -//! Signature related RPC values. - -use alloy_primitives::U256; - -/// Container type for all signature fields in RPC -#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Signature { - /// The R field of the signature; the point on the curve. - pub r: U256, - /// The S field of the signature; the point on the curve. - pub s: U256, - // TODO: change these fields to an untagged enum for `v` XOR `y_parity` if/when CLs support it. - // See for more information - /// For EIP-155, EIP-2930 and Blob transactions this is set to the parity (0 for even, 1 for - /// odd) of the y-value of the secp256k1 signature. - /// - /// For legacy transactions, this is the recovery id - /// - /// See also and - pub v: U256, - /// The y parity of the signature. This is only used for typed (non-legacy) transactions. - #[cfg_attr( - feature = "serde", - serde(default, rename = "yParity", skip_serializing_if = "Option::is_none") - )] - pub y_parity: Option, -} - -/// Type that represents the signature parity byte, meant for use in RPC. -/// -/// This will be serialized as "0x0" if false, and "0x1" if true. -#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] -#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Parity( - #[cfg_attr( - feature = "serde", - serde(serialize_with = "serialize_parity", deserialize_with = "deserialize_parity") - )] - pub bool, -); - -impl From for Parity { - fn from(b: bool) -> Self { - Self(b) - } -} - -#[cfg(feature = "serde")] -fn serialize_parity(parity: &bool, serializer: S) -> Result -where - S: serde::Serializer, -{ - serializer.serialize_str(if *parity { "0x1" } else { "0x0" }) -} - -/// This implementation disallows serialization of the y parity bit that are not `"0x0"` or `"0x1"`. -#[cfg(feature = "serde")] -fn deserialize_parity<'de, D>(deserializer: D) -> Result -where - D: serde::Deserializer<'de>, -{ - use serde::Deserialize; - let s = alloc::string::String::deserialize(deserializer)?; - match s.as_str() { - "0x0" => Ok(false), - "0x1" => Ok(true), - _ => Err(serde::de::Error::custom(format!( - "invalid parity value, parity should be either \"0x0\" or \"0x1\": {}", - s - ))), - } -} - -impl TryFrom for alloy_primitives::Signature { - type Error = alloy_primitives::SignatureError; - - fn try_from(value: Signature) -> Result { - let parity = if let Some(y_parity) = value.y_parity { - alloy_primitives::Parity::Parity(y_parity.0) - } else { - value.v.to::().try_into()? - }; - Self::from_rs_and_parity(value.r, value.s, parity) - } -} - -impl From for Signature { - fn from(signature: alloy_primitives::Signature) -> Self { - Self { - v: U256::from(signature.v().to_u64()), - r: signature.r(), - s: signature.s(), - y_parity: Some(Parity::from(signature.v().y_parity())), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use core::str::FromStr; - use similar_asserts::assert_eq; - - #[test] - #[cfg(feature = "serde")] - fn deserialize_without_parity() { - let raw_signature_without_y_parity = r#"{ - "r":"0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0", - "s":"0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05", - "v":"0x1" - }"#; - - let signature: Signature = serde_json::from_str(raw_signature_without_y_parity).unwrap(); - let expected = Signature { - r: U256::from_str("0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0") - .unwrap(), - s: U256::from_str("0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05") - .unwrap(), - v: U256::from_str("1").unwrap(), - y_parity: None, - }; - - assert_eq!(signature, expected); - } - - #[test] - #[cfg(feature = "serde")] - fn deserialize_with_parity() { - let raw_signature_with_y_parity = r#"{ - "r":"0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0", - "s":"0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05", - "v":"0x1", - "yParity": "0x1" - }"#; - - let signature: Signature = serde_json::from_str(raw_signature_with_y_parity).unwrap(); - let expected = Signature { - r: U256::from_str("0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0") - .unwrap(), - s: U256::from_str("0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05") - .unwrap(), - v: U256::from_str("1").unwrap(), - y_parity: Some(Parity(true)), - }; - - assert_eq!(signature, expected); - } - - #[test] - #[cfg(feature = "serde")] - fn serialize_both_parity() { - // this test should be removed if the struct moves to an enum based on tx type - let signature = Signature { - r: U256::from_str("0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0") - .unwrap(), - s: U256::from_str("0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05") - .unwrap(), - v: U256::from_str("1").unwrap(), - y_parity: Some(Parity(true)), - }; - - let serialized = serde_json::to_string(&signature).unwrap(); - assert_eq!( - serialized, - r#"{"r":"0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0","s":"0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05","v":"0x1","yParity":"0x1"}"# - ); - } - - #[test] - #[cfg(feature = "serde")] - fn serialize_v_only() { - // this test should be removed if the struct moves to an enum based on tx type - let signature = Signature { - r: U256::from_str("0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0") - .unwrap(), - s: U256::from_str("0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05") - .unwrap(), - v: U256::from_str("1").unwrap(), - y_parity: None, - }; - - let expected = r#"{"r":"0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0","s":"0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05","v":"0x1"}"#; - - let serialized = serde_json::to_string(&signature).unwrap(); - assert_eq!(serialized, expected); - } - - #[test] - #[cfg(feature = "serde")] - fn serialize_parity() { - let parity = Parity(true); - let serialized = serde_json::to_string(&parity).unwrap(); - assert_eq!(serialized, r#""0x1""#); - - let parity = Parity(false); - let serialized = serde_json::to_string(&parity).unwrap(); - assert_eq!(serialized, r#""0x0""#); - } - - #[test] - #[cfg(feature = "serde")] - fn deserialize_parity() { - let raw_parity = r#""0x1""#; - let parity: Parity = serde_json::from_str(raw_parity).unwrap(); - assert_eq!(parity, Parity(true)); - - let raw_parity = r#""0x0""#; - let parity: Parity = serde_json::from_str(raw_parity).unwrap(); - assert_eq!(parity, Parity(false)); - } - - #[test] - #[cfg(feature = "serde")] - fn deserialize_parity_invalid() { - let raw_parity = r#""0x2""#; - let parity: Result = serde_json::from_str(raw_parity); - assert!(parity.is_err()); - - let raw_parity = r#""0x""#; - let parity: Result = serde_json::from_str(raw_parity); - assert!(parity.is_err()); - - // In the spec this is defined as a uint, which requires 0x - // yParity: - // - // - // uint: - // - let raw_parity = r#""1""#; - let parity: Result = serde_json::from_str(raw_parity); - assert!(parity.is_err()); - - let raw_parity = r#""0""#; - let parity: Result = serde_json::from_str(raw_parity); - assert!(parity.is_err()); - } -} diff --git a/crates/rpc-types-txpool/src/txpool.rs b/crates/rpc-types-txpool/src/txpool.rs index 14702de29f9..b0bd5a773a0 100644 --- a/crates/rpc-types-txpool/src/txpool.rs +++ b/crates/rpc-types-txpool/src/txpool.rs @@ -204,7 +204,6 @@ mod tests { "blockNumber": null, "from": "0x00000000863b56a3c1f0f1be8bc4f8b7bd78f57a", "gas": "0x2af9e", - "gasPrice": "0x218711a00", "maxFeePerGas": "0x218711a00", "maxPriorityFeePerGas": "0x3b9aca00", "hash": "0xfbc6fd04ba1c4114f06574263f04099b4fb2da72acc6f9709f0a3d2361308344", @@ -216,7 +215,7 @@ mod tests { "type": "0x2", "accessList": [], "chainId": "0x1", - "v": "0x0", + "yParity": "0x0", "r": "0xbb809ae71b03319ba2811ebd581c85665169143ffade86e07d2eb4cd03b544dc", "s": "0x65a2aa7e0e70356f765205a611d580de8e84fa79086f117fd9ab4765f5cf1339" } @@ -236,7 +235,7 @@ mod tests { "value": "0x0", "type": "0x0", "chainId": "0x1", - "v": "0x26", + "yParity": "0x0", "r": "0xaf46b2c0f067f7d1d63ac19daa349c0e1eb83f019ee00542ffa7095e05352e92", "s": "0x21d6d24d58ec361379ffffe4cc17bec8ce2b9f5f9759a91afc9a54dfdfa519c2" } @@ -247,7 +246,6 @@ mod tests { "blockNumber": null, "from": "0x000fab888651fbceb55de230493562159ead0340", "gas": "0x12fed", - "gasPrice": "0x1a13b8600", "maxFeePerGas": "0x1a13b8600", "maxPriorityFeePerGas": "0x59682f00", "hash": "0xfae0cffdae6774abe11662a2cdbea019fce48fca87ba9ebf5e9e7c2454c01715", @@ -259,7 +257,7 @@ mod tests { "type": "0x2", "accessList": [], "chainId": "0x1", - "v": "0x0", + "yParity": "0x0", "r": "0x7b717e689d1bd045ee7afd79b97219f2e36bd22a6a14e07023902194bca96fbf", "s": "0x7b0ba462c98e7b0f95a53f047cf568ee0443839628dfe4ab294bfab88fa8e251" } @@ -272,7 +270,6 @@ mod tests { "blockNumber": null, "from": "0x00b846f07f5e7c61569437ca16f88a9dfa00f1bf", "gas": "0x33c3b", - "gasPrice": "0x218711a00", "maxFeePerGas": "0x218711a00", "maxPriorityFeePerGas": "0x77359400", "hash": "0x68959706857f7a58d752ede0a5118a5f55f4ae40801f31377e1af201944720b2", @@ -284,7 +281,7 @@ mod tests { "type": "0x2", "accessList": [], "chainId": "0x1", - "v": "0x0", + "yParity": "0x0", "r": "0x77d149add2b1b84af9408af55661b05b21e2a436f9bfcaa844584905a0f8f1ac", "s": "0x358d79063d702f0c3fb46ad0f6ce5db61f5fdb0b20359c8da2e72a11988db283" } @@ -295,7 +292,6 @@ mod tests { "blockNumber": null, "from": "0x025276ec2de8ee570cfd4c1010319f14a6d9f0dd", "gas": "0x7918", - "gasPrice": "0x12e531724e", "maxFeePerGas": "0x12e531724e", "maxPriorityFeePerGas": "0x59682f00", "hash": "0x35109918ab6129a4d69480514ebec0ea08dc4a4de032fec59003ea66718828c4", @@ -307,7 +303,7 @@ mod tests { "type": "0x2", "accessList": [], "chainId": "0x1", - "v": "0x0", + "yParity": "0x0", "r": "0x863ed0413a14f3f1695fd9728f1500a2b46e69d6f4c82408af15354cc5a667d6", "s": "0x2d503050aa1c9ecbb6df9957459c296f2f6190bc07aa09047d541233100b1c7a" }, @@ -316,7 +312,6 @@ mod tests { "blockNumber": null, "from": "0x025276ec2de8ee570cfd4c1010319f14a6d9f0dd", "gas": "0x7530", - "gasPrice": "0x1919617600", "maxFeePerGas": "0x1919617600", "maxPriorityFeePerGas": "0x5c7261c0", "hash": "0xa58e54464b2ca62a5e2d976604ed9a53b13f8823a170ee4c3ae0cd91cde2a6c5", @@ -328,7 +323,7 @@ mod tests { "type": "0x2", "accessList": [], "chainId": "0x1", - "v": "0x1", + "yParity": "0x1", "r": "0xb6a571191c4b5b667876295571c42c9411bbb4569eea1a6ad149572e4efc55a9", "s": "0x248a72dab9b24568dd9cbe289c205eaba1a6b58b32b5a96c48554945d3fd0d86" } @@ -339,7 +334,6 @@ mod tests { "blockNumber": null, "from": "0x02666081cfb787de3562efbbca5f0fe890e927f1", "gas": "0x16404", - "gasPrice": "0x4bad00695", "maxFeePerGas": "0x4bad00695", "maxPriorityFeePerGas": "0xa3e9ab80", "hash": "0xf627e59d7a59eb650f4c9df222858572601a566263809fdacbb755ac2277a4a7", @@ -351,7 +345,7 @@ mod tests { "type": "0x2", "accessList": [], "chainId": "0x1", - "v": "0x1", + "yParity": "0x0", "r": "0xcfc88f55fc0779d12705acba58719cd7d0ed5b0c1a7c3c3682b56397ca493dd5", "s": "0x7e7dc008058c543ebfdae67154c797639447db5e8006f8fc0585352d857c1b6c" } diff --git a/crates/serde/src/other/mod.rs b/crates/serde/src/other/mod.rs index 8fec656ba1a..c70d14921c6 100644 --- a/crates/serde/src/other/mod.rs +++ b/crates/serde/src/other/mod.rs @@ -178,6 +178,15 @@ pub struct WithOtherFields { pub other: OtherFields, } +impl AsRef for WithOtherFields +where + T: AsRef, +{ + fn as_ref(&self) -> &U { + self.inner.as_ref() + } +} + impl WithOtherFields { /// Creates a new [`WithOtherFields`] instance. pub fn new(inner: T) -> Self {