diff --git a/crates/contract/src/call.rs b/crates/contract/src/call.rs
index 22490ea5751..45bfb11f38f 100644
--- a/crates/contract/src/call.rs
+++ b/crates/contract/src/call.rs
@@ -3,7 +3,11 @@ use alloy_dyn_abi::{DynSolValue, FunctionExt, JsonAbiExt};
use alloy_json_abi::Function;
use alloy_primitives::{Address, Bytes, U256, U64};
use alloy_providers::provider::TempProvider;
-use alloy_rpc_types::{state::StateOverride, BlockId, CallInput, CallRequest, TransactionReceipt};
+use alloy_rpc_types::{
+ request::{TransactionInput, TransactionRequest},
+ state::StateOverride,
+ BlockId, TransactionReceipt,
+};
use alloy_sol_types::SolCall;
use std::{
future::{Future, IntoFuture},
@@ -189,7 +193,7 @@ impl CallDecoder for () {
pub struct CallBuilder
{
// TODO: this will not work with `send_transaction` and does not differentiate between EIP-1559
// and legacy tx
- request: CallRequest,
+ request: TransactionRequest,
block: Option,
state: Option,
provider: P,
@@ -249,7 +253,8 @@ impl RawCallBuilder {
impl CallBuilder {
fn new_inner(provider: P, input: Bytes, decoder: D) -> Self {
- let request = CallRequest { input: CallInput::new(input), ..Default::default() };
+ let request =
+ TransactionRequest { input: TransactionInput::new(input), ..Default::default() };
Self { request, decoder, provider, block: None, state: None }
}
@@ -272,7 +277,7 @@ impl CallBuilder {
/// Sets the `gas` field in the transaction to the provided value
pub fn gas(mut self, gas: U256) -> Self {
- self.request = self.request.gas(gas);
+ self.request = self.request.gas_limit(gas);
self
}
@@ -280,7 +285,8 @@ impl CallBuilder {
/// If the internal transaction is an EIP-1559 one, then it sets both
/// `max_fee_per_gas` and `max_priority_fee_per_gas` to the same value
pub fn gas_price(mut self, gas_price: U256) -> Self {
- self.request = self.request.gas_price(gas_price);
+ self.request = self.request.max_fee_per_gas(gas_price);
+ self.request = self.request.max_priority_fee_per_gas(gas_price);
self
}
diff --git a/crates/providers/src/provider.rs b/crates/providers/src/provider.rs
index d179c0a08f2..934c5c99cdc 100644
--- a/crates/providers/src/provider.rs
+++ b/crates/providers/src/provider.rs
@@ -8,9 +8,9 @@ use alloy_rpc_trace_types::{
parity::LocalizedTransactionTrace,
};
use alloy_rpc_types::{
- state::StateOverride, AccessListWithGasUsed, Block, BlockId, BlockNumberOrTag, CallRequest,
- EIP1186AccountProofResponse, FeeHistory, Filter, Log, SyncStatus, Transaction,
- TransactionReceipt,
+ request::TransactionRequest, state::StateOverride, AccessListWithGasUsed, Block, BlockId,
+ BlockNumberOrTag, EIP1186AccountProofResponse, FeeHistory, Filter, Log, SyncStatus,
+ Transaction, TransactionReceipt,
};
use alloy_transport::{BoxTransport, Transport, TransportErrorKind, TransportResult};
use alloy_transport_http::Http;
@@ -141,24 +141,28 @@ pub trait TempProvider: Send + Sync {
/// Gets syncing info.
async fn syncing(&self) -> TransportResult;
- /// Execute a smart contract call with [CallRequest] without publishing a transaction.
- async fn call(&self, tx: CallRequest, block: Option) -> TransportResult;
+ /// Execute a smart contract call with [TransactionRequest] without publishing a transaction.
+ async fn call(&self, tx: TransactionRequest, block: Option) -> TransportResult;
- /// Execute a smart contract call with [CallRequest] and state overrides, without publishing a
- /// transaction.
+ /// Execute a smart contract call with [TransactionRequest] and state overrides, without
+ /// publishing a transaction.
///
/// # Note
///
/// Not all client implementations support state overrides.
async fn call_with_overrides(
&self,
- tx: CallRequest,
+ tx: TransactionRequest,
block: Option,
state: StateOverride,
) -> TransportResult;
/// Estimate the gas needed for a transaction.
- async fn estimate_gas(&self, tx: CallRequest, block: Option) -> TransportResult;
+ async fn estimate_gas(
+ &self,
+ tx: TransactionRequest,
+ block: Option,
+ ) -> TransportResult;
/// Sends an already-signed transaction.
async fn send_raw_transaction(&self, tx: Bytes) -> TransportResult;
@@ -183,7 +187,7 @@ pub trait TempProvider: Send + Sync {
async fn create_access_list(
&self,
- request: CallRequest,
+ request: TransactionRequest,
block: Option,
) -> TransportResult;
@@ -379,20 +383,20 @@ impl TempProvider for Provider {
self.inner.prepare("eth_syncing", ()).await
}
- /// Execute a smart contract call with [CallRequest] without publishing a transaction.
- async fn call(&self, tx: CallRequest, block: Option) -> TransportResult {
+ /// Execute a smart contract call with [TransactionRequest] without publishing a transaction.
+ async fn call(&self, tx: TransactionRequest, block: Option) -> TransportResult {
self.inner.prepare("eth_call", (tx, block.unwrap_or_default())).await
}
- /// Execute a smart contract call with [CallRequest] and state overrides, without publishing a
- /// transaction.
+ /// Execute a smart contract call with [TransactionRequest] and state overrides, without
+ /// publishing a transaction.
///
/// # Note
///
/// Not all client implementations support state overrides.
async fn call_with_overrides(
&self,
- tx: CallRequest,
+ tx: TransactionRequest,
block: Option,
state: StateOverride,
) -> TransportResult {
@@ -400,7 +404,11 @@ impl TempProvider for Provider {
}
/// Estimate the gas needed for a transaction.
- async fn estimate_gas(&self, tx: CallRequest, block: Option) -> TransportResult {
+ async fn estimate_gas(
+ &self,
+ tx: TransactionRequest,
+ block: Option,
+ ) -> TransportResult {
if let Some(block_id) = block {
self.inner.prepare("eth_estimateGas", (tx, block_id)).await
} else {
@@ -468,7 +476,7 @@ impl TempProvider for Provider {
async fn create_access_list(
&self,
- request: CallRequest,
+ request: TransactionRequest,
block: Option,
) -> TransportResult {
self.inner.prepare("eth_createAccessList", (request, block.unwrap_or_default())).await
diff --git a/crates/rpc-trace-types/src/tracerequest.rs b/crates/rpc-trace-types/src/tracerequest.rs
index 7c1a98501fc..f6c6c23c5a3 100644
--- a/crates/rpc-trace-types/src/tracerequest.rs
+++ b/crates/rpc-trace-types/src/tracerequest.rs
@@ -1,7 +1,7 @@
//! Builder style functions for `trace_call`
use crate::parity::TraceType;
-use alloy_rpc_types::{state::StateOverride, BlockId, BlockOverrides, CallRequest};
+use alloy_rpc_types::{request::TransactionRequest, state::StateOverride, BlockId, BlockOverrides};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
@@ -9,7 +9,7 @@ use std::collections::HashSet;
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct TraceCallRequest {
/// call request object
- pub call: CallRequest,
+ pub call: TransactionRequest,
/// trace types
pub trace_types: HashSet,
/// Optional: blockId
@@ -21,8 +21,8 @@ pub struct TraceCallRequest {
}
impl TraceCallRequest {
- /// Returns a new [`TraceCallRequest`] given a [`CallRequest`] and [`HashSet`]
- pub fn new(call: CallRequest) -> Self {
+ /// Returns a new [`TraceCallRequest`] given a [`TransactionRequest`] and [`HashSet`]
+ pub fn new(call: TransactionRequest) -> Self {
Self {
call,
trace_types: HashSet::new(),
diff --git a/crates/rpc-types/src/eth/call.rs b/crates/rpc-types/src/eth/call.rs
index a954bb76b70..fd766ee22e4 100644
--- a/crates/rpc-types/src/eth/call.rs
+++ b/crates/rpc-types/src/eth/call.rs
@@ -1,5 +1,5 @@
-use crate::{AccessList, BlockId, BlockOverrides};
-use alloy_primitives::{Address, Bytes, B256, U256, U64, U8};
+use crate::{request::TransactionRequest, BlockId, BlockOverrides};
+use alloy_primitives::Bytes;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
/// Bundle of transactions
@@ -7,7 +7,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[serde(default, rename_all = "camelCase")]
pub struct Bundle {
/// All transactions to execute
- pub transactions: Vec,
+ pub transactions: Vec,
/// Block overrides to apply
pub block_override: Option,
}
@@ -97,201 +97,6 @@ impl<'de> Deserialize<'de> for TransactionIndex {
}
}
-/// Call request for `eth_call` and adjacent methods.
-#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Hash)]
-#[serde(default, rename_all = "camelCase")]
-pub struct CallRequest {
- /// From
- pub from: Option,
- /// To
- pub to: Option,
- /// Gas Price
- pub gas_price: Option,
- /// EIP-1559 Max base fee the caller is willing to pay
- pub max_fee_per_gas: Option,
- /// EIP-1559 Priority fee the caller is paying to the block author
- pub max_priority_fee_per_gas: Option,
- /// Gas
- pub gas: Option,
- /// Value
- pub value: Option,
- /// Transaction input data
- #[serde(default, flatten)]
- pub input: CallInput,
- /// Nonce
- pub nonce: Option,
- /// chain id
- pub chain_id: Option,
- /// AccessList
- pub access_list: Option,
- /// Max Fee per Blob gas for EIP-4844 transactions
- #[serde(skip_serializing_if = "Option::is_none")]
- pub max_fee_per_blob_gas: Option,
- /// Blob Versioned Hashes for EIP-4844 transactions
- #[serde(skip_serializing_if = "Option::is_none")]
- pub blob_versioned_hashes: Option>,
- /// EIP-2718 type
- #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
- pub transaction_type: Option,
-}
-
-impl CallRequest {
- /// Returns the configured fee cap, if any.
- ///
- /// The returns `gas_price` (legacy) if set or `max_fee_per_gas` (EIP1559)
- #[inline]
- pub fn fee_cap(&self) -> Option {
- self.gas_price.or(self.max_fee_per_gas)
- }
-
- /// Returns true if the request has a `blobVersionedHashes` field but it is empty.
- #[inline]
- pub fn has_empty_blob_hashes(&self) -> bool {
- self.blob_versioned_hashes.as_ref().map(|blobs| blobs.is_empty()).unwrap_or(false)
- }
-
- /// Sets the `from` field in the call to the provided address
- #[inline]
- pub const fn from(mut self, from: Address) -> Self {
- self.from = Some(from);
- self
- }
-
- /// Sets the `to` field in the call to the provided address
- #[inline]
- pub const fn to(mut self, to: Option) -> Self {
- self.to = to;
- self
- }
-
- /// Sets the `gas` field in the transaction to the provided value
- pub const fn gas(mut self, gas: U256) -> Self {
- self.gas = Some(gas);
- self
- }
-
- /// Sets the `gas_price` field in the transaction to the provided value
- /// If the internal transaction is an EIP-1559 one, then it sets both
- /// `max_fee_per_gas` and `max_priority_fee_per_gas` to the same value
- pub const fn gas_price(mut self, gas_price: U256) -> Self {
- // todo: Add legacy support
- self.max_fee_per_gas = Some(gas_price);
- self.max_fee_per_gas = Some(gas_price);
- self
- }
-
- /// Sets the `value` field in the transaction to the provided value
- pub const fn value(mut self, value: U256) -> Self {
- self.value = Some(value);
- self
- }
-
- /// Sets the `nonce` field in the transaction to the provided value
- pub const fn nonce(mut self, nonce: U64) -> Self {
- self.nonce = Some(nonce);
- self
- }
-
- /// Calculates the address that will be created by the transaction, if any.
- ///
- /// Returns `None` if the transaction is not a contract creation (the `to` field is set), or if
- /// the `from` or `nonce` fields are not set.
- pub fn calculate_create_address(&self) -> Option {
- if self.to.is_some() {
- return None;
- }
- let from = self.from.as_ref()?;
- let nonce = self.nonce?;
- Some(from.create(nonce.to()))
- }
-}
-
-/// Helper type that supports both `data` and `input` fields that map to transaction input data.
-///
-/// This is done for compatibility reasons where older implementations used `data` instead of the
-/// newer, recommended `input` field.
-///
-/// If both fields are set, it is expected that they contain the same value, otherwise an error is
-/// returned.
-#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Hash)]
-pub struct CallInput {
- /// Transaction data
- #[serde(skip_serializing_if = "Option::is_none")]
- pub input: Option,
- /// Transaction data
- ///
- /// This is the same as `input` but is used for backwards compatibility:
- #[serde(skip_serializing_if = "Option::is_none")]
- pub data: Option,
-}
-
-impl CallInput {
- /// Creates a new instance with the given input data.
- pub const fn new(data: Bytes) -> Self {
- Self::maybe_input(Some(data))
- }
-
- /// Creates a new instance with the given input data.
- pub const fn maybe_input(input: Option) -> Self {
- Self { input, data: None }
- }
-
- /// Consumes the type and returns the optional input data.
- #[inline]
- pub fn into_input(self) -> Option {
- self.input.or(self.data)
- }
-
- /// Consumes the type and returns the optional input data.
- ///
- /// Returns an error if both `data` and `input` fields are set and not equal.
- #[inline]
- pub fn try_into_unique_input(self) -> Result, CallInputError> {
- self.check_unique_input().map(|()| self.into_input())
- }
-
- /// Returns the optional input data.
- #[inline]
- pub fn input(&self) -> Option<&Bytes> {
- self.input.as_ref().or(self.data.as_ref())
- }
-
- /// Returns the optional input data.
- ///
- /// Returns an error if both `data` and `input` fields are set and not equal.
- #[inline]
- pub fn unique_input(&self) -> Result , CallInputError> {
- self.check_unique_input().map(|()| self.input())
- }
-
- fn check_unique_input(&self) -> Result<(), CallInputError> {
- if let (Some(input), Some(data)) = (&self.input, &self.data) {
- if input != data {
- return Err(CallInputError::default());
- }
- }
- Ok(())
- }
-}
-
-impl From for CallInput {
- fn from(input: Bytes) -> Self {
- Self { input: Some(input), data: None }
- }
-}
-
-impl From> for CallInput {
- fn from(input: Option) -> Self {
- Self { input, data: None }
- }
-}
-
-/// Error thrown when both `data` and `input` fields are set and not equal.
-#[derive(Debug, Default, thiserror::Error)]
-#[error("both \"data\" and \"input\" are set and not equal. Please use \"input\" to pass transaction call data")]
-#[non_exhaustive]
-pub struct CallInputError;
-
#[cfg(test)]
mod tests {
use super::*;
@@ -310,29 +115,4 @@ mod tests {
let res = serde_json::from_str::(s);
assert!(res.is_err());
}
-
- #[test]
- fn serde_call_request() {
- let s = r#"{"accessList":[],"data":"0x0902f1ac","to":"0xa478c2975ab1ea89e8196811f51a7b7ade33eb11","type":"0x02"}"#;
- let _req = serde_json::from_str::(s).unwrap();
- }
-
- #[test]
- fn serde_unique_call_input() {
- let s = r#"{"accessList":[],"data":"0x0902f1ac", "input":"0x0902f1ac","to":"0xa478c2975ab1ea89e8196811f51a7b7ade33eb11","type":"0x02"}"#;
- let req = serde_json::from_str::(s).unwrap();
- assert!(req.input.try_into_unique_input().unwrap().is_some());
-
- let s = r#"{"accessList":[],"data":"0x0902f1ac","to":"0xa478c2975ab1ea89e8196811f51a7b7ade33eb11","type":"0x02"}"#;
- let req = serde_json::from_str::(s).unwrap();
- assert!(req.input.try_into_unique_input().unwrap().is_some());
-
- let s = r#"{"accessList":[],"input":"0x0902f1ac","to":"0xa478c2975ab1ea89e8196811f51a7b7ade33eb11","type":"0x02"}"#;
- let req = serde_json::from_str::(s).unwrap();
- assert!(req.input.try_into_unique_input().unwrap().is_some());
-
- let s = r#"{"accessList":[],"data":"0x0902f1ac", "input":"0x0902f1","to":"0xa478c2975ab1ea89e8196811f51a7b7ade33eb11","type":"0x02"}"#;
- let req = serde_json::from_str::(s).unwrap();
- assert!(req.input.try_into_unique_input().is_err());
- }
}
diff --git a/crates/rpc-types/src/eth/mod.rs b/crates/rpc-types/src/eth/mod.rs
index 1486fec7c3b..674688ac012 100644
--- a/crates/rpc-types/src/eth/mod.rs
+++ b/crates/rpc-types/src/eth/mod.rs
@@ -20,7 +20,7 @@ mod work;
pub use account::*;
pub use block::*;
-pub use call::{Bundle, CallInput, CallInputError, CallRequest, EthCallResponse, StateContext};
+pub use call::{Bundle, EthCallResponse, StateContext};
pub use fee::{FeeHistory, TxGasAndReward};
pub use filter::*;
pub use index::Index;
diff --git a/crates/rpc-types/src/eth/transaction/blob.rs b/crates/rpc-types/src/eth/transaction/blob.rs
index f21cb7224ab..1edc6326063 100644
--- a/crates/rpc-types/src/eth/transaction/blob.rs
+++ b/crates/rpc-types/src/eth/transaction/blob.rs
@@ -4,7 +4,7 @@ use crate::kzg::{Blob, Bytes48};
use serde::{Deserialize, Serialize};
/// This represents a set of blobs, and its corresponding commitments and proofs.
-#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[repr(C)]
pub struct BlobTransactionSidecar {
/// The blob data.
diff --git a/crates/rpc-types/src/eth/transaction/request.rs b/crates/rpc-types/src/eth/transaction/request.rs
index e4430b47003..42fefe23a5a 100644
--- a/crates/rpc-types/src/eth/transaction/request.rs
+++ b/crates/rpc-types/src/eth/transaction/request.rs
@@ -1,84 +1,141 @@
//! Alloy basic Transaction Request type.
+use std::hash::Hash;
+
use crate::{eth::transaction::AccessList, other::OtherFields, BlobTransactionSidecar};
-use alloy_primitives::{Address, Bytes, U128, U256, U64, U8};
+use alloy_primitives::{Address, Bytes, B256, U256, U64, U8};
use serde::{Deserialize, Serialize};
-/// Represents _all_ transaction requests received from RPC
+/// Represents _all_ transaction requests to/from RPC.
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
pub struct TransactionRequest {
- /// from address
+ /// The address of the transaction author.
pub from: Option,
- /// to address
+ /// The destination address of the transaction.
pub to: Option,
- /// legacy, gas Price
+ /// The legacy gas price.
#[serde(default)]
- pub gas_price: Option,
- /// max base fee per gas sender is willing to pay
+ pub gas_price: Option,
+ /// The max base fee per gas the sender is willing to pay.
#[serde(default)]
- pub max_fee_per_gas: Option,
- /// miner tip
+ pub max_fee_per_gas: Option,
+ /// The max priority fee per gas the sender is willing to pay, also called the miner tip.
#[serde(default)]
- pub max_priority_fee_per_gas: Option,
- /// gas
+ pub max_priority_fee_per_gas: Option,
+ /// The max fee per blob gas for EIP-4844 blob transactions.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub max_fee_per_blob_gas: Option,
+ /// The gas limit for the transaction.
pub gas: Option,
- /// value of th tx in wei
+ /// The value transferred in the transaction, in wei.
pub value: Option,
- /// Any additional data sent
- #[serde(alias = "input")]
- pub data: Option,
- /// Transaction nonce
+ /// Transaction data.
+ #[serde(default, flatten)]
+ pub input: TransactionInput,
+ /// The nonce of the transaction.
pub nonce: Option,
- /// warm storage access pre-payment
+ /// The chain ID for the transaction.
+ pub chain_id: Option,
+ /// An EIP-2930 access list, which lowers cost for accessing accounts and storages in the list. See [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930) for more information.
#[serde(default)]
pub access_list: Option,
- /// EIP-2718 type
+ /// The EIP-2718 transaction type. See [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718) for more information.
#[serde(rename = "type")]
pub transaction_type: Option,
- /// sidecar for EIP-4844 transactions
+ /// Blob versioned hashes for EIP-4844 transactions.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub blob_versioned_hashes: Option>,
+ /// 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 {
+ fn hash(&self, state: &mut H) {
+ self.from.hash(state);
+ self.to.hash(state);
+ self.gas_price.hash(state);
+ self.max_fee_per_gas.hash(state);
+ self.max_priority_fee_per_gas.hash(state);
+ self.max_fee_per_blob_gas.hash(state);
+ self.gas.hash(state);
+ self.value.hash(state);
+ self.input.hash(state);
+ self.nonce.hash(state);
+ self.chain_id.hash(state);
+ self.access_list.hash(state);
+ 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);
+ }
+ }
+}
+
// == impl TransactionRequest ==
impl TransactionRequest {
+ /// Returns the configured fee cap, if any.
+ ///
+ /// The returns `gas_price` (legacy) if set or `max_fee_per_gas` (EIP1559)
+ #[inline]
+ pub fn fee_cap(&self) -> Option {
+ self.gas_price.or(self.max_fee_per_gas)
+ }
+
+ /// Returns true if the request has a `blobVersionedHashes` field but it is empty.
+ #[inline]
+ pub fn has_empty_blob_hashes(&self) -> bool {
+ self.blob_versioned_hashes.as_ref().map(|blobs| blobs.is_empty()).unwrap_or(false)
+ }
+
+ /// Sets the `from` field in the call to the provided address
+ #[inline]
+ pub const fn from(mut self, from: Address) -> Self {
+ self.from = Some(from);
+ self
+ }
+
/// Sets the gas limit for the transaction.
- pub fn gas_limit(mut self, gas_limit: u64) -> Self {
- self.gas = Some(U256::from(gas_limit));
+ pub const fn gas_limit(mut self, gas_limit: U256) -> Self {
+ self.gas = Some(gas_limit);
self
}
/// Sets the nonce for the transaction.
- pub fn nonce(mut self, nonce: u64) -> Self {
- self.nonce = Some(U64::from(nonce));
+ pub const fn nonce(mut self, nonce: U64) -> Self {
+ self.nonce = Some(nonce);
self
}
/// Sets the maximum fee per gas for the transaction.
- pub fn max_fee_per_gas(mut self, max_fee_per_gas: u128) -> Self {
- self.max_fee_per_gas = Some(U128::from(max_fee_per_gas));
+ pub const fn max_fee_per_gas(mut self, max_fee_per_gas: U256) -> Self {
+ self.max_fee_per_gas = Some(max_fee_per_gas);
self
}
/// Sets the maximum priority fee per gas for the transaction.
- pub fn max_priority_fee_per_gas(mut self, max_priority_fee_per_gas: u128) -> Self {
- self.max_priority_fee_per_gas = Some(U128::from(max_priority_fee_per_gas));
+ pub const fn max_priority_fee_per_gas(mut self, max_priority_fee_per_gas: U256) -> Self {
+ self.max_priority_fee_per_gas = Some(max_priority_fee_per_gas);
self
}
/// Sets the recipient address for the transaction.
- pub const fn to(mut self, to: Address) -> Self {
- self.to = Some(to);
+ #[inline]
+ pub const fn to(mut self, to: Option) -> Self {
+ self.to = to;
self
}
- /// Sets the value (amount) for the transaction.
- pub fn value(mut self, value: u128) -> Self {
- self.value = Some(U256::from(value));
+ /// Sets the value (amount) for the transaction.
+ pub const fn value(mut self, value: U256) -> Self {
+ self.value = Some(value);
self
}
@@ -89,8 +146,8 @@ impl TransactionRequest {
}
/// Sets the input data for the transaction.
- pub fn input(mut self, input: Bytes) -> Self {
- self.data = Some(input);
+ pub fn input(mut self, input: TransactionInput) -> Self {
+ self.input = input;
self
}
@@ -99,4 +156,133 @@ impl TransactionRequest {
self.transaction_type = Some(U8::from(transaction_type));
self
}
+
+ /// Calculates the address that will be created by the transaction, if any.
+ ///
+ /// Returns `None` if the transaction is not a contract creation (the `to` field is set), or if
+ /// the `from` or `nonce` fields are not set.
+ pub fn calculate_create_address(&self) -> Option {
+ if self.to.is_some() {
+ return None;
+ }
+ let from = self.from.as_ref()?;
+ let nonce = self.nonce?;
+ Some(from.create(nonce.to()))
+ }
+}
+
+/// Helper type that supports both `data` and `input` fields that map to transaction input data.
+///
+/// This is done for compatibility reasons where older implementations used `data` instead of the
+/// newer, recommended `input` field.
+///
+/// If both fields are set, it is expected that they contain the same value, otherwise an error is
+/// returned.
+#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Hash)]
+pub struct TransactionInput {
+ /// Transaction data
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub input: Option,
+ /// Transaction data
+ ///
+ /// This is the same as `input` but is used for backwards compatibility:
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub data: Option,
+}
+
+impl TransactionInput {
+ /// Creates a new instance with the given input data.
+ pub const fn new(data: Bytes) -> Self {
+ Self::maybe_input(Some(data))
+ }
+
+ /// Creates a new instance with the given input data.
+ pub const fn maybe_input(input: Option) -> Self {
+ Self { input, data: None }
+ }
+
+ /// Consumes the type and returns the optional input data.
+ #[inline]
+ pub fn into_input(self) -> Option {
+ self.input.or(self.data)
+ }
+
+ /// Consumes the type and returns the optional input data.
+ ///
+ /// Returns an error if both `data` and `input` fields are set and not equal.
+ #[inline]
+ pub fn try_into_unique_input(self) -> Result, TransactionInputError> {
+ self.check_unique_input().map(|()| self.into_input())
+ }
+
+ /// Returns the optional input data.
+ #[inline]
+ pub fn input(&self) -> Option<&Bytes> {
+ self.input.as_ref().or(self.data.as_ref())
+ }
+
+ /// Returns the optional input data.
+ ///
+ /// Returns an error if both `data` and `input` fields are set and not equal.
+ #[inline]
+ pub fn unique_input(&self) -> Result , TransactionInputError> {
+ self.check_unique_input().map(|()| self.input())
+ }
+
+ fn check_unique_input(&self) -> Result<(), TransactionInputError> {
+ if let (Some(input), Some(data)) = (&self.input, &self.data) {
+ if input != data {
+ return Err(TransactionInputError::default());
+ }
+ }
+ Ok(())
+ }
+}
+
+impl From for TransactionInput {
+ fn from(input: Bytes) -> Self {
+ Self { input: Some(input), data: None }
+ }
+}
+
+impl From> for TransactionInput {
+ fn from(input: Option) -> Self {
+ Self { input, data: None }
+ }
+}
+
+/// Error thrown when both `data` and `input` fields are set and not equal.
+#[derive(Debug, Default, thiserror::Error)]
+#[error("both \"data\" and \"input\" are set and not equal. Please use \"input\" to pass transaction call data")]
+#[non_exhaustive]
+pub struct TransactionInputError;
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn serde_tx_request() {
+ let s = r#"{"accessList":[],"data":"0x0902f1ac","to":"0xa478c2975ab1ea89e8196811f51a7b7ade33eb11","type":"0x02"}"#;
+ let _req = serde_json::from_str::(s).unwrap();
+ }
+
+ #[test]
+ fn serde_unique_call_input() {
+ let s = r#"{"accessList":[],"data":"0x0902f1ac", "input":"0x0902f1ac","to":"0xa478c2975ab1ea89e8196811f51a7b7ade33eb11","type":"0x02"}"#;
+ let req = serde_json::from_str::(s).unwrap();
+ assert!(req.input.try_into_unique_input().unwrap().is_some());
+
+ let s = r#"{"accessList":[],"data":"0x0902f1ac","to":"0xa478c2975ab1ea89e8196811f51a7b7ade33eb11","type":"0x02"}"#;
+ let req = serde_json::from_str::(s).unwrap();
+ assert!(req.input.try_into_unique_input().unwrap().is_some());
+
+ let s = r#"{"accessList":[],"input":"0x0902f1ac","to":"0xa478c2975ab1ea89e8196811f51a7b7ade33eb11","type":"0x02"}"#;
+ let req = serde_json::from_str::(s).unwrap();
+ assert!(req.input.try_into_unique_input().unwrap().is_some());
+
+ let s = r#"{"accessList":[],"data":"0x0902f1ac", "input":"0x0902f1","to":"0xa478c2975ab1ea89e8196811f51a7b7ade33eb11","type":"0x02"}"#;
+ let req = serde_json::from_str::(s).unwrap();
+ assert!(req.input.try_into_unique_input().is_err());
+ }
}