diff --git a/crates/rpc/rpc-types/src/eth/call.rs b/crates/rpc/rpc-types/src/eth/call.rs index 49b5b7613d..9aec714c12 100644 --- a/crates/rpc/rpc-types/src/eth/call.rs +++ b/crates/rpc/rpc-types/src/eth/call.rs @@ -19,11 +19,9 @@ pub struct CallRequest { pub gas: Option, /// Value pub value: Option, - /// Transaction data - /// - /// This accepts both `input` and `data` - #[serde(alias = "input")] - pub data: Option, + /// Transaction input data + #[serde(default, flatten)] + pub input: CallInput, /// Nonce pub nonce: Option, /// chain id @@ -44,6 +42,71 @@ impl CallRequest { } } +/// 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)] +pub struct CallInput { + /// Transaction data + pub input: Option, + /// Transaction data + /// + /// This is the same as `input` but is used for backwards compatibility: + pub data: Option, +} + +impl CallInput { + /// Consumes the type and returns the optional input data. + /// + /// Returns an error if both `data` and `input` fields are set and not equal. + pub fn try_into_unique_input(self) -> Result, CallInputError> { + let Self { input, data } = self; + match (input, data) { + (Some(input), Some(data)) if input == data => Ok(Some(input)), + (Some(_), Some(_)) => Err(CallInputError::default()), + (Some(input), None) => Ok(Some(input)), + (None, Some(data)) => Ok(Some(data)), + (None, None) => Ok(None), + } + } + + /// Consumes the type and returns the optional input data. + /// + /// Returns an error if both `data` and `input` fields are set and not equal. + pub fn unique_input(&self) -> Result, CallInputError> { + let Self { input, data } = self; + match (input, data) { + (Some(input), Some(data)) if input == data => Ok(Some(input)), + (Some(_), Some(_)) => Err(CallInputError::default()), + (Some(input), None) => Ok(Some(input)), + (None, Some(data)) => Ok(Some(data)), + (None, None) => Ok(None), + } + } +} + +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::*; @@ -53,4 +116,23 @@ mod tests { 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/rpc-types/src/eth/mod.rs b/crates/rpc/rpc-types/src/eth/mod.rs index 04b2da6214..446e4192f6 100644 --- a/crates/rpc/rpc-types/src/eth/mod.rs +++ b/crates/rpc/rpc-types/src/eth/mod.rs @@ -19,7 +19,7 @@ mod work; pub use account::*; pub use block::*; -pub use call::CallRequest; +pub use call::{CallInput, CallInputError, CallRequest}; pub use fee::{FeeHistory, TxGasAndReward}; pub use filter::*; pub use index::Index; diff --git a/crates/rpc/rpc/src/eth/api/transactions.rs b/crates/rpc/rpc/src/eth/api/transactions.rs index e8dd2ed7cc..514d436b3a 100644 --- a/crates/rpc/rpc/src/eth/api/transactions.rs +++ b/crates/rpc/rpc/src/eth/api/transactions.rs @@ -423,7 +423,7 @@ where gas_price: Some(U256::from(gas_price)), max_fee_per_gas: Some(U256::from(max_fee_per_gas)), value: request.value, - data: request.data.clone(), + input: request.data.clone().into(), nonce: request.nonce, chain_id: Some(chain_id), access_list: request.access_list.clone(), diff --git a/crates/rpc/rpc/src/eth/error.rs b/crates/rpc/rpc/src/eth/error.rs index f52bf7ca11..3b3d179b48 100644 --- a/crates/rpc/rpc/src/eth/error.rs +++ b/crates/rpc/rpc/src/eth/error.rs @@ -7,7 +7,7 @@ use jsonrpsee::{ }; use reth_primitives::{abi::decode_revert_reason, Address, Bytes, U256}; use reth_revm::tracing::js::JsInspectorError; -use reth_rpc_types::{error::EthRpcErrorCode, BlockError}; +use reth_rpc_types::{error::EthRpcErrorCode, BlockError, CallInputError}; use reth_transaction_pool::error::{InvalidPoolTransactionError, PoolError}; use revm::primitives::{EVMError, ExecutionResult, Halt, OutOfGasError}; use std::time::Duration; @@ -90,6 +90,8 @@ pub enum EthApiError { /// Internal Error thrown by the javascript tracer #[error("{0}")] InternalJsTracerError(String), + #[error(transparent)] + CallInputError(#[from] CallInputError), } impl From for ErrorObject<'static> { @@ -124,6 +126,7 @@ impl From for ErrorObject<'static> { } err @ EthApiError::InternalTracingError => internal_rpc_err(err.to_string()), err @ EthApiError::InternalEthError => internal_rpc_err(err.to_string()), + err @ EthApiError::CallInputError(_) => invalid_params_rpc_err(err.to_string()), } } } diff --git a/crates/rpc/rpc/src/eth/revm_utils.rs b/crates/rpc/rpc/src/eth/revm_utils.rs index d40689abe1..d83b3d53e1 100644 --- a/crates/rpc/rpc/src/eth/revm_utils.rs +++ b/crates/rpc/rpc/src/eth/revm_utils.rs @@ -282,7 +282,7 @@ pub(crate) fn create_txn_env(block_env: &BlockEnv, request: CallRequest) -> EthR max_priority_fee_per_gas, gas, value, - data, + input, nonce, access_list, chain_id, @@ -308,7 +308,7 @@ pub(crate) fn create_txn_env(block_env: &BlockEnv, request: CallRequest) -> EthR gas_priority_fee: max_priority_fee_per_gas, transact_to: to.map(TransactTo::Call).unwrap_or_else(TransactTo::create), value: value.unwrap_or_default(), - data: data.map(|data| data.0).unwrap_or_default(), + data: input.try_into_unique_input()?.map(|data| data.0).unwrap_or_default(), chain_id: chain_id.map(|c| c.as_u64()), access_list: access_list.map(AccessList::flattened).unwrap_or_default(), };