From 36b4a503e2c419423c848d782cbc0eec5604fa08 Mon Sep 17 00:00:00 2001 From: evalir Date: Wed, 1 Nov 2023 19:47:18 -0400 Subject: [PATCH] feat: add RPC types + Add temporary bare `Provider` (#13) --- .github/workflows/ci.yml | 30 +- Cargo.toml | 3 +- crates/json-rpc/src/packet.rs | 60 +- crates/json-rpc/src/request.rs | 2 +- crates/providers/Cargo.toml | 13 + crates/providers/src/lib.rs | 3 + crates/providers/src/provider.rs | 622 +++++++++ crates/providers/src/utils.rs | 104 ++ crates/rpc-types/Cargo.toml | 44 + crates/rpc-types/README.md | 3 + crates/rpc-types/src/eth/block.rs | 972 +++++++++++++ crates/rpc-types/src/eth/call.rs | 267 ++++ crates/rpc-types/src/eth/fee.rs | 58 + crates/rpc-types/src/eth/filter.rs | 1232 +++++++++++++++++ crates/rpc-types/src/eth/log.rs | 55 + crates/rpc-types/src/eth/mod.rs | 22 + crates/rpc-types/src/eth/pubsub.rs | 169 +++ crates/rpc-types/src/eth/raw_log.rs | 30 + crates/rpc-types/src/eth/syncing.rs | 161 +++ .../src/eth/transaction/access_list.rs | 107 ++ .../rpc-types/src/eth/transaction/common.rs | 19 + crates/rpc-types/src/eth/transaction/mod.rs | 154 +++ .../rpc-types/src/eth/transaction/receipt.rs | 54 + .../rpc-types/src/eth/transaction/request.rs | 176 +++ .../src/eth/transaction/signature.rs | 193 +++ crates/rpc-types/src/eth/transaction/typed.rs | 115 ++ crates/rpc-types/src/eth/withdrawal.rs | 125 ++ crates/rpc-types/src/lib.rs | 21 + crates/rpc-types/src/rpc.rs | 41 + .../rpc-types/src/serde_helpers/json_u256.rs | 93 ++ crates/rpc-types/src/serde_helpers/mod.rs | 30 + crates/rpc-types/src/serde_helpers/num.rs | 139 ++ crates/rpc-types/src/serde_helpers/storage.rs | 102 ++ crates/rpc-types/src/serde_helpers/u64_hex.rs | 18 + .../test_data/call_tracer/default.json | 21 + .../test_data/call_tracer/legacy.json | 19 + .../test_data/call_tracer/only_top_call.json | 10 + .../test_data/call_tracer/with_log.json | 20 + .../test_data/default/structlogs_01.json | 1 + .../test_data/pre_state_tracer/default.json | 20 + .../test_data/pre_state_tracer/diff_mode.json | 41 + .../test_data/pre_state_tracer/legacy.json | 25 + crates/transports/src/transports/json.rs | 1 - deny.toml | 2 + 44 files changed, 5384 insertions(+), 13 deletions(-) create mode 100644 crates/providers/src/provider.rs create mode 100644 crates/providers/src/utils.rs create mode 100644 crates/rpc-types/Cargo.toml create mode 100644 crates/rpc-types/README.md create mode 100644 crates/rpc-types/src/eth/block.rs create mode 100644 crates/rpc-types/src/eth/call.rs create mode 100644 crates/rpc-types/src/eth/fee.rs create mode 100644 crates/rpc-types/src/eth/filter.rs create mode 100644 crates/rpc-types/src/eth/log.rs create mode 100644 crates/rpc-types/src/eth/mod.rs create mode 100644 crates/rpc-types/src/eth/pubsub.rs create mode 100644 crates/rpc-types/src/eth/raw_log.rs create mode 100644 crates/rpc-types/src/eth/syncing.rs create mode 100644 crates/rpc-types/src/eth/transaction/access_list.rs create mode 100644 crates/rpc-types/src/eth/transaction/common.rs create mode 100644 crates/rpc-types/src/eth/transaction/mod.rs create mode 100644 crates/rpc-types/src/eth/transaction/receipt.rs create mode 100644 crates/rpc-types/src/eth/transaction/request.rs create mode 100644 crates/rpc-types/src/eth/transaction/signature.rs create mode 100644 crates/rpc-types/src/eth/transaction/typed.rs create mode 100644 crates/rpc-types/src/eth/withdrawal.rs create mode 100644 crates/rpc-types/src/lib.rs create mode 100644 crates/rpc-types/src/rpc.rs create mode 100644 crates/rpc-types/src/serde_helpers/json_u256.rs create mode 100644 crates/rpc-types/src/serde_helpers/mod.rs create mode 100644 crates/rpc-types/src/serde_helpers/num.rs create mode 100644 crates/rpc-types/src/serde_helpers/storage.rs create mode 100644 crates/rpc-types/src/serde_helpers/u64_hex.rs create mode 100644 crates/rpc-types/test_data/call_tracer/default.json create mode 100644 crates/rpc-types/test_data/call_tracer/legacy.json create mode 100644 crates/rpc-types/test_data/call_tracer/only_top_call.json create mode 100644 crates/rpc-types/test_data/call_tracer/with_log.json create mode 100644 crates/rpc-types/test_data/default/structlogs_01.json create mode 100644 crates/rpc-types/test_data/pre_state_tracer/default.json create mode 100644 crates/rpc-types/test_data/pre_state_tracer/diff_mode.json create mode 100644 crates/rpc-types/test_data/pre_state_tracer/legacy.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f0e64cd59ce..6128b7074ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,15 +18,29 @@ jobs: matrix: rust: ["stable", "beta", "nightly", "1.65"] # MSRV flags: ["--no-default-features", "", "--all-features"] + exclude: + # Skip because some features have highest MSRV. + - rust: "1.65" # MSRV + flags: "--all-features" steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ matrix.rust }} - - uses: Swatinem/rust-cache@v2 - - name: test - run: cargo test --workspace ${{ matrix.flags }} - + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - name: Install Anvil + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + # Only run tests on latest stable and above + - name: build + if: ${{ matrix.rust == '1.65' }} # MSRV + run: cargo build --workspace ${{ matrix.flags }} + - name: test + if: ${{ matrix.rust != '1.65' }} # MSRV + run: cargo test --workspace ${{ matrix.flags }} wasm: name: check WASM runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index 7ca9c3f7d6d..c1b4109c732 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,8 +21,9 @@ rustdoc-args = ["--cfg", "docsrs"] alloy-json-rpc = { version = "0.1.0", path = "crates/json-rpc" } alloy-transports = { version = "0.1.0", path = "crates/transports" } alloy-networks = { version = "0.1.0", path = "crates/networks" } +alloy-rpc-types = { version = "0.1.0", path = "crates/rpc-types" } -alloy-primitives = { version = "0.3.0", features = ["serde"] } +alloy-primitives = { version = "0.4.2", features = ["serde"] } alloy-rlp = "0.3.0" # futures diff --git a/crates/json-rpc/src/packet.rs b/crates/json-rpc/src/packet.rs index 308d08757d6..75c6a2c717d 100644 --- a/crates/json-rpc/src/packet.rs +++ b/crates/json-rpc/src/packet.rs @@ -105,13 +105,69 @@ impl RequestPacket { } /// A [`ResponsePacket`] is a [`Response`] or a batch of responses. -#[derive(Debug, Clone, Deserialize)] -#[serde(untagged)] +#[derive(Debug, Clone)] pub enum ResponsePacket, ErrData = Box> { Single(Response), Batch(Vec>), } +use serde::de::{self, Deserializer, MapAccess, SeqAccess, Visitor}; +use std::fmt; +use std::marker::PhantomData; + +impl<'de, Payload, ErrData> Deserialize<'de> for ResponsePacket +where + Payload: Deserialize<'de>, + ErrData: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct ResponsePacketVisitor { + marker: PhantomData ResponsePacket>, + } + + impl<'de, Payload, ErrData> Visitor<'de> for ResponsePacketVisitor + where + Payload: Deserialize<'de>, + ErrData: Deserialize<'de>, + { + type Value = ResponsePacket; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a single response or a batch of responses") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut responses = Vec::new(); + + while let Some(response) = seq.next_element()? { + responses.push(response); + } + + Ok(ResponsePacket::Batch(responses)) + } + + fn visit_map(self, map: M) -> Result + where + M: MapAccess<'de>, + { + let response = + Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))?; + Ok(ResponsePacket::Single(response)) + } + } + + deserializer.deserialize_any(ResponsePacketVisitor { + marker: PhantomData, + }) + } +} + /// A [`BorrowedResponsePacket`] is a [`ResponsePacket`] that has been partially /// deserialized, borrowing its contents from the deserializer. This is used /// primarily for intermediate deserialization. Most users will not require it. diff --git a/crates/json-rpc/src/request.rs b/crates/json-rpc/src/request.rs index e07cb8ebd67..5616e0254b8 100644 --- a/crates/json-rpc/src/request.rs +++ b/crates/json-rpc/src/request.rs @@ -52,7 +52,7 @@ where /// Serialize the request, including the request parameters. pub fn serialize(self) -> serde_json::Result { - let request = serde_json::to_string(&self.params)?; + let request = serde_json::to_string(&self)?; Ok(SerializedRequest { meta: self.meta, request: RawValue::from_string(request)?, diff --git a/crates/providers/Cargo.toml b/crates/providers/Cargo.toml index 3e9fffaa6f8..1c44afd7663 100644 --- a/crates/providers/Cargo.toml +++ b/crates/providers/Cargo.toml @@ -16,6 +16,19 @@ alloy-json-rpc.workspace = true alloy-networks.workspace = true alloy-primitives.workspace = true alloy-transports.workspace = true +alloy-rpc-types.workspace = true async-trait = "0.1.73" futures-util = "0.3.28" serde_json = { workspace = true, features = ["raw_value"] } +serde = { workspace = true, features = ["derive"] } +thiserror = "1.0" +once_cell = "1.17" +reqwest = "0.11.22" +serial_test = "2.0.0" + +[dev-dependencies] +tokio = { version = "1.33.0", features = ["macros"] } +ethers-core = "2.0.10" + +[features] +anvil = [] \ No newline at end of file diff --git a/crates/providers/src/lib.rs b/crates/providers/src/lib.rs index 0621ad365c0..d033c422195 100644 --- a/crates/providers/src/lib.rs +++ b/crates/providers/src/lib.rs @@ -7,6 +7,9 @@ use alloy_primitives::Address; use alloy_transports::{BoxTransport, RpcClient, Transport, TransportError}; use serde_json::value::RawValue; +pub mod provider; +pub mod utils; + use std::{borrow::Cow, marker::PhantomData}; /// A network-wrapped RPC client. diff --git a/crates/providers/src/provider.rs b/crates/providers/src/provider.rs new file mode 100644 index 00000000000..d7e971cd24b --- /dev/null +++ b/crates/providers/src/provider.rs @@ -0,0 +1,622 @@ +//! Alloy main Provider abstraction. + +use alloy_primitives::{Address, BlockHash, Bytes, TxHash, U256, U64}; +use alloy_rpc_types::{ + Block, BlockId, BlockNumberOrTag, FeeHistory, Filter, Log, RpcBlockHash, SyncStatus, + Transaction, TransactionReceipt, TransactionRequest, +}; +use alloy_transports::{BoxTransport, Http, RpcClient, RpcResult, Transport, TransportError}; +use reqwest::Client; +use serde::{Deserialize, Serialize}; +use serde_json::value::RawValue; +use std::borrow::Cow; +use thiserror::Error; + +use crate::utils::{self, EstimatorFunction}; + +#[derive(Debug, Error, Serialize, Deserialize)] +pub enum ClientError { + #[error("Could not parse URL")] + ParseError, + #[error("Unsupported Tag")] + UnsupportedBlockIdError, +} + +/// An abstract provider for interacting with the [Ethereum JSON RPC +/// API](https://github.com/ethereum/wiki/wiki/JSON-RPC). Must be instantiated +/// with a transport which implements the [Transport] trait. +#[derive(Debug)] +pub struct Provider { + inner: RpcClient, + from: Option
, +} + +// Simple JSON-RPC bindings. +// In the future, this will be replaced by a Provider trait, +// but as the interface is not stable yet, we define the bindings ourselves +// until we can use the trait and the client abstraction that will use it. +impl Provider { + /// Gets the transaction count of the corresponding address. + pub async fn get_transaction_count( + &self, + address: Address, + ) -> RpcResult, TransportError> + where + Self: Sync, + { + self.inner + .prepare( + "eth_getTransactionCount", + Cow::<(Address, &'static str)>::Owned((address, "latest")), + ) + .await + } + + /// Gets the last block number available. + pub async fn get_block_number(&self) -> RpcResult, TransportError> + where + Self: Sync, + { + self.inner + .prepare("eth_blockNumber", Cow::<()>::Owned(())) + .await + } + + /// Gets the balance of the account at the specified tag, which defaults to latest. + pub async fn get_balance( + &self, + address: Address, + tag: Option, + ) -> RpcResult, TransportError> + where + Self: Sync, + { + self.inner + .prepare( + "eth_getBalance", + Cow::<(Address, BlockId)>::Owned(( + address, + tag.unwrap_or(BlockId::Number(BlockNumberOrTag::Latest)), + )), + ) + .await + } + + /// Gets a block by its [BlockHash], with full transactions or only hashes. + pub async fn get_block_by_hash( + &self, + hash: BlockHash, + full: bool, + ) -> RpcResult, Box, TransportError> + where + Self: Sync, + { + self.inner + .prepare( + "eth_getBlockByHash", + Cow::<(BlockHash, bool)>::Owned((hash, full)), + ) + .await + } + + /// Gets a block by [BlockNumberOrTag], with full transactions or only hashes. + pub async fn get_block_by_number + Send + Sync>( + &self, + number: B, + full: bool, + ) -> RpcResult, Box, TransportError> + where + Self: Sync, + { + self.inner + .prepare( + "eth_getBlockByNumber", + Cow::<(BlockNumberOrTag, bool)>::Owned((number.into(), full)), + ) + .await + } + + /// Gets the chain ID. + pub async fn get_chain_id(&self) -> RpcResult, TransportError> + where + Self: Sync, + { + self.inner + .prepare("eth_chainId", Cow::<()>::Owned(())) + .await + } + /// Gets the bytecode located at the corresponding [Address]. + pub async fn get_code_at + Send + Sync>( + &self, + address: Address, + tag: B, + ) -> RpcResult, TransportError> + where + Self: Sync, + { + self.inner + .prepare( + "eth_getCode", + Cow::<(Address, BlockId)>::Owned((address, tag.into())), + ) + .await + } + + /// Gets a [Transaction] by its [TxHash]. + pub async fn get_transaction_by_hash( + &self, + hash: TxHash, + ) -> RpcResult, TransportError> + where + Self: Sync, + { + self.inner + .prepare( + "eth_getTransactionByHash", + // Force alloy-rs/alloy to encode this an array of strings, + // even if we only need to send one hash. + Cow::>::Owned(vec![hash]), + ) + .await + } + + /// Retrieves a [`Vec`] with the given [Filter]. + pub async fn get_logs( + &self, + filter: Filter, + ) -> RpcResult, Box, TransportError> + where + Self: Sync, + { + self.inner + .prepare("eth_getLogs", Cow::>::Owned(vec![filter])) + .await + } + + /// Gets the accounts in the remote node. This is usually empty unless you're using a local node. + pub async fn get_accounts(&self) -> RpcResult, Box, TransportError> + where + Self: Sync, + { + self.inner + .prepare("eth_accounts", Cow::<()>::Owned(())) + .await + } + + /// Gets the current gas price. + pub async fn get_gas_price(&self) -> RpcResult, TransportError> + where + Self: Sync, + { + self.inner + .prepare("eth_gasPrice", Cow::<()>::Owned(())) + .await + } + + /// Gets a [TransactionReceipt] if it exists, by its [TxHash]. + pub async fn get_transaction_receipt( + &self, + hash: TxHash, + ) -> RpcResult, Box, TransportError> + where + Self: Sync, + { + self.inner + .prepare( + "eth_getTransactionReceipt", + Cow::>::Owned(vec![hash]), + ) + .await + } + + /// Returns a collection of historical gas information [FeeHistory] which + /// can be used to calculate the EIP1559 fields `maxFeePerGas` and `maxPriorityFeePerGas`. + pub async fn get_fee_history + Send + Sync>( + &self, + block_count: U256, + last_block: B, + reward_percentiles: &[f64], + ) -> RpcResult, TransportError> + where + Self: Sync, + { + self.inner + .prepare( + "eth_feeHistory", + Cow::<(U256, BlockNumberOrTag, Vec)>::Owned(( + block_count, + last_block.into(), + reward_percentiles.to_vec(), + )), + ) + .await + } + + /// Gets the selected block [BlockNumberOrTag] receipts. + pub async fn get_block_receipts( + &self, + block: BlockNumberOrTag, + ) -> RpcResult, Box, TransportError> + where + Self: Sync, + { + self.inner + .prepare( + "eth_getBlockReceipts", + Cow::::Owned(block), + ) + .await + } + + /// Gets an uncle block through the tag [BlockId] and index [U64]. + pub async fn get_uncle + Send + Sync>( + &self, + tag: B, + idx: U64, + ) -> RpcResult, Box, TransportError> + where + Self: Sync, + { + let tag = tag.into(); + match tag { + BlockId::Hash(hash) => { + self.inner + .prepare( + "eth_getUncleByBlockHashAndIndex", + Cow::<(RpcBlockHash, U64)>::Owned((hash, idx)), + ) + .await + } + BlockId::Number(number) => { + self.inner + .prepare( + "eth_getUncleByBlockNumberAndIndex", + Cow::<(BlockNumberOrTag, U64)>::Owned((number, idx)), + ) + .await + } + } + } + + /// Gets syncing info. + pub async fn syncing(&self) -> RpcResult, TransportError> + where + Self: Sync, + { + self.inner + .prepare("eth_syncing", Cow::<()>::Owned(())) + .await + } + + /// Execute a smart contract call with [TransactionRequest] without publishing a transaction. + pub async fn call( + &self, + tx: TransactionRequest, + block: Option, + ) -> RpcResult, TransportError> + where + Self: Sync, + { + self.inner + .prepare( + "eth_call", + Cow::<(TransactionRequest, BlockId)>::Owned(( + tx, + block.unwrap_or(BlockId::Number(BlockNumberOrTag::Latest)), + )), + ) + .await + } + + /// Estimate the gas needed for a transaction. + pub async fn estimate_gas( + &self, + tx: TransactionRequest, + block: Option, + ) -> RpcResult, TransportError> + where + Self: Sync, + { + if let Some(block_id) = block { + let params = Cow::<(TransactionRequest, BlockId)>::Owned((tx, block_id)); + self.inner.prepare("eth_estimateGas", params).await + } else { + let params = Cow::::Owned(tx); + self.inner.prepare("eth_estimateGas", params).await + } + } + + /// Sends an already-signed transaction. + pub async fn send_raw_transaction( + &self, + tx: Bytes, + ) -> RpcResult, TransportError> + where + Self: Sync, + { + self.inner + .prepare("eth_sendRawTransaction", Cow::::Owned(tx)) + .await + } + + /// Estimates the EIP1559 `maxFeePerGas` and `maxPriorityFeePerGas` fields. + /// Receives an optional [EstimatorFunction] that can be used to modify + /// how to estimate these fees. + pub async fn estimate_eip1559_fees( + &self, + estimator: Option, + ) -> RpcResult<(U256, U256), Box, TransportError> + where + Self: Sync, + { + let base_fee_per_gas = match self + .get_block_by_number(BlockNumberOrTag::Latest, false) + .await + { + RpcResult::Success(Some(block)) => match block.header.base_fee_per_gas { + Some(base_fee_per_gas) => base_fee_per_gas, + None => { + return RpcResult::Err(TransportError::Custom("EIP-1559 not activated".into())) + } + }, + RpcResult::Success(None) => { + return RpcResult::Err(TransportError::Custom("Latest block not found".into())) + } + RpcResult::Err(err) => return RpcResult::Err(err), + RpcResult::Failure(err) => return RpcResult::Failure(err), + }; + + let fee_history = match self + .get_fee_history( + U256::from(utils::EIP1559_FEE_ESTIMATION_PAST_BLOCKS), + BlockNumberOrTag::Latest, + &[utils::EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE], + ) + .await + { + RpcResult::Success(fee_history) => fee_history, + RpcResult::Err(err) => return RpcResult::Err(err), + RpcResult::Failure(err) => return RpcResult::Failure(err), + }; + + // use the provided fee estimator function, or fallback to the default implementation. + let (max_fee_per_gas, max_priority_fee_per_gas) = if let Some(es) = estimator { + es(base_fee_per_gas, fee_history.reward.unwrap_or_default()) + } else { + utils::eip1559_default_estimator( + base_fee_per_gas, + fee_history.reward.unwrap_or_default(), + ) + }; + + RpcResult::Success((max_fee_per_gas, max_priority_fee_per_gas)) + } + + #[cfg(feature = "anvil")] + pub async fn set_code( + &self, + address: Address, + code: &'static str, + ) -> RpcResult<(), Box, TransportError> + where + Self: Sync, + { + self.inner + .prepare( + "anvil_setCode", + Cow::<(Address, &'static str)>::Owned((address, code)), + ) + .await + } + + pub fn with_sender(mut self, from: Address) -> Self { + self.from = Some(from); + self + } + + pub fn inner(&self) -> &RpcClient { + &self.inner + } +} + +// HTTP Transport Provider implementation +impl Provider> { + pub fn new(url: &str) -> Result { + let inner: RpcClient> = url.parse().map_err(|_e| ClientError::ParseError)?; + Ok(Self { inner, from: None }) + } +} + +impl TryFrom<&str> for Provider> { + type Error = ClientError; + + fn try_from(value: &str) -> Result { + Provider::new(value) + } +} + +impl TryFrom for Provider> { + type Error = ClientError; + + fn try_from(value: String) -> Result { + Provider::try_from(value.as_str()) + } +} + +impl<'a> TryFrom<&'a String> for Provider> { + type Error = ClientError; + + fn try_from(value: &'a String) -> Result { + Provider::try_from(value.as_str()) + } +} + +#[cfg(test)] +mod providers_test { + use crate::{provider::Provider, utils}; + use alloy_primitives::{address, b256, Address, U256, U64}; + use alloy_rpc_types::{BlockId, BlockNumberOrTag, Filter}; + + use ethers_core::utils::Anvil; + + #[tokio::test] + async fn gets_block_number() { + let anvil = Anvil::new().spawn(); + let provider = Provider::new(&anvil.endpoint()).unwrap(); + let num = provider.get_block_number().await.unwrap(); + assert_eq!(U256::ZERO, num) + } + + #[tokio::test] + async fn gets_transaction_count() { + let anvil = Anvil::new().spawn(); + let provider = Provider::new(&anvil.endpoint()).unwrap(); + let count = provider + .get_transaction_count(address!("328375e18E7db8F1CA9d9bA8bF3E9C94ee34136A")) + .await + .unwrap(); + assert_eq!(count, U256::from(0)); + } + + #[tokio::test] + async fn gets_block_by_hash() { + let anvil = Anvil::new().spawn(); + let provider = Provider::new(&anvil.endpoint()).unwrap(); + let num = 0; + let tag: BlockNumberOrTag = num.into(); + let block = provider + .get_block_by_number(tag, true) + .await + .unwrap() + .unwrap(); + let hash = block.header.hash.unwrap(); + let block = provider + .get_block_by_hash(hash, true) + .await + .unwrap() + .unwrap(); + assert_eq!(block.header.hash.unwrap(), hash); + } + + #[tokio::test] + async fn gets_block_by_number_full() { + let anvil = Anvil::new().spawn(); + let provider = Provider::new(&anvil.endpoint()).unwrap(); + let num = 0; + let tag: BlockNumberOrTag = num.into(); + let block = provider + .get_block_by_number(tag, true) + .await + .unwrap() + .unwrap(); + assert_eq!(block.header.number.unwrap(), U256::from(num)); + } + + #[tokio::test] + async fn gets_block_by_number() { + let anvil = Anvil::new().spawn(); + let provider = Provider::new(&anvil.endpoint()).unwrap(); + let num = 0; + let tag: BlockNumberOrTag = num.into(); + let block = provider + .get_block_by_number(tag, true) + .await + .unwrap() + .unwrap(); + assert_eq!(block.header.number.unwrap(), U256::from(num)); + } + + #[tokio::test] + async fn gets_chain_id() { + let anvil = Anvil::new().args(vec!["--chain-id", "13371337"]).spawn(); + let provider = Provider::new(&anvil.endpoint()).unwrap(); + let chain_id = provider.get_chain_id().await.unwrap(); + assert_eq!(chain_id, U64::from(13371337)); + } + + #[tokio::test] + #[cfg(feature = "anvil")] + async fn gets_code_at() { + let anvil = Anvil::new().spawn(); + let provider = Provider::new(&anvil.endpoint()).unwrap(); + // Set the code + let addr = Address::with_last_byte(16); + provider.set_code(addr, "0xbeef").await.unwrap(); + let _code = provider + .get_code_at( + addr, + BlockId::Number(alloy_rpc_types::BlockNumberOrTag::Latest), + ) + .await + .unwrap(); + } + + #[tokio::test] + #[ignore] + async fn gets_transaction_by_hash() { + let anvil = Anvil::new().spawn(); + let provider = Provider::new(&anvil.endpoint()).unwrap(); + let tx = provider + .get_transaction_by_hash(b256!( + "5c03fab9114ceb98994b43892ade87ddfd9ae7e8f293935c3bd29d435dc9fd95" + )) + .await + .unwrap(); + assert_eq!( + tx.block_hash.unwrap(), + b256!("b20e6f35d4b46b3c4cd72152faec7143da851a0dc281d390bdd50f58bfbdb5d3") + ); + assert_eq!(tx.block_number.unwrap(), U256::from(4571819)); + } + + #[tokio::test] + #[ignore] + async fn gets_logs() { + let anvil = Anvil::new().spawn(); + let provider = Provider::new(&anvil.endpoint()).unwrap(); + let filter = Filter::new() + .at_block_hash(b256!( + "b20e6f35d4b46b3c4cd72152faec7143da851a0dc281d390bdd50f58bfbdb5d3" + )) + .event_signature(b256!( + "e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c" + )); + let logs = provider.get_logs(filter).await.unwrap(); + assert_eq!(logs.len(), 1); + } + + #[tokio::test] + #[ignore] + async fn gets_tx_receipt() { + let anvil = Anvil::new().spawn(); + let provider = Provider::new(&anvil.endpoint()).unwrap(); + let receipt = provider + .get_transaction_receipt(b256!( + "5c03fab9114ceb98994b43892ade87ddfd9ae7e8f293935c3bd29d435dc9fd95" + )) + .await + .unwrap(); + assert!(receipt.is_some()); + let receipt = receipt.unwrap(); + assert_eq!( + receipt.transaction_hash.unwrap(), + b256!("5c03fab9114ceb98994b43892ade87ddfd9ae7e8f293935c3bd29d435dc9fd95") + ); + } + + #[tokio::test] + async fn gets_fee_history() { + let anvil = Anvil::new().spawn(); + let provider = Provider::new(&anvil.endpoint()).unwrap(); + let block_number = provider.get_block_number().await.unwrap(); + let fee_history = provider + .get_fee_history( + U256::from(utils::EIP1559_FEE_ESTIMATION_PAST_BLOCKS), + BlockNumberOrTag::Number(block_number.to()), + &[utils::EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE], + ) + .await + .unwrap(); + assert_eq!(fee_history.oldest_block, U256::ZERO); + } +} diff --git a/crates/providers/src/utils.rs b/crates/providers/src/utils.rs new file mode 100644 index 00000000000..e19a8d1d3a2 --- /dev/null +++ b/crates/providers/src/utils.rs @@ -0,0 +1,104 @@ +//! Provider-related utilities. + +use alloy_primitives::{I256, U256}; + +/// The number of blocks from the past for which the fee rewards are fetched for fee estimation. +pub const EIP1559_FEE_ESTIMATION_PAST_BLOCKS: u64 = 10; +/// The default percentile of gas premiums that are fetched for fee estimation. +pub const EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE: f64 = 5.0; +/// The default max priority fee per gas, used in case the base fee is within a threshold. +pub const EIP1559_FEE_ESTIMATION_DEFAULT_PRIORITY_FEE: u64 = 3_000_000_000; +/// The threshold for base fee below which we use the default priority fee, and beyond which we +/// estimate an appropriate value for priority fee. +pub const EIP1559_FEE_ESTIMATION_PRIORITY_FEE_TRIGGER: u64 = 100_000_000_000; +/// The threshold max change/difference (in %) at which we will ignore the fee history values +/// under it. +pub const EIP1559_FEE_ESTIMATION_THRESHOLD_MAX_CHANGE: i64 = 200; + +/// An estimator function for EIP1559 fees. +pub type EstimatorFunction = fn(U256, Vec>) -> (U256, U256); + +fn estimate_priority_fee(rewards: Vec>) -> U256 { + let mut rewards: Vec = rewards + .iter() + .map(|r| r[0]) + .filter(|r| *r > U256::ZERO) + .collect(); + if rewards.is_empty() { + return U256::ZERO; + } + if rewards.len() == 1 { + return rewards[0]; + } + // Sort the rewards as we will eventually take the median. + rewards.sort(); + + // A copy of the same vector is created for convenience to calculate percentage change + // between subsequent fee values. + let mut rewards_copy = rewards.clone(); + rewards_copy.rotate_left(1); + + let mut percentage_change: Vec = rewards + .iter() + .zip(rewards_copy.iter()) + .map(|(a, b)| { + let a = I256::try_from(*a).expect("priority fee overflow"); + let b = I256::try_from(*b).expect("priority fee overflow"); + ((b - a) * I256::try_from(100).expect("Unexpected overflow")) / a + }) + .collect(); + percentage_change.pop(); + + // Fetch the max of the percentage change, and that element's index. + let max_change = percentage_change.iter().max().unwrap(); + let max_change_index = percentage_change + .iter() + .position(|&c| c == *max_change) + .unwrap(); + + // If we encountered a big change in fees at a certain position, then consider only + // the values >= it. + let values = if *max_change + >= I256::from_raw(U256::from(EIP1559_FEE_ESTIMATION_THRESHOLD_MAX_CHANGE)) + && (max_change_index >= (rewards.len() / 2)) + { + rewards[max_change_index..].to_vec() + } else { + rewards + }; + + // Return the median. + values[values.len() / 2] +} + +fn base_fee_surged(base_fee_per_gas: U256) -> U256 { + if base_fee_per_gas <= U256::from(40_000_000_000u64) { + base_fee_per_gas * U256::from(2) + } else if base_fee_per_gas <= U256::from(100_000_000_000u64) { + base_fee_per_gas * U256::from(16) / U256::from(10) + } else if base_fee_per_gas <= U256::from(200_000_000_000u64) { + base_fee_per_gas * U256::from(14) / U256::from(10) + } else { + base_fee_per_gas * U256::from(12) / U256::from(10) + } +} + +/// The default EIP-1559 fee estimator which is based on the work by [MyCrypto](https://github.com/MyCryptoHQ/MyCrypto/blob/master/src/services/ApiService/Gas/eip1559.ts) +pub fn eip1559_default_estimator(base_fee_per_gas: U256, rewards: Vec>) -> (U256, U256) { + let max_priority_fee_per_gas = + if base_fee_per_gas < U256::from(EIP1559_FEE_ESTIMATION_PRIORITY_FEE_TRIGGER) { + U256::from(EIP1559_FEE_ESTIMATION_DEFAULT_PRIORITY_FEE) + } else { + std::cmp::max( + estimate_priority_fee(rewards), + U256::from(EIP1559_FEE_ESTIMATION_DEFAULT_PRIORITY_FEE), + ) + }; + let potential_max_fee = base_fee_surged(base_fee_per_gas); + let max_fee_per_gas = if max_priority_fee_per_gas > potential_max_fee { + max_priority_fee_per_gas + potential_max_fee + } else { + potential_max_fee + }; + (max_fee_per_gas, max_priority_fee_per_gas) +} diff --git a/crates/rpc-types/Cargo.toml b/crates/rpc-types/Cargo.toml new file mode 100644 index 00000000000..cb80e7a7194 --- /dev/null +++ b/crates/rpc-types/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "alloy-rpc-types" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +authors.workspace = true +repository.workspace = true +exclude.workspace = true +description = """ +Alloy RPC types +""" + +[dependencies] +# # ethereum +alloy-rlp = { workspace = true, features = ["arrayvec", "derive"] } + +# misc +thiserror.workspace = true +itertools = "0.11" +serde = { workspace = true, features = ["derive"] } +serde_with = "3.3" +serde_json.workspace = true +jsonrpsee-types = { version = "0.20", optional = true } +alloy-primitives = { workspace = true, features = ["rlp", "serde"] } +url = "2.3" +bytes = "1.5" + +arbitrary = { version = "1.1", features = ["derive"], optional = true } +proptest = { version = "1.0", optional = true } +proptest-derive = { version = "0.4", optional = true } + +[features] +default = ["jsonrpsee-types"] +arbitrary = ["dep:arbitrary", "dep:proptest-derive", "dep:proptest", "alloy-primitives/arbitrary"] + +[dev-dependencies] +# misc +alloy-primitives = { workspace = true, features = ["rlp", "serde", "arbitrary"] } +arbitrary = { version = "1.1", features = ["derive"] } +proptest = "1.0" +proptest-derive = "0.4" +similar-asserts = "1.4" diff --git a/crates/rpc-types/README.md b/crates/rpc-types/README.md new file mode 100644 index 00000000000..66f8ddcd61f --- /dev/null +++ b/crates/rpc-types/README.md @@ -0,0 +1,3 @@ +## Alloy RPC Types + +Ethereum RPC-related types for Alloy. \ No newline at end of file diff --git a/crates/rpc-types/src/eth/block.rs b/crates/rpc-types/src/eth/block.rs new file mode 100644 index 00000000000..e07a7530d8e --- /dev/null +++ b/crates/rpc-types/src/eth/block.rs @@ -0,0 +1,972 @@ +//! Contains types that represent ethereum types when used in RPC +use crate::{Transaction, Withdrawal}; +use alloy_primitives::{Address, BlockHash, BlockNumber, Bloom, Bytes, B256, B64, U256, U64}; +use alloy_rlp::{bytes, Decodable, Encodable, Error as RlpError}; +use serde::{ + de::{MapAccess, Visitor}, + ser::{Error, SerializeStruct}, + Deserialize, Deserializer, Serialize, Serializer, +}; +use std::{ + collections::BTreeMap, + fmt::{self, Formatter}, + num::ParseIntError, + ops::Deref, + str::FromStr, +}; +/// Block Transactions depending on the boolean attribute of `eth_getBlockBy*`, +/// or if used by `eth_getUncle*` +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum BlockTransactions { + /// Only hashes + Hashes(Vec), + /// Full transactions + Full(Vec), + /// Special case for uncle response. + Uncle, +} + +impl BlockTransactions { + /// Check if the enum variant is + /// used for an uncle response. + pub fn is_uncle(&self) -> bool { + matches!(self, Self::Uncle) + } + + /// Returns an iterator over the transaction hashes. + pub fn iter(&self) -> BlockTransactionsHashIterator<'_> { + BlockTransactionsHashIterator::new(self) + } +} + +/// An Iterator over the transaction hashes of a block. +#[derive(Debug, Clone)] +pub struct BlockTransactionsHashIterator<'a> { + txs: &'a BlockTransactions, + idx: usize, +} + +impl<'a> BlockTransactionsHashIterator<'a> { + fn new(txs: &'a BlockTransactions) -> Self { + Self { txs, idx: 0 } + } +} + +impl<'a> Iterator for BlockTransactionsHashIterator<'a> { + type Item = B256; + + fn next(&mut self) -> Option { + match self.txs { + BlockTransactions::Full(txs) => { + let tx = txs.get(self.idx); + self.idx += 1; + tx.map(|tx| tx.hash) + } + BlockTransactions::Hashes(txs) => { + let tx = txs.get(self.idx).copied(); + self.idx += 1; + tx + } + BlockTransactions::Uncle => None, + } + } +} + +/// Determines how the `transactions` field of [Block] should be filled. +/// +/// This essentially represents the `full:bool` argument in RPC calls that determine whether the +/// response should include full transaction objects or just the hashes. +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum BlockTransactionsKind { + /// Only include hashes: [BlockTransactions::Hashes] + Hashes, + /// Include full transaction objects: [BlockTransactions::Full] + Full, +} + +impl From for BlockTransactionsKind { + fn from(is_full: bool) -> Self { + if is_full { + BlockTransactionsKind::Full + } else { + BlockTransactionsKind::Hashes + } + } +} + +/// Error that can occur when converting other types to blocks +#[derive(Debug, thiserror::Error)] +pub enum BlockError { + /// A transaction failed sender recovery + #[error("transaction failed sender recovery")] + InvalidSignature, + /// A raw block failed to decode + #[error("failed to decode raw block {0}")] + RlpDecodeRawBlock(alloy_rlp::Error), +} + +/// Block representation +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Block { + /// Header of the block + #[serde(flatten)] + pub header: Header, + /// Total difficulty, this field is None only if representing + /// an Uncle block. + #[serde(skip_serializing_if = "Option::is_none")] + pub total_difficulty: Option, + /// Uncles' hashes + pub uncles: Vec, + /// Transactions + #[serde(skip_serializing_if = "BlockTransactions::is_uncle")] + pub transactions: BlockTransactions, + /// Integer the size of this block in bytes. + pub size: Option, + /// Withdrawals in the block + #[serde(default, skip_serializing_if = "Option::is_none")] + pub withdrawals: Option>, +} + +/// Block header representation. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[serde(rename_all = "camelCase")] +pub struct Header { + /// Hash of the block + pub hash: Option, + /// Hash of the parent + pub parent_hash: B256, + /// Hash of the uncles + #[serde(rename = "sha3Uncles")] + pub uncles_hash: B256, + /// Alias of `author` + pub miner: Address, + /// State root hash + pub state_root: B256, + /// Transactions root hash + pub transactions_root: B256, + /// Transactions receipts root hash + pub receipts_root: B256, + /// Logs bloom + pub logs_bloom: Bloom, + /// Difficulty + pub difficulty: U256, + /// Block number + pub number: Option, + /// Gas Limit + pub gas_limit: U256, + /// Gas Used + pub gas_used: U256, + /// Timestamp + pub timestamp: U256, + /// Extra data + pub extra_data: Bytes, + /// Mix Hash + pub mix_hash: B256, + /// Nonce + pub nonce: Option, + /// Base fee per unit of gas (if past London) + #[serde(rename = "baseFeePerGas", skip_serializing_if = "Option::is_none")] + pub base_fee_per_gas: Option, + /// Withdrawals root hash added by EIP-4895 and is ignored in legacy headers. + #[serde(skip_serializing_if = "Option::is_none")] + pub withdrawals_root: Option, + /// Blob gas used + #[serde(rename = "blobGasUsed", skip_serializing_if = "Option::is_none")] + pub blob_gas_used: Option, + /// Excess blob gas + #[serde(rename = "excessBlobGas", skip_serializing_if = "Option::is_none")] + pub excess_blob_gas: Option, + /// Parent beacon block root + #[serde( + rename = "parentBeaconBlockRoot", + skip_serializing_if = "Option::is_none" + )] + pub parent_beacon_block_root: Option, +} + +/// A block hash which may have +/// a boolean requireCanonical field. +/// If false, an RPC call should raise if a block +/// matching the hash is not found. +/// If true, an RPC call should additionaly raise if +/// the block is not in the canonical chain. +/// +#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize)] +pub struct RpcBlockHash { + /// A block hash + pub block_hash: B256, + /// Whether the block must be a canonical block + pub require_canonical: Option, +} + +impl RpcBlockHash { + /// Returns an [RpcBlockHash] from a [B256]. + pub const fn from_hash(block_hash: B256, require_canonical: Option) -> Self { + RpcBlockHash { + block_hash, + require_canonical, + } + } +} + +impl From for RpcBlockHash { + fn from(value: B256) -> Self { + Self::from_hash(value, None) + } +} + +impl From for B256 { + fn from(value: RpcBlockHash) -> Self { + value.block_hash + } +} + +impl AsRef for RpcBlockHash { + fn as_ref(&self) -> &B256 { + &self.block_hash + } +} + +/// A block Number (or tag - "latest", "earliest", "pending") +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)] +pub enum BlockNumberOrTag { + /// Latest block + #[default] + Latest, + /// Finalized block accepted as canonical + Finalized, + /// Safe head block + Safe, + /// Earliest block (genesis) + Earliest, + /// Pending block (not yet part of the blockchain) + Pending, + /// Block by number from canon chain + Number(u64), +} + +impl BlockNumberOrTag { + /// Returns the numeric block number if explicitly set + pub const fn as_number(&self) -> Option { + match *self { + BlockNumberOrTag::Number(num) => Some(num), + _ => None, + } + } + + /// Returns `true` if a numeric block number is set + pub const fn is_number(&self) -> bool { + matches!(self, BlockNumberOrTag::Number(_)) + } + + /// Returns `true` if it's "latest" + pub const fn is_latest(&self) -> bool { + matches!(self, BlockNumberOrTag::Latest) + } + + /// Returns `true` if it's "finalized" + pub const fn is_finalized(&self) -> bool { + matches!(self, BlockNumberOrTag::Finalized) + } + + /// Returns `true` if it's "safe" + pub const fn is_safe(&self) -> bool { + matches!(self, BlockNumberOrTag::Safe) + } + + /// Returns `true` if it's "pending" + pub const fn is_pending(&self) -> bool { + matches!(self, BlockNumberOrTag::Pending) + } + + /// Returns `true` if it's "earliest" + pub const fn is_earliest(&self) -> bool { + matches!(self, BlockNumberOrTag::Earliest) + } +} + +impl From for BlockNumberOrTag { + fn from(num: u64) -> Self { + BlockNumberOrTag::Number(num) + } +} + +impl From for BlockNumberOrTag { + fn from(num: U64) -> Self { + num.into_limbs()[0].into() + } +} + +impl Serialize for BlockNumberOrTag { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match *self { + BlockNumberOrTag::Number(ref x) => serializer.serialize_str(&format!("0x{x:x}")), + BlockNumberOrTag::Latest => serializer.serialize_str("latest"), + BlockNumberOrTag::Finalized => serializer.serialize_str("finalized"), + BlockNumberOrTag::Safe => serializer.serialize_str("safe"), + BlockNumberOrTag::Earliest => serializer.serialize_str("earliest"), + BlockNumberOrTag::Pending => serializer.serialize_str("pending"), + } + } +} + +impl<'de> Deserialize<'de> for BlockNumberOrTag { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?.to_lowercase(); + s.parse().map_err(serde::de::Error::custom) + } +} + +impl FromStr for BlockNumberOrTag { + type Err = ParseBlockNumberError; + + fn from_str(s: &str) -> Result { + let block = match s { + "latest" => Self::Latest, + "finalized" => Self::Finalized, + "safe" => Self::Safe, + "earliest" => Self::Earliest, + "pending" => Self::Pending, + _number => { + if let Some(hex_val) = s.strip_prefix("0x") { + let number = u64::from_str_radix(hex_val, 16); + BlockNumberOrTag::Number(number?) + } else { + return Err(HexStringMissingPrefixError::default().into()); + } + } + }; + Ok(block) + } +} + +impl fmt::Display for BlockNumberOrTag { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + BlockNumberOrTag::Number(ref x) => format!("0x{x:x}").fmt(f), + BlockNumberOrTag::Latest => f.write_str("latest"), + BlockNumberOrTag::Finalized => f.write_str("finalized"), + BlockNumberOrTag::Safe => f.write_str("safe"), + BlockNumberOrTag::Earliest => f.write_str("earliest"), + BlockNumberOrTag::Pending => f.write_str("pending"), + } + } +} + +/// Error variants when parsing a [BlockNumberOrTag] +#[derive(Debug, thiserror::Error)] +pub enum ParseBlockNumberError { + /// Failed to parse hex value + #[error(transparent)] + ParseIntErr(#[from] ParseIntError), + /// Block numbers should be 0x-prefixed + #[error(transparent)] + MissingPrefix(#[from] HexStringMissingPrefixError), +} + +/// Thrown when a 0x-prefixed hex string was expected +#[derive(Copy, Clone, Debug, Default, thiserror::Error)] +#[non_exhaustive] +#[error("hex string without 0x prefix")] +pub struct HexStringMissingPrefixError; + +/// A Block Identifier +/// +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum BlockId { + /// A block hash and an optional bool that defines if it's canonical + Hash(RpcBlockHash), + /// A block number + Number(BlockNumberOrTag), +} + +// === impl BlockId === + +impl BlockId { + /// Returns the block hash if it is [BlockId::Hash] + pub const fn as_block_hash(&self) -> Option { + match self { + BlockId::Hash(hash) => Some(hash.block_hash), + BlockId::Number(_) => None, + } + } + + /// Returns true if this is [BlockNumberOrTag::Latest] + pub const fn is_latest(&self) -> bool { + matches!(self, BlockId::Number(BlockNumberOrTag::Latest)) + } + + /// Returns true if this is [BlockNumberOrTag::Pending] + pub const fn is_pending(&self) -> bool { + matches!(self, BlockId::Number(BlockNumberOrTag::Pending)) + } +} + +impl From for BlockId { + fn from(num: u64) -> Self { + BlockNumberOrTag::Number(num).into() + } +} + +impl From for BlockId { + fn from(num: BlockNumberOrTag) -> Self { + BlockId::Number(num) + } +} + +impl From for BlockId { + fn from(block_hash: B256) -> Self { + BlockId::Hash(RpcBlockHash { + block_hash, + require_canonical: None, + }) + } +} + +impl From<(B256, Option)> for BlockId { + fn from(hash_can: (B256, Option)) -> Self { + BlockId::Hash(RpcBlockHash { + block_hash: hash_can.0, + require_canonical: hash_can.1, + }) + } +} + +impl Serialize for BlockId { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match *self { + BlockId::Hash(RpcBlockHash { + ref block_hash, + ref require_canonical, + }) => { + let mut s = serializer.serialize_struct("BlockIdEip1898", 1)?; + s.serialize_field("blockHash", block_hash)?; + if let Some(require_canonical) = require_canonical { + s.serialize_field("requireCanonical", require_canonical)?; + } + s.end() + } + BlockId::Number(ref num) => num.serialize(serializer), + } + } +} + +impl<'de> Deserialize<'de> for BlockId { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct BlockIdVisitor; + + impl<'de> Visitor<'de> for BlockIdVisitor { + type Value = BlockId; + + fn expecting(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result { + formatter.write_str("Block identifier following EIP-1898") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + // Since there is no way to clearly distinguish between a DATA parameter and a QUANTITY parameter. A str is therefor deserialized into a Block Number: + // However, since the hex string should be a QUANTITY, we can safely assume that if the len is 66 bytes, it is in fact a hash, ref + if v.len() == 66 { + Ok(BlockId::Hash( + v.parse::().map_err(serde::de::Error::custom)?.into(), + )) + } else { + // quantity hex string or tag + Ok(BlockId::Number( + v.parse().map_err(serde::de::Error::custom)?, + )) + } + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + let mut number = None; + let mut block_hash = None; + let mut require_canonical = None; + while let Some(key) = map.next_key::()? { + match key.as_str() { + "blockNumber" => { + if number.is_some() || block_hash.is_some() { + return Err(serde::de::Error::duplicate_field("blockNumber")); + } + if require_canonical.is_some() { + return Err(serde::de::Error::custom( + "Non-valid require_canonical field", + )); + } + number = Some(map.next_value::()?) + } + "blockHash" => { + if number.is_some() || block_hash.is_some() { + return Err(serde::de::Error::duplicate_field("blockHash")); + } + + block_hash = Some(map.next_value::()?); + } + "requireCanonical" => { + if number.is_some() || require_canonical.is_some() { + return Err(serde::de::Error::duplicate_field("requireCanonical")); + } + + require_canonical = Some(map.next_value::()?) + } + key => { + return Err(serde::de::Error::unknown_field( + key, + &["blockNumber", "blockHash", "requireCanonical"], + )) + } + } + } + + if let Some(number) = number { + Ok(BlockId::Number(number)) + } else if let Some(block_hash) = block_hash { + Ok(BlockId::Hash(RpcBlockHash { + block_hash, + require_canonical, + })) + } else { + Err(serde::de::Error::custom( + "Expected `blockNumber` or `blockHash` with `requireCanonical` optionally", + )) + } + } + } + + deserializer.deserialize_any(BlockIdVisitor) + } +} + +/// Block number and hash. +#[derive(Clone, Copy, Hash, Default, PartialEq, Eq)] +pub struct BlockNumHash { + /// Block number + pub number: BlockNumber, + /// Block hash + pub hash: BlockHash, +} + +/// Block number and hash of the forked block. +pub type ForkBlock = BlockNumHash; + +impl std::fmt::Debug for BlockNumHash { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("") + .field(&self.number) + .field(&self.hash) + .finish() + } +} + +impl BlockNumHash { + /// Creates a new `BlockNumHash` from a block number and hash. + pub fn new(number: BlockNumber, hash: BlockHash) -> Self { + Self { number, hash } + } + + /// Consumes `Self` and returns [`BlockNumber`], [`BlockHash`] + pub fn into_components(self) -> (BlockNumber, BlockHash) { + (self.number, self.hash) + } + + /// Returns whether or not the block matches the given [BlockHashOrNumber]. + pub fn matches_block_or_num(&self, block: &BlockHashOrNumber) -> bool { + match block { + BlockHashOrNumber::Hash(hash) => self.hash == *hash, + BlockHashOrNumber::Number(number) => self.number == *number, + } + } +} + +impl From<(BlockNumber, BlockHash)> for BlockNumHash { + fn from(val: (BlockNumber, BlockHash)) -> Self { + BlockNumHash { + number: val.0, + hash: val.1, + } + } +} + +impl From<(BlockHash, BlockNumber)> for BlockNumHash { + fn from(val: (BlockHash, BlockNumber)) -> Self { + BlockNumHash { + hash: val.0, + number: val.1, + } + } +} + +/// Either a block hash _or_ a block number +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[cfg_attr( + any(test, feature = "arbitrary"), + derive(proptest_derive::Arbitrary, arbitrary::Arbitrary) +)] +pub enum BlockHashOrNumber { + /// A block hash + Hash(B256), + /// A block number + Number(u64), +} + +// === impl BlockHashOrNumber === + +impl BlockHashOrNumber { + /// Returns the block number if it is a [`BlockHashOrNumber::Number`]. + #[inline] + pub fn as_number(self) -> Option { + match self { + BlockHashOrNumber::Hash(_) => None, + BlockHashOrNumber::Number(num) => Some(num), + } + } +} + +impl From for BlockHashOrNumber { + fn from(value: B256) -> Self { + BlockHashOrNumber::Hash(value) + } +} + +impl From for BlockHashOrNumber { + fn from(value: u64) -> Self { + BlockHashOrNumber::Number(value) + } +} + +/// Allows for RLP encoding of either a block hash or block number +impl Encodable for BlockHashOrNumber { + fn encode(&self, out: &mut dyn bytes::BufMut) { + match self { + Self::Hash(block_hash) => block_hash.encode(out), + Self::Number(block_number) => block_number.encode(out), + } + } + fn length(&self) -> usize { + match self { + Self::Hash(block_hash) => block_hash.length(), + Self::Number(block_number) => block_number.length(), + } + } +} + +/// Allows for RLP decoding of a block hash or block number +impl Decodable for BlockHashOrNumber { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + let header: u8 = *buf.first().ok_or(RlpError::InputTooShort)?; + // if the byte string is exactly 32 bytes, decode it into a Hash + // 0xa0 = 0x80 (start of string) + 0x20 (32, length of string) + if header == 0xa0 { + // strip the first byte, parsing the rest of the string. + // If the rest of the string fails to decode into 32 bytes, we'll bubble up the + // decoding error. + let hash = B256::decode(buf)?; + Ok(Self::Hash(hash)) + } else { + // a block number when encoded as bytes ranges from 0 to any number of bytes - we're + // going to accept numbers which fit in less than 64 bytes. + // Any data larger than this which is not caught by the Hash decoding should error and + // is considered an invalid block number. + Ok(Self::Number(u64::decode(buf)?)) + } + } +} + +/// Error thrown when parsing a [BlockHashOrNumber] from a string. +#[derive(Debug, thiserror::Error)] +#[error("failed to parse {input:?} as a number: {parse_int_error} or hash: {hex_error}")] +pub struct ParseBlockHashOrNumberError { + input: String, + parse_int_error: ParseIntError, + hex_error: alloy_primitives::hex::FromHexError, +} + +impl FromStr for BlockHashOrNumber { + type Err = ParseBlockHashOrNumberError; + + fn from_str(s: &str) -> Result { + match u64::from_str(s) { + Ok(val) => Ok(val.into()), + Err(pares_int_error) => match B256::from_str(s) { + Ok(val) => Ok(val.into()), + Err(hex_error) => Err(ParseBlockHashOrNumberError { + input: s.to_string(), + parse_int_error: pares_int_error, + hex_error, + }), + }, + } + } +} + +/// A Block representation that allows to include additional fields +pub type RichBlock = Rich; + +impl From for RichBlock { + fn from(block: Block) -> Self { + Rich { + inner: block, + extra_info: Default::default(), + } + } +} + +/// Header representation with additional info. +pub type RichHeader = Rich
; + +impl From
for RichHeader { + fn from(header: Header) -> Self { + Rich { + inner: header, + extra_info: Default::default(), + } + } +} + +/// Value representation with additional info +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct Rich { + /// Standard value. + #[serde(flatten)] + pub inner: T, + /// Additional fields that should be serialized into the `Block` object + #[serde(flatten)] + pub extra_info: BTreeMap, +} + +impl Deref for Rich { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl Serialize for Rich { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if self.extra_info.is_empty() { + return self.inner.serialize(serializer); + } + + let inner = serde_json::to_value(&self.inner); + let extras = serde_json::to_value(&self.extra_info); + + if let (Ok(serde_json::Value::Object(mut value)), Ok(serde_json::Value::Object(extras))) = + (inner, extras) + { + value.extend(extras); + value.serialize(serializer) + } else { + Err(S::Error::custom( + "Unserializable structures: expected objects", + )) + } + } +} + +/// BlockOverrides is a set of header fields to override. +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +#[serde(default, rename_all = "camelCase", deny_unknown_fields)] +pub struct BlockOverrides { + /// Overrides the block number. + /// + /// For `eth_callMany` this will be the block number of the first simulated block. Each + /// following block increments its block number by 1 + // Note: geth uses `number`, erigon uses `blockNumber` + #[serde( + default, + skip_serializing_if = "Option::is_none", + alias = "blockNumber" + )] + pub number: Option, + /// Overrides the difficulty of the block. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub difficulty: Option, + /// Overrides the timestamp of the block. + // Note: geth uses `time`, erigon uses `timestamp` + #[serde(default, skip_serializing_if = "Option::is_none", alias = "timestamp")] + pub time: Option, + /// Overrides the gas limit of the block. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub gas_limit: Option, + /// Overrides the coinbase address of the block. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub coinbase: Option
, + /// Overrides the prevrandao of the block. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub random: Option, + /// Overrides the basefee of the block. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub base_fee: Option, + /// A dictionary that maps blockNumber to a user-defined hash. It could be queried from the + /// solidity opcode BLOCKHASH. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub block_hash: Option>, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_full_conversion() { + let full = true; + assert_eq!(BlockTransactionsKind::Full, full.into()); + + let full = false; + assert_eq!(BlockTransactionsKind::Hashes, full.into()); + } + + #[test] + #[cfg(feature = "jsonrpsee-types")] + fn serde_json_header() { + use jsonrpsee_types::SubscriptionResponse; + let resp = r#"{"jsonrpc":"2.0","method":"eth_subscribe","params":{"subscription":"0x7eef37ff35d471f8825b1c8f67a5d3c0","result":{"hash":"0x7a7ada12e140961a32395059597764416499f4178daf1917193fad7bd2cc6386","parentHash":"0xdedbd831f496e705e7f2ec3c8dcb79051040a360bf1455dbd7eb8ea6ad03b751","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","number":"0x8","gasUsed":"0x0","gasLimit":"0x1c9c380","extraData":"0x","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x642aa48f","difficulty":"0x0","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000"}}}"#; + let _header: SubscriptionResponse<'_, Header> = serde_json::from_str(resp).unwrap(); + + let resp = r#"{"jsonrpc":"2.0","method":"eth_subscription","params":{"subscription":"0x1a14b6bdcf4542fabf71c4abee244e47","result":{"author":"0x000000568b9b5a365eaa767d42e74ed88915c204","difficulty":"0x1","extraData":"0x4e65746865726d696e6420312e392e32322d302d6463373666616366612d32308639ad8ff3d850a261f3b26bc2a55e0f3a718de0dd040a19a4ce37e7b473f2d7481448a1e1fd8fb69260825377c0478393e6055f471a5cf839467ce919a6ad2700","gasLimit":"0x7a1200","gasUsed":"0x0","hash":"0xa4856602944fdfd18c528ef93cc52a681b38d766a7e39c27a47488c8461adcb0","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","number":"0x434822","parentHash":"0x1a9bdc31fc785f8a95efeeb7ae58f40f6366b8e805f47447a52335c95f4ceb49","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x261","stateRoot":"0xf38c4bf2958e541ec6df148e54ce073dc6b610f8613147ede568cb7b5c2d81ee","totalDifficulty":"0x633ebd","timestamp":"0x604726b0","transactions":[],"transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","uncles":[]}}}"#; + let _header: SubscriptionResponse<'_, Header> = serde_json::from_str(resp).unwrap(); + } + + #[test] + fn serde_block() { + let block = Block { + header: Header { + hash: Some(B256::with_last_byte(1)), + parent_hash: B256::with_last_byte(2), + uncles_hash: B256::with_last_byte(3), + miner: Address::with_last_byte(4), + state_root: B256::with_last_byte(5), + transactions_root: B256::with_last_byte(6), + receipts_root: B256::with_last_byte(7), + withdrawals_root: Some(B256::with_last_byte(8)), + number: Some(U256::from(9)), + gas_used: U256::from(10), + gas_limit: U256::from(11), + extra_data: Bytes::from(vec![1, 2, 3]), + logs_bloom: Bloom::default(), + timestamp: U256::from(12), + difficulty: U256::from(13), + mix_hash: B256::with_last_byte(14), + nonce: Some(B64::with_last_byte(15)), + base_fee_per_gas: Some(U256::from(20)), + blob_gas_used: None, + excess_blob_gas: None, + parent_beacon_block_root: None, + }, + total_difficulty: Some(U256::from(100000)), + uncles: vec![B256::with_last_byte(17)], + transactions: BlockTransactions::Hashes(vec![B256::with_last_byte(18)]), + size: Some(U256::from(19)), + withdrawals: Some(vec![]), + }; + let serialized = serde_json::to_string(&block).unwrap(); + assert_eq!( + serialized, + r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000002","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000003","miner":"0x0000000000000000000000000000000000000004","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000005","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000006","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000007","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0xd","number":"0x9","gasLimit":"0xb","gasUsed":"0xa","timestamp":"0xc","extraData":"0x010203","mixHash":"0x000000000000000000000000000000000000000000000000000000000000000e","nonce":"0x000000000000000f","baseFeePerGas":"0x14","withdrawalsRoot":"0x0000000000000000000000000000000000000000000000000000000000000008","totalDifficulty":"0x186a0","uncles":["0x0000000000000000000000000000000000000000000000000000000000000011"],"transactions":["0x0000000000000000000000000000000000000000000000000000000000000012"],"size":"0x13","withdrawals":[]}"# + ); + let deserialized: Block = serde_json::from_str(&serialized).unwrap(); + assert_eq!(block, deserialized); + } + + #[test] + fn serde_block_with_withdrawals_set_as_none() { + let block = Block { + header: Header { + hash: Some(B256::with_last_byte(1)), + parent_hash: B256::with_last_byte(2), + uncles_hash: B256::with_last_byte(3), + miner: Address::with_last_byte(4), + state_root: B256::with_last_byte(5), + transactions_root: B256::with_last_byte(6), + receipts_root: B256::with_last_byte(7), + withdrawals_root: None, + number: Some(U256::from(9)), + gas_used: U256::from(10), + gas_limit: U256::from(11), + extra_data: Bytes::from(vec![1, 2, 3]), + logs_bloom: Bloom::default(), + timestamp: U256::from(12), + difficulty: U256::from(13), + mix_hash: B256::with_last_byte(14), + nonce: Some(B64::with_last_byte(15)), + base_fee_per_gas: Some(U256::from(20)), + blob_gas_used: None, + excess_blob_gas: None, + parent_beacon_block_root: None, + }, + total_difficulty: Some(U256::from(100000)), + uncles: vec![B256::with_last_byte(17)], + transactions: BlockTransactions::Hashes(vec![B256::with_last_byte(18)]), + size: Some(U256::from(19)), + withdrawals: None, + }; + let serialized = serde_json::to_string(&block).unwrap(); + assert_eq!( + serialized, + r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000002","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000003","miner":"0x0000000000000000000000000000000000000004","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000005","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000006","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000007","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0xd","number":"0x9","gasLimit":"0xb","gasUsed":"0xa","timestamp":"0xc","extraData":"0x010203","mixHash":"0x000000000000000000000000000000000000000000000000000000000000000e","nonce":"0x000000000000000f","baseFeePerGas":"0x14","totalDifficulty":"0x186a0","uncles":["0x0000000000000000000000000000000000000000000000000000000000000011"],"transactions":["0x0000000000000000000000000000000000000000000000000000000000000012"],"size":"0x13"}"# + ); + let deserialized: Block = serde_json::from_str(&serialized).unwrap(); + assert_eq!(block, deserialized); + } + + #[test] + fn block_overrides() { + let s = r#"{"blockNumber": "0xe39dd0"}"#; + let _overrides = serde_json::from_str::(s).unwrap(); + } + + #[test] + fn serde_rich_block() { + let s = r#"{ + "hash": "0xb25d0e54ca0104e3ebfb5a1dcdf9528140854d609886a300946fd6750dcb19f4", + "parentHash": "0x9400ec9ef59689c157ac89eeed906f15ddd768f94e1575e0e27d37c241439a5d", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "miner": "0x829bd824b016326a401d083b33d092293333a830", + "stateRoot": "0x546e330050c66d02923e7f1f3e925efaf64e4384eeecf2288f40088714a77a84", + "transactionsRoot": "0xd5eb3ad6d7c7a4798cc5fb14a6820073f44a941107c5d79dac60bd16325631fe", + "receiptsRoot": "0xb21c41cbb3439c5af25304e1405524c885e733b16203221900cb7f4b387b62f0", + "logsBloom": "0x1f304e641097eafae088627298685d20202004a4a59e4d8900914724e2402b028c9d596660581f361240816e82d00fa14250c9ca89840887a381efa600288283d170010ab0b2a0694c81842c2482457e0eb77c2c02554614007f42aaf3b4dc15d006a83522c86a240c06d241013258d90540c3008888d576a02c10120808520a2221110f4805200302624d22092b2c0e94e849b1e1aa80bc4cc3206f00b249d0a603ee4310216850e47c8997a20aa81fe95040a49ca5a420464600e008351d161dc00d620970b6a801535c218d0b4116099292000c08001943a225d6485528828110645b8244625a182c1a88a41087e6d039b000a180d04300d0680700a15794", + "difficulty": "0xc40faff9c737d", + "number": "0xa9a230", + "gasLimit": "0xbe5a66", + "gasUsed": "0xbe0fcc", + "timestamp": "0x5f93b749", + "extraData": "0x7070796520e4b883e5bda9e7a59ee4bb99e9b1bc0103", + "mixHash": "0xd5e2b7b71fbe4ddfe552fb2377bf7cddb16bbb7e185806036cee86994c6e97fc", + "nonce": "0x4722f2acd35abe0f", + "totalDifficulty": "0x3dc957fd8167fb2684a", + "uncles": [], + "transactions": [ + "0xf435a26acc2a9ef73ac0b73632e32e29bd0e28d5c4f46a7e18ed545c93315916" + ], + "size": "0xaeb6" +}"#; + + let block = serde_json::from_str::(s).unwrap(); + let serialized = serde_json::to_string(&block).unwrap(); + let block2 = serde_json::from_str::(&serialized).unwrap(); + assert_eq!(block, block2); + } +} diff --git a/crates/rpc-types/src/eth/call.rs b/crates/rpc-types/src/eth/call.rs new file mode 100644 index 00000000000..416ecdda194 --- /dev/null +++ b/crates/rpc-types/src/eth/call.rs @@ -0,0 +1,267 @@ +//use crate::access_list::AccessList; +use crate::{AccessList, BlockId, BlockOverrides}; +use alloy_primitives::{Address, Bytes, B256, U256, U64, U8}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +/// Bundle of transactions +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] +#[serde(default, rename_all = "camelCase")] +pub struct Bundle { + /// Transactions + pub transactions: Vec, + /// Block overides + pub block_override: Option, +} + +/// State context for callMany +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] +#[serde(default, rename_all = "camelCase")] +pub struct StateContext { + /// Block Number + pub block_number: Option, + /// Inclusive number of tx to replay in block. -1 means replay all + pub transaction_index: Option, +} + +/// CallResponse for eth_callMany +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] +#[serde(default, rename_all = "camelCase")] +pub struct EthCallResponse { + #[serde(skip_serializing_if = "Option::is_none")] + /// eth_call output (if no error) + pub output: Option, + #[serde(skip_serializing_if = "Option::is_none")] + /// eth_call output (if error) + pub error: Option, +} + +/// Represents a transaction index where -1 means all transactions +#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)] +pub enum TransactionIndex { + /// -1 means all transactions + #[default] + All, + /// Transaction index + Index(usize), +} + +impl TransactionIndex { + /// Returns true if this is the all variant + pub fn is_all(&self) -> bool { + matches!(self, TransactionIndex::All) + } + + /// Returns the index if this is the index variant + pub fn index(&self) -> Option { + match self { + TransactionIndex::All => None, + TransactionIndex::Index(idx) => Some(*idx), + } + } +} + +impl Serialize for TransactionIndex { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + TransactionIndex::All => serializer.serialize_i8(-1), + TransactionIndex::Index(idx) => idx.serialize(serializer), + } + } +} + +impl<'de> Deserialize<'de> for TransactionIndex { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + match isize::deserialize(deserializer)? { + -1 => Ok(TransactionIndex::All), + idx if idx < -1 => Err(serde::de::Error::custom(format!( + "Invalid transaction index, expected -1 or positive integer, got {}", + idx + ))), + idx => Ok(TransactionIndex::Index(idx as usize)), + } + } +} + +/// Call request for `eth_call` and adjacent methods. +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] +#[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 + 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")] + 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) + } +} + +/// 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 { + /// Creates a new instance with the given input data. + pub fn new(data: Bytes) -> Self { + Self::maybe_input(Some(data)) + } + + /// Creates a new instance with the given input data. + pub fn maybe_input(input: Option) -> Self { + Self { input, data: 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 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::*; + + #[test] + fn transaction_index() { + let s = "-1"; + let idx = serde_json::from_str::(s).unwrap(); + assert_eq!(idx, TransactionIndex::All); + + let s = "5"; + let idx = serde_json::from_str::(s).unwrap(); + assert_eq!(idx, TransactionIndex::Index(5)); + + let s = "-2"; + 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/fee.rs b/crates/rpc-types/src/eth/fee.rs new file mode 100644 index 00000000000..c7717994ea1 --- /dev/null +++ b/crates/rpc-types/src/eth/fee.rs @@ -0,0 +1,58 @@ +use alloy_primitives::U256; +use serde::{Deserialize, Serialize}; + +/// Internal struct to calculate reward percentiles +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TxGasAndReward { + /// Gas used by the transaction + pub gas_used: u64, + /// The effective gas tip by the transaction + pub reward: u128, +} + +impl PartialOrd for TxGasAndReward { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for TxGasAndReward { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + // compare only the reward + // see: + // + self.reward.cmp(&other.reward) + } +} + +/// Response type for `eth_feeHistory` +#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct FeeHistory { + /// An array of block base fees per gas. + /// This includes the next block after the newest of the returned range, + /// because this value can be derived from the newest block. Zeroes are + /// returned for pre-EIP-1559 blocks. + /// + /// # Note + /// + /// The `Option` is only for compatability with Erigon and Geth. + #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(default)] + pub base_fee_per_gas: Vec, + /// An array of block gas used ratios. These are calculated as the ratio + /// of `gasUsed` and `gasLimit`. + /// + /// # Note + /// + /// The `Option` is only for compatability with Erigon and Geth. + #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(default)] + pub gas_used_ratio: Vec, + /// Lowest number block of the returned range. + pub oldest_block: U256, + /// An (optional) array of effective priority fee per gas data points from a single + /// block. All zeroes are returned if the block is empty. + #[serde(default)] + pub reward: Option>>, +} diff --git a/crates/rpc-types/src/eth/filter.rs b/crates/rpc-types/src/eth/filter.rs new file mode 100644 index 00000000000..5c812d66a38 --- /dev/null +++ b/crates/rpc-types/src/eth/filter.rs @@ -0,0 +1,1232 @@ +use crate::{eth::log::Log as RpcLog, BlockNumberOrTag, Log, Transaction}; +use alloy_primitives::{keccak256, Address, Bloom, BloomInput, B256, U256, U64}; +use itertools::{EitherOrBoth::*, Itertools}; +use serde::{ + de::{DeserializeOwned, MapAccess, Visitor}, + ser::SerializeStruct, + Deserialize, Deserializer, Serialize, Serializer, +}; +use std::{ + collections::HashSet, + hash::Hash, + ops::{Range, RangeFrom, RangeTo}, +}; + +/// Helper type to represent a bloom filter used for matching logs. +#[derive(Default, Debug)] +pub struct BloomFilter(Vec); + +impl From> for BloomFilter { + fn from(src: Vec) -> Self { + BloomFilter(src) + } +} + +impl BloomFilter { + /// Returns whether the given bloom matches the list of Blooms in the current filter. + /// If the filter is empty (the list is empty), then any bloom matches + /// Otherwise, there must be at least one matche for the BloomFilter to match. + pub fn matches(&self, bloom: Bloom) -> bool { + self.0.is_empty() || self.0.iter().any(|a| bloom.contains(a)) + } +} + +#[derive(Default, Debug, PartialEq, Eq, Clone, Deserialize)] +/// FilterSet is a set of values that will be used to filter logs +pub struct FilterSet(HashSet); + +impl From for FilterSet { + fn from(src: T) -> Self { + FilterSet(HashSet::from([src])) + } +} + +impl From> for FilterSet { + fn from(src: Vec) -> Self { + FilterSet(HashSet::from_iter(src.into_iter().map(Into::into))) + } +} + +impl From> for FilterSet { + fn from(src: ValueOrArray) -> Self { + match src { + ValueOrArray::Value(val) => val.into(), + ValueOrArray::Array(arr) => arr.into(), + } + } +} + +impl From>> for FilterSet { + fn from(src: ValueOrArray>) -> Self { + match src { + ValueOrArray::Value(None) => FilterSet(HashSet::new()), + ValueOrArray::Value(Some(val)) => val.into(), + ValueOrArray::Array(arr) => { + // If the array contains at least one `null` (ie. None), as it's considered + // a "wildcard" value, the whole filter should be treated as matching everything, + // thus is empty. + if arr.iter().contains(&None) { + FilterSet(HashSet::new()) + } else { + // Otherwise, we flatten the array, knowing there are no `None` values + arr.into_iter().flatten().collect::>().into() + } + } + } + } +} + +impl FilterSet { + /// Returns wheter the filter is empty + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Returns whether the given value matches the filter. It the filter is empty + /// any value matches. Otherwise, the filter must include the value + pub fn matches(&self, value: &T) -> bool { + self.is_empty() || self.0.contains(value) + } +} + +impl + Eq + Hash> FilterSet { + /// Returns a list of Bloom (BloomFilter) corresponding to the filter's values + pub fn to_bloom_filter(&self) -> BloomFilter { + self.0 + .iter() + .map(|a| BloomInput::Raw(a.as_ref()).into()) + .collect::>() + .into() + } +} + +impl FilterSet { + /// Returns a ValueOrArray inside an Option, so that: + /// - If the filter is empty, it returns None + /// - If the filter has only 1 value, it returns the single value + /// - Otherwise it returns an array of values + /// This should be useful for serialization + pub fn to_value_or_array(&self) -> Option> { + let mut values = self.0.iter().cloned().collect::>(); + match values.len() { + 0 => None, + 1 => Some(ValueOrArray::Value( + values.pop().expect("values length is one"), + )), + _ => Some(ValueOrArray::Array(values)), + } + } +} + +/// A single topic +pub type Topic = FilterSet; + +impl From for Topic { + fn from(src: U256) -> Self { + Into::::into(src).into() + } +} + +/// Represents the target range of blocks for the filter +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum FilterBlockOption { + /// Represents a range of blocks with optional from and to blocks + /// + /// Note: ranges are considered to be __inclusive__ + Range { + /// The block number or tag this filter should start at. + from_block: Option, + /// The block number or that this filter should end at. + to_block: Option, + }, + /// The hash of the block if the filter only targets a single block + AtBlockHash(B256), +} + +impl FilterBlockOption { + /// Returns the `fromBlock` value, if any + pub fn get_to_block(&self) -> Option<&BlockNumberOrTag> { + match self { + FilterBlockOption::Range { to_block, .. } => to_block.as_ref(), + FilterBlockOption::AtBlockHash(_) => None, + } + } + + /// Returns the `toBlock` value, if any + pub fn get_from_block(&self) -> Option<&BlockNumberOrTag> { + match self { + FilterBlockOption::Range { from_block, .. } => from_block.as_ref(), + FilterBlockOption::AtBlockHash(_) => None, + } + } + + /// Returns the range (`fromBlock`, `toBlock`) if this is a range filter. + pub fn as_range(&self) -> (Option<&BlockNumberOrTag>, Option<&BlockNumberOrTag>) { + match self { + FilterBlockOption::Range { + from_block, + to_block, + } => (from_block.as_ref(), to_block.as_ref()), + FilterBlockOption::AtBlockHash(_) => (None, None), + } + } +} + +impl From for FilterBlockOption { + fn from(block: BlockNumberOrTag) -> Self { + let block = Some(block); + FilterBlockOption::Range { + from_block: block, + to_block: block, + } + } +} + +impl From for FilterBlockOption { + fn from(block: U64) -> Self { + BlockNumberOrTag::from(block).into() + } +} + +impl From for FilterBlockOption { + fn from(block: u64) -> Self { + BlockNumberOrTag::from(block).into() + } +} + +impl> From> for FilterBlockOption { + fn from(r: Range) -> Self { + let from_block = Some(r.start.into()); + let to_block = Some(r.end.into()); + FilterBlockOption::Range { + from_block, + to_block, + } + } +} + +impl> From> for FilterBlockOption { + fn from(r: RangeTo) -> Self { + let to_block = Some(r.end.into()); + FilterBlockOption::Range { + from_block: Some(BlockNumberOrTag::Earliest), + to_block, + } + } +} + +impl> From> for FilterBlockOption { + fn from(r: RangeFrom) -> Self { + let from_block = Some(r.start.into()); + FilterBlockOption::Range { + from_block, + to_block: Some(BlockNumberOrTag::Latest), + } + } +} + +impl From for FilterBlockOption { + fn from(hash: B256) -> Self { + FilterBlockOption::AtBlockHash(hash) + } +} + +impl Default for FilterBlockOption { + fn default() -> Self { + FilterBlockOption::Range { + from_block: None, + to_block: None, + } + } +} + +impl FilterBlockOption { + /// Sets the block number this range filter should start at. + #[must_use] + pub fn set_from_block(&self, block: BlockNumberOrTag) -> Self { + let to_block = if let FilterBlockOption::Range { to_block, .. } = self { + *to_block + } else { + None + }; + + FilterBlockOption::Range { + from_block: Some(block), + to_block, + } + } + + /// Sets the block number this range filter should end at. + #[must_use] + pub fn set_to_block(&self, block: BlockNumberOrTag) -> Self { + let from_block = if let FilterBlockOption::Range { from_block, .. } = self { + *from_block + } else { + None + }; + + FilterBlockOption::Range { + from_block, + to_block: Some(block), + } + } + + /// Pins the block hash this filter should target. + #[must_use] + pub fn set_hash(&self, hash: B256) -> Self { + FilterBlockOption::AtBlockHash(hash) + } +} + +/// Filter for +#[derive(Default, Debug, PartialEq, Eq, Clone)] +pub struct Filter { + /// Filter block options, specifying on which blocks the filter should + /// match. + // https://eips.ethereum.org/EIPS/eip-234 + pub block_option: FilterBlockOption, + /// Address + pub address: FilterSet
, + /// Topics (maxmimum of 4) + pub topics: [Topic; 4], +} + +impl Filter { + /// Creates a new, empty filter + pub fn new() -> Self { + Self::default() + } + + /// Sets the inner filter object + /// + /// *NOTE:* ranges are always inclusive + /// + /// # Examples + /// + /// Match only a specific block + /// + /// ```rust + /// # use alloy_rpc_types::Filter; + /// # fn main() { + /// let filter = Filter::new().select(69u64); + /// # } + /// ``` + /// This is the same as `Filter::new().from_block(1337u64).to_block(1337u64)` + /// + /// Match the latest block only + /// + /// ```rust + /// # use alloy_rpc_types::BlockNumberOrTag; + /// # use alloy_rpc_types::Filter; + /// # fn main() { + /// let filter = Filter::new().select(BlockNumberOrTag::Latest); + /// # } + /// ``` + /// + /// Match a block by its hash + /// + /// ```rust + /// # use alloy_primitives::B256; + /// # use alloy_rpc_types::Filter; + /// # fn main() { + /// let filter = Filter::new().select(B256::ZERO); + /// # } + /// ``` + /// This is the same as `at_block_hash` + /// + /// Match a range of blocks + /// + /// ```rust + /// # use alloy_rpc_types::Filter; + /// # fn main() { + /// let filter = Filter::new().select(0u64..100u64); + /// # } + /// ``` + /// + /// Match all blocks in range `(1337..BlockNumberOrTag::Latest)` + /// + /// ```rust + /// # use alloy_rpc_types::Filter; + /// # fn main() { + /// let filter = Filter::new().select(1337u64..); + /// # } + /// ``` + /// + /// Match all blocks in range `(BlockNumberOrTag::Earliest..1337)` + /// + /// ```rust + /// # use alloy_rpc_types::Filter; + /// # fn main() { + /// let filter = Filter::new().select(..1337u64); + /// # } + /// ``` + #[must_use] + pub fn select(mut self, filter: impl Into) -> Self { + self.block_option = filter.into(); + self + } + + /// Sets the from block number + #[allow(clippy::wrong_self_convention)] + #[must_use] + pub fn from_block>(mut self, block: T) -> Self { + self.block_option = self.block_option.set_from_block(block.into()); + self + } + + /// Sets the to block number + #[allow(clippy::wrong_self_convention)] + #[must_use] + pub fn to_block>(mut self, block: T) -> Self { + self.block_option = self.block_option.set_to_block(block.into()); + self + } + + /// Pins the block hash for the filter + #[must_use] + pub fn at_block_hash>(mut self, hash: T) -> Self { + self.block_option = self.block_option.set_hash(hash.into()); + self + } + /// Sets the inner filter object + /// + /// *NOTE:* ranges are always inclusive + /// + /// # Examples + /// + /// Match only a specific address `("0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF")` + /// + /// ```rust + /// # use alloy_primitives::Address; + /// # use alloy_rpc_types::Filter; + /// # fn main() { + /// let filter = Filter::new().address("0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF".parse::
().unwrap()); + /// # } + /// ``` + /// + /// Match all addresses in array `(vec!["0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF", + /// "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8"])` + /// + /// ```rust + /// # use alloy_primitives::Address; + /// # use alloy_rpc_types::Filter; + /// # fn main() { + /// let addresses = vec!["0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF".parse::
().unwrap(),"0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".parse::
().unwrap()]; + /// let filter = Filter::new().address(addresses); + /// # } + /// ``` + #[must_use] + pub fn address>>(mut self, address: T) -> Self { + self.address = address.into().into(); + self + } + + /// Given the event signature in string form, it hashes it and adds it to the topics to monitor + #[must_use] + pub fn event(self, event_name: &str) -> Self { + let hash = keccak256(event_name.as_bytes()); + self.event_signature(hash) + } + + /// Hashes all event signatures and sets them as array to event_signature(topic0) + #[must_use] + pub fn events(self, events: impl IntoIterator>) -> Self { + let events = events + .into_iter() + .map(|e| keccak256(e.as_ref())) + .collect::>(); + self.event_signature(events) + } + + /// Sets event_signature(topic0) (the event name for non-anonymous events) + #[must_use] + pub fn event_signature>(mut self, topic: T) -> Self { + self.topics[0] = topic.into(); + self + } + + /// Sets topic0 (the event name for non-anonymous events) + #[must_use] + #[deprecated(note = "use `event_signature` instead")] + pub fn topic0>(mut self, topic: T) -> Self { + self.topics[0] = topic.into(); + self + } + + /// Sets the 1st indexed topic + #[must_use] + pub fn topic1>(mut self, topic: T) -> Self { + self.topics[1] = topic.into(); + self + } + + /// Sets the 2nd indexed topic + #[must_use] + pub fn topic2>(mut self, topic: T) -> Self { + self.topics[2] = topic.into(); + self + } + + /// Sets the 3rd indexed topic + #[must_use] + pub fn topic3>(mut self, topic: T) -> Self { + self.topics[3] = topic.into(); + self + } + + /// Returns true if this is a range filter and has a from block + pub fn is_paginatable(&self) -> bool { + self.get_from_block().is_some() + } + + /// Returns the numeric value of the `toBlock` field + pub fn get_to_block(&self) -> Option { + self.block_option.get_to_block().and_then(|b| b.as_number()) + } + + /// Returns the numeric value of the `fromBlock` field + pub fn get_from_block(&self) -> Option { + self.block_option + .get_from_block() + .and_then(|b| b.as_number()) + } + + /// Returns the numeric value of the `fromBlock` field + pub fn get_block_hash(&self) -> Option { + match self.block_option { + FilterBlockOption::AtBlockHash(hash) => Some(hash), + FilterBlockOption::Range { .. } => None, + } + } + + /// Returns true if at least one topic is set + pub fn has_topics(&self) -> bool { + self.topics.iter().any(|t| !t.is_empty()) + } +} + +impl Serialize for Filter { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut s = serializer.serialize_struct("Filter", 5)?; + match self.block_option { + FilterBlockOption::Range { + from_block, + to_block, + } => { + if let Some(ref from_block) = from_block { + s.serialize_field("fromBlock", from_block)?; + } + + if let Some(ref to_block) = to_block { + s.serialize_field("toBlock", to_block)?; + } + } + + FilterBlockOption::AtBlockHash(ref h) => s.serialize_field("blockHash", h)?, + } + + if let Some(address) = self.address.to_value_or_array() { + s.serialize_field("address", &address)?; + } + + let mut filtered_topics = Vec::new(); + let mut filtered_topics_len = 0; + for (i, topic) in self.topics.iter().enumerate() { + if !topic.is_empty() { + filtered_topics_len = i + 1; + } + filtered_topics.push(topic.to_value_or_array()); + } + filtered_topics.truncate(filtered_topics_len); + s.serialize_field("topics", &filtered_topics)?; + + s.end() + } +} + +type RawAddressFilter = ValueOrArray>; +type RawTopicsFilter = Vec>>>; + +impl<'de> Deserialize<'de> for Filter { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct FilterVisitor; + + impl<'de> Visitor<'de> for FilterVisitor { + type Value = Filter; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("Filter object") + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + let mut from_block: Option> = None; + let mut to_block: Option> = None; + let mut block_hash: Option> = None; + let mut address: Option> = None; + let mut topics: Option> = None; + + while let Some(key) = map.next_key::()? { + match key.as_str() { + "fromBlock" => { + if from_block.is_some() { + return Err(serde::de::Error::duplicate_field("fromBlock")); + } + if block_hash.is_some() { + return Err(serde::de::Error::custom( + "fromBlock not allowed with blockHash", + )); + } + from_block = Some(map.next_value()?) + } + "toBlock" => { + if to_block.is_some() { + return Err(serde::de::Error::duplicate_field("toBlock")); + } + if block_hash.is_some() { + return Err(serde::de::Error::custom( + "toBlock not allowed with blockHash", + )); + } + to_block = Some(map.next_value()?) + } + "blockHash" => { + if block_hash.is_some() { + return Err(serde::de::Error::duplicate_field("blockHash")); + } + if from_block.is_some() || to_block.is_some() { + return Err(serde::de::Error::custom( + "fromBlock,toBlock not allowed with blockHash", + )); + } + block_hash = Some(map.next_value()?) + } + "address" => { + if address.is_some() { + return Err(serde::de::Error::duplicate_field("address")); + } + address = Some(map.next_value()?) + } + "topics" => { + if topics.is_some() { + return Err(serde::de::Error::duplicate_field("topics")); + } + topics = Some(map.next_value()?) + } + + key => { + return Err(serde::de::Error::unknown_field( + key, + &["fromBlock", "toBlock", "address", "topics", "blockHash"], + )) + } + } + } + + let from_block = from_block.unwrap_or_default(); + let to_block = to_block.unwrap_or_default(); + let block_hash = block_hash.unwrap_or_default(); + let address = address.flatten().map(|a| a.into()).unwrap_or_default(); + let topics_vec = topics.flatten().unwrap_or_default(); + + // maximum allowed filter len + if topics_vec.len() > 4 { + return Err(serde::de::Error::custom("exceeded maximum topics len")); + } + let mut topics: [Topic; 4] = [ + Default::default(), + Default::default(), + Default::default(), + Default::default(), + ]; + for (idx, topic) in topics_vec.into_iter().enumerate() { + topics[idx] = topic.map(|t| t.into()).unwrap_or_default(); + } + + let block_option = if let Some(block_hash) = block_hash { + FilterBlockOption::AtBlockHash(block_hash) + } else { + FilterBlockOption::Range { + from_block, + to_block, + } + }; + + Ok(Filter { + block_option, + address, + topics, + }) + } + } + + deserializer.deserialize_any(FilterVisitor) + } +} + +/// Union type for representing a single value or a vector of values inside a filter +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub enum ValueOrArray { + /// A single value + Value(T), + /// A vector of values + Array(Vec), +} + +impl From
for ValueOrArray
{ + fn from(src: Address) -> Self { + ValueOrArray::Value(src) + } +} + +impl From> for ValueOrArray
{ + fn from(src: Vec
) -> Self { + ValueOrArray::Array(src) + } +} + +impl From> for ValueOrArray { + fn from(src: Vec) -> Self { + ValueOrArray::Array(src) + } +} + +impl Serialize for ValueOrArray +where + T: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + ValueOrArray::Value(inner) => inner.serialize(serializer), + ValueOrArray::Array(inner) => inner.serialize(serializer), + } + } +} + +impl<'a, T> Deserialize<'a> for ValueOrArray +where + T: DeserializeOwned, +{ + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'a>, + { + let value = serde_json::Value::deserialize(deserializer)?; + + if value.is_null() { + return Ok(ValueOrArray::Array(Vec::new())); + } + + #[derive(Deserialize)] + #[serde(untagged)] + enum Variadic { + Value(T), + Array(Vec), + } + + match serde_json::from_value::>(value).map_err(|err| { + serde::de::Error::custom(format!("Invalid variadic value or array type: {err}")) + })? { + Variadic::Value(val) => Ok(ValueOrArray::Value(val)), + Variadic::Array(arr) => Ok(ValueOrArray::Array(arr)), + } + } +} + +/// Support for matching [Filter]s +#[derive(Debug, Default)] +pub struct FilteredParams { + /// The original filter, if any + pub filter: Option, +} + +impl FilteredParams { + /// Creates a new wrapper type for a [Filter], if any with flattened topics, that can be used + /// for matching + pub fn new(filter: Option) -> Self { + if let Some(filter) = filter { + FilteredParams { + filter: Some(filter), + } + } else { + Default::default() + } + } + + /// Returns the [BloomFilter] for the given address + pub fn address_filter(address: &FilterSet
) -> BloomFilter { + address.to_bloom_filter() + } + + /// Returns the [BloomFilter] for the given topics + pub fn topics_filter(topics: &[FilterSet]) -> Vec { + topics.iter().map(|t| t.to_bloom_filter()).collect() + } + + /// Returns `true` if the bloom matches the topics + pub fn matches_topics(bloom: Bloom, topic_filters: &Vec) -> bool { + if topic_filters.is_empty() { + return true; + } + + // for each filter, iterate through the list of filter blooms. for each set of filter + // (each BloomFilter), the given `bloom` must match at least one of them, unless the list is + // empty (no filters). + for filter in topic_filters.iter() { + if !filter.matches(bloom) { + return false; + } + } + true + } + + /// Returns `true` if the bloom contains one of the address blooms, or the address blooms + /// list is empty (thus, no filters) + pub fn matches_address(bloom: Bloom, address_filter: &BloomFilter) -> bool { + address_filter.matches(bloom) + } + + /// Returns true if the filter matches the given block number + pub fn filter_block_range(&self, block_number: u64) -> bool { + if self.filter.is_none() { + return true; + } + let filter = self.filter.as_ref().unwrap(); + let mut res = true; + + if let Some(BlockNumberOrTag::Number(num)) = filter.block_option.get_from_block() { + if *num > block_number { + res = false; + } + } + + if let Some(to) = filter.block_option.get_to_block() { + match to { + BlockNumberOrTag::Number(num) => { + if *num < block_number { + res = false; + } + } + BlockNumberOrTag::Earliest => { + res = false; + } + _ => {} + } + } + res + } + + /// Returns `true` if the filter matches the given block hash. + pub fn filter_block_hash(&self, block_hash: B256) -> bool { + if let Some(h) = self.filter.as_ref().and_then(|f| f.get_block_hash()) { + if h != block_hash { + return false; + } + } + true + } + + /// Returns `true` if the filter matches the given log. + pub fn filter_address(&self, log: &Log) -> bool { + self.filter + .as_ref() + .map(|f| f.address.matches(&log.address)) + .unwrap_or(true) + } + + /// Returns `true` if the log matches the filter's topics + pub fn filter_topics(&self, log: &Log) -> bool { + let topics = match self.filter.as_ref() { + None => return true, + Some(f) => &f.topics, + }; + for topic_tuple in topics.iter().zip_longest(log.topics.iter()) { + match topic_tuple { + // We exhausted the `log.topics`, so if there's a filter set for + // this topic index, there is no match. Otherwise (empty filter), continue. + Left(filter_topic) => { + if !filter_topic.is_empty() { + return false; + } + } + // We exhausted the filter topics, therefore any subsequent log topic + // will match. + Right(_) => return true, + // Check that `log_topic` is included in `filter_topic` + Both(filter_topic, log_topic) => { + if !filter_topic.matches(log_topic) { + return false; + } + } + } + } + true + } +} +/// Response of the `eth_getFilterChanges` RPC. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum FilterChanges { + /// New logs. + Logs(Vec), + /// New hashes (block or transactions) + Hashes(Vec), + /// New transactions. + Transactions(Vec), + /// Empty result, + Empty, +} + +impl Serialize for FilterChanges { + fn serialize(&self, s: S) -> Result + where + S: Serializer, + { + match self { + FilterChanges::Logs(logs) => logs.serialize(s), + FilterChanges::Hashes(hashes) => hashes.serialize(s), + FilterChanges::Transactions(transactions) => transactions.serialize(s), + FilterChanges::Empty => (&[] as &[serde_json::Value]).serialize(s), + } + } +} + +impl<'de> Deserialize<'de> for FilterChanges { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(untagged)] + enum Changes { + Logs(Vec), + Hashes(Vec), + } + + let changes = Changes::deserialize(deserializer)?; + let changes = match changes { + Changes::Logs(vals) => { + if vals.is_empty() { + FilterChanges::Empty + } else { + FilterChanges::Logs(vals) + } + } + Changes::Hashes(vals) => { + if vals.is_empty() { + FilterChanges::Empty + } else { + FilterChanges::Hashes(vals) + } + } + }; + Ok(changes) + } +} + +/// Owned equivalent of a `SubscriptionId` +#[derive(Debug, PartialEq, Clone, Hash, Eq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +#[serde(untagged)] +pub enum FilterId { + /// Numeric id + Num(u64), + /// String id + Str(String), +} + +#[cfg(feature = "jsonrpsee-types")] +impl From for jsonrpsee_types::SubscriptionId<'_> { + fn from(value: FilterId) -> Self { + match value { + FilterId::Num(n) => jsonrpsee_types::SubscriptionId::Num(n), + FilterId::Str(s) => jsonrpsee_types::SubscriptionId::Str(s.into()), + } + } +} + +#[cfg(feature = "jsonrpsee-types")] +impl From> for FilterId { + fn from(value: jsonrpsee_types::SubscriptionId<'_>) -> Self { + match value { + jsonrpsee_types::SubscriptionId::Num(n) => FilterId::Num(n), + jsonrpsee_types::SubscriptionId::Str(s) => FilterId::Str(s.into_owned()), + } + } +} +/// Specifies the kind of information you wish to receive from the `eth_newPendingTransactionFilter` +/// RPC endpoint. +/// +/// When this type is used in a request, it determines whether the client wishes to receive: +/// - Only the transaction hashes (`Hashes` variant), or +/// - Full transaction details (`Full` variant). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum PendingTransactionFilterKind { + /// Receive only the hashes of the transactions. + #[default] + Hashes, + /// Receive full details of the transactions. + Full, +} + +impl Serialize for PendingTransactionFilterKind { + /// Serializes the `PendingTransactionFilterKind` into a boolean value: + /// - `false` for `Hashes` + /// - `true` for `Full` + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + PendingTransactionFilterKind::Hashes => false.serialize(serializer), + PendingTransactionFilterKind::Full => true.serialize(serializer), + } + } +} + +impl<'a> Deserialize<'a> for PendingTransactionFilterKind { + /// Deserializes a boolean value into `PendingTransactionFilterKind`: + /// - `false` becomes `Hashes` + /// - `true` becomes `Full` + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'a>, + { + let val = Option::::deserialize(deserializer)?; + match val { + Some(true) => Ok(PendingTransactionFilterKind::Full), + _ => Ok(PendingTransactionFilterKind::Hashes), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::U256; + use serde_json::json; + + fn serialize(t: &T) -> serde_json::Value { + serde_json::to_value(t).expect("Failed to serialize value") + } + + #[test] + fn test_empty_filter_topics_list() { + let s = r#"{"fromBlock": "0xfc359e", "toBlock": "0xfc359e", "topics": [["0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"], [], ["0x0000000000000000000000000c17e776cd218252adfca8d4e761d3fe757e9778"]]}"#; + let filter = serde_json::from_str::(s).unwrap(); + similar_asserts::assert_eq!( + filter.topics, + [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925" + .parse::() + .unwrap() + .into(), + Default::default(), + "0x0000000000000000000000000c17e776cd218252adfca8d4e761d3fe757e9778" + .parse::() + .unwrap() + .into(), + Default::default(), + ] + ); + } + + #[test] + fn test_filter_topics_middle_wildcard() { + let s = r#"{"fromBlock": "0xfc359e", "toBlock": "0xfc359e", "topics": [["0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"], [], [null, "0x0000000000000000000000000c17e776cd218252adfca8d4e761d3fe757e9778"]]}"#; + let filter = serde_json::from_str::(s).unwrap(); + similar_asserts::assert_eq!( + filter.topics, + [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925" + .parse::() + .unwrap() + .into(), + Default::default(), + Default::default(), + Default::default(), + ] + ); + } + + #[test] + fn can_serde_value_or_array() { + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] + struct Item { + value: ValueOrArray, + } + + let item = Item { + value: ValueOrArray::Value(U256::from(1u64)), + }; + let json = serde_json::to_value(item.clone()).unwrap(); + let deserialized: Item = serde_json::from_value(json).unwrap(); + assert_eq!(item, deserialized); + + let item = Item { + value: ValueOrArray::Array(vec![U256::from(1u64), U256::ZERO]), + }; + let json = serde_json::to_value(item.clone()).unwrap(); + let deserialized: Item = serde_json::from_value(json).unwrap(); + assert_eq!(item, deserialized); + } + + #[test] + fn filter_serialization_test() { + let t1 = "0000000000000000000000009729a6fbefefc8f6005933898b13dc45c3a2c8b7" + .parse::() + .unwrap(); + let t2 = B256::from([0; 32]); + let t3 = U256::from(123); + + let t1_padded = t1; + let t3_padded = B256::from({ + let mut x = [0; 32]; + x[31] = 123; + x + }); + + let event = "ValueChanged(address,string,string)"; + let t0 = keccak256(event.as_bytes()); + let addr: Address = "f817796F60D268A36a57b8D2dF1B97B14C0D0E1d".parse().unwrap(); + let filter = Filter::new(); + + let ser = serialize(&filter); + assert_eq!(ser, json!({ "topics": [] })); + + let filter = filter.address(ValueOrArray::Value(addr)); + + let ser = serialize(&filter); + assert_eq!(ser, json!({"address" : addr, "topics": []})); + + let filter = filter.event(event); + + // 0 + let ser = serialize(&filter); + assert_eq!(ser, json!({ "address" : addr, "topics": [t0]})); + + // 1 + let ser = serialize(&filter.clone().topic1(t1)); + assert_eq!(ser, json!({ "address" : addr, "topics": [t0, t1_padded]})); + + // 2 + let ser = serialize(&filter.clone().topic2(t2)); + assert_eq!(ser, json!({ "address" : addr, "topics": [t0, null, t2]})); + + // 3 + let ser = serialize(&filter.clone().topic3(t3)); + assert_eq!( + ser, + json!({ "address" : addr, "topics": [t0, null, null, t3_padded]}) + ); + + // 1 & 2 + let ser = serialize(&filter.clone().topic1(t1).topic2(t2)); + assert_eq!( + ser, + json!({ "address" : addr, "topics": [t0, t1_padded, t2]}) + ); + + // 1 & 3 + let ser = serialize(&filter.clone().topic1(t1).topic3(t3)); + assert_eq!( + ser, + json!({ "address" : addr, "topics": [t0, t1_padded, null, t3_padded]}) + ); + + // 2 & 3 + let ser = serialize(&filter.clone().topic2(t2).topic3(t3)); + assert_eq!( + ser, + json!({ "address" : addr, "topics": [t0, null, t2, t3_padded]}) + ); + + // 1 & 2 & 3 + let ser = serialize(&filter.topic1(t1).topic2(t2).topic3(t3)); + assert_eq!( + ser, + json!({ "address" : addr, "topics": [t0, t1_padded, t2, t3_padded]}) + ); + } + + #[test] + fn can_convert_to_ethers_filter() { + let json = json!( + { + "fromBlock": "0x429d3b", + "toBlock": "0x429d3b", + "address": "0xb59f67a8bff5d8cd03f6ac17265c550ed8f33907", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x00000000000000000000000000b46c2526e227482e2ebb8f4c69e4674d262e75", + "0x00000000000000000000000054a2d42a40f51259dedd1978f6c118a0f0eff078" + ] + } + ); + + let filter: Filter = serde_json::from_value(json).unwrap(); + assert_eq!( + filter, + Filter { + block_option: FilterBlockOption::Range { + from_block: Some(4365627u64.into()), + to_block: Some(4365627u64.into()), + }, + address: "0xb59f67a8bff5d8cd03f6ac17265c550ed8f33907" + .parse::
() + .unwrap() + .into(), + topics: [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + .parse::() + .unwrap() + .into(), + "0x00000000000000000000000000b46c2526e227482e2ebb8f4c69e4674d262e75" + .parse::() + .unwrap() + .into(), + "0x00000000000000000000000054a2d42a40f51259dedd1978f6c118a0f0eff078" + .parse::() + .unwrap() + .into(), + Default::default(), + ], + } + ); + } + + #[test] + fn can_convert_to_ethers_filter_with_null_fields() { + let json = json!( + { + "fromBlock": "0x429d3b", + "toBlock": "0x429d3b", + "address": null, + "topics": null + } + ); + + let filter: Filter = serde_json::from_value(json).unwrap(); + assert_eq!( + filter, + Filter { + block_option: FilterBlockOption::Range { + from_block: Some(4365627u64.into()), + to_block: Some(4365627u64.into()), + }, + address: Default::default(), + topics: Default::default(), + } + ); + } +} diff --git a/crates/rpc-types/src/eth/log.rs b/crates/rpc-types/src/eth/log.rs new file mode 100644 index 00000000000..199af24976a --- /dev/null +++ b/crates/rpc-types/src/eth/log.rs @@ -0,0 +1,55 @@ +use alloy_primitives::{Address, Bytes, B256, U256}; +use serde::{Deserialize, Serialize}; + +/// Ethereum Log emitted by a transaction +#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Log { + /// Address + pub address: Address, + /// All topics of the log + pub topics: Vec, + /// Additional data fields of the log + pub data: Bytes, + /// Hash of the block the transaction that emitted this log was mined in + pub block_hash: Option, + /// Number of the block the transaction that emitted this log was mined in + pub block_number: Option, + /// Transaction Hash + pub transaction_hash: Option, + /// Index of the Transaction in the block + pub transaction_index: Option, + /// Log Index in Block + pub log_index: Option, + /// Geth Compatibility Field: whether this log was removed + #[serde(default)] + pub removed: bool, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serde_log() { + let log = Log { + address: Address::with_last_byte(0x69), + topics: vec![B256::with_last_byte(0x69)], + data: Bytes::from_static(&[0x69]), + block_hash: Some(B256::with_last_byte(0x69)), + block_number: Some(U256::from(0x69)), + transaction_hash: Some(B256::with_last_byte(0x69)), + transaction_index: Some(U256::from(0x69)), + log_index: Some(U256::from(0x69)), + removed: false, + }; + let serialized = serde_json::to_string(&log).unwrap(); + assert_eq!( + serialized, + r#"{"address":"0x0000000000000000000000000000000000000069","topics":["0x0000000000000000000000000000000000000000000000000000000000000069"],"data":"0x69","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000069","blockNumber":"0x69","transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000069","transactionIndex":"0x69","logIndex":"0x69","removed":false}"# + ); + + let deserialized: Log = serde_json::from_str(&serialized).unwrap(); + assert_eq!(log, deserialized); + } +} diff --git a/crates/rpc-types/src/eth/mod.rs b/crates/rpc-types/src/eth/mod.rs new file mode 100644 index 00000000000..f992f9543d2 --- /dev/null +++ b/crates/rpc-types/src/eth/mod.rs @@ -0,0 +1,22 @@ +//! Ethereum related types + +mod block; +mod call; +mod fee; +mod filter; +mod log; +pub mod pubsub; +pub mod raw_log; +mod syncing; +mod transaction; +pub mod withdrawal; + +pub use block::*; +pub use call::{Bundle, CallInput, CallInputError, CallRequest, EthCallResponse, StateContext}; +pub use fee::{FeeHistory, TxGasAndReward}; +pub use filter::*; +pub use log::Log; +pub use raw_log::{logs_bloom, Log as RawLog}; +pub use syncing::*; +pub use transaction::*; +pub use withdrawal::Withdrawal; diff --git a/crates/rpc-types/src/eth/pubsub.rs b/crates/rpc-types/src/eth/pubsub.rs new file mode 100644 index 00000000000..2fef14834ce --- /dev/null +++ b/crates/rpc-types/src/eth/pubsub.rs @@ -0,0 +1,169 @@ +//! Ethereum types for pub-sub + +use crate::{ + eth::{Filter, Transaction}, + Log, RichHeader, +}; +use alloy_primitives::B256; +use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; + +/// Subscription result. +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[serde(untagged)] +pub enum SubscriptionResult { + /// New block header. + Header(Box), + /// Log + Log(Box), + /// Transaction hash + TransactionHash(B256), + /// Full Transaction + FullTransaction(Box), + /// SyncStatus + SyncState(PubSubSyncStatus), +} + +/// Response type for a SyncStatus subscription +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum PubSubSyncStatus { + /// If not currently syncing, this should always be `false` + Simple(bool), + /// Current Stats about syncing + Detailed(SyncStatusMetadata), +} + +/// Sync status infos +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[allow(missing_docs)] +pub struct SyncStatusMetadata { + pub syncing: bool, + pub starting_block: u64, + pub current_block: u64, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub highest_block: Option, +} + +impl Serialize for SubscriptionResult { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match *self { + SubscriptionResult::Header(ref header) => header.serialize(serializer), + SubscriptionResult::Log(ref log) => log.serialize(serializer), + SubscriptionResult::TransactionHash(ref hash) => hash.serialize(serializer), + SubscriptionResult::FullTransaction(ref tx) => tx.serialize(serializer), + SubscriptionResult::SyncState(ref sync) => sync.serialize(serializer), + } + } +} + +/// Subscription kind. +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "camelCase")] +pub enum SubscriptionKind { + /// New block headers subscription. + /// + /// Fires a notification each time a new header is appended to the chain, including chain + /// reorganizations. In case of a chain reorganization the subscription will emit all new + /// headers for the new chain. Therefore the subscription can emit multiple headers on the same + /// height. + NewHeads, + /// Logs subscription. + /// + /// Returns logs that are included in new imported blocks and match the given filter criteria. + /// In case of a chain reorganization previous sent logs that are on the old chain will be + /// resent with the removed property set to true. Logs from transactions that ended up in the + /// new chain are emitted. Therefore, a subscription can emit logs for the same transaction + /// multiple times. + Logs, + /// New Pending Transactions subscription. + /// + /// Returns the hash or full tx for all transactions that are added to the pending state and + /// are signed with a key that is available in the node. When a transaction that was + /// previously part of the canonical chain isn't part of the new canonical chain after a + /// reorganization its again emitted. + NewPendingTransactions, + /// Node syncing status subscription. + /// + /// Indicates when the node starts or stops synchronizing. The result can either be a boolean + /// indicating that the synchronization has started (true), finished (false) or an object with + /// various progress indicators. + Syncing, +} + +/// Any additional parameters for a subscription. +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub enum Params { + /// No parameters passed. + #[default] + None, + /// Log parameters. + Logs(Box), + /// Boolean parameter for new pending transactions. + Bool(bool), +} + +impl Params { + /// Returns true if it's a bool parameter. + #[inline] + pub fn is_bool(&self) -> bool { + matches!(self, Params::Bool(_)) + } + + /// Returns true if it's a log parameter. + #[inline] + pub fn is_logs(&self) -> bool { + matches!(self, Params::Logs(_)) + } +} + +impl Serialize for Params { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Params::None => (&[] as &[serde_json::Value]).serialize(serializer), + Params::Logs(logs) => logs.serialize(serializer), + Params::Bool(full) => full.serialize(serializer), + } + } +} + +impl<'a> Deserialize<'a> for Params { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'a>, + { + let v = serde_json::Value::deserialize(deserializer)?; + + if v.is_null() { + return Ok(Params::None); + } + + if let Some(val) = v.as_bool() { + return Ok(Params::Bool(val)); + } + + serde_json::from_value(v) + .map(|f| Params::Logs(Box::new(f))) + .map_err(|e| D::Error::custom(format!("Invalid Pub-Sub parameters: {e}"))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn params_serde() { + let s: Params = serde_json::from_str("true").unwrap(); + assert_eq!(s, Params::Bool(true)); + let s: Params = serde_json::from_str("null").unwrap(); + assert_eq!(s, Params::None); + } +} diff --git a/crates/rpc-types/src/eth/raw_log.rs b/crates/rpc-types/src/eth/raw_log.rs new file mode 100644 index 00000000000..2a12903ec09 --- /dev/null +++ b/crates/rpc-types/src/eth/raw_log.rs @@ -0,0 +1,30 @@ +//! Ethereum log object. + +use alloy_primitives::{Address, Bloom, Bytes, B256}; +use alloy_rlp::{RlpDecodable, RlpEncodable}; + +/// Ethereum Log +#[derive(Clone, Debug, PartialEq, Eq, RlpDecodable, RlpEncodable, Default)] +pub struct Log { + /// Contract that emitted this log. + pub address: Address, + /// Topics of the log. The number of logs depend on what `LOG` opcode is used. + pub topics: Vec, + /// Arbitrary length data. + pub data: Bytes, +} + +/// Calculate receipt logs bloom. +pub fn logs_bloom<'a, It>(logs: It) -> Bloom +where + It: IntoIterator, +{ + let mut bloom = Bloom::ZERO; + for log in logs { + bloom.m3_2048(log.address.as_slice()); + for topic in &log.topics { + bloom.m3_2048(topic.as_slice()); + } + } + bloom +} diff --git a/crates/rpc-types/src/eth/syncing.rs b/crates/rpc-types/src/eth/syncing.rs new file mode 100644 index 00000000000..2ad0c8be0bd --- /dev/null +++ b/crates/rpc-types/src/eth/syncing.rs @@ -0,0 +1,161 @@ +use alloy_primitives::{B512, U256, U64}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::collections::BTreeMap; + +/// Syncing info +#[derive(Debug, Clone, Default, Serialize, Deserialize, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct SyncInfo { + /// Starting block + pub starting_block: U256, + /// Current block + pub current_block: U256, + /// Highest block seen so far + pub highest_block: U256, + /// Warp sync snapshot chunks total. + pub warp_chunks_amount: Option, + /// Warp sync snapshot chunks processed. + pub warp_chunks_processed: Option, +} + +/// Peers info +#[derive(Debug, Clone, Default, Serialize)] +pub struct Peers { + /// Number of active peers + pub active: usize, + /// Number of connected peers + pub connected: usize, + /// Max number of peers + pub max: u32, + /// Detailed information on peers + pub peers: Vec, +} + +/// Number of peers connected to. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum PeerCount { + /// Peer count as integer + Number(u32), + /// Peer count as hex + Hex(U64), +} + +/// Peer connection information +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct PeerInfo { + /// Public node id + pub id: Option, + /// Node client ID + pub name: String, + /// Capabilities + pub caps: Vec, + /// Network information + pub network: PeerNetworkInfo, + /// Protocols information + pub protocols: PeerProtocolsInfo, +} + +/// Peer network information +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PeerNetworkInfo { + /// Remote endpoint address + pub remote_address: String, + /// Local endpoint address + pub local_address: String, +} + +/// Peer protocols information +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct PeerProtocolsInfo { + /// Ethereum protocol information + pub eth: Option, + /// PIP protocol information. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub pip: Option, +} + +/// Peer Ethereum protocol information +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct PeerEthProtocolInfo { + /// Negotiated ethereum protocol version + pub version: u32, + /// Peer total difficulty if known + pub difficulty: Option, + /// SHA3 of peer best block hash + pub head: String, +} + +/// Peer PIP protocol information +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct PipProtocolInfo { + /// Negotiated PIP protocol version + pub version: u32, + /// Peer total difficulty + pub difficulty: U256, + /// SHA3 of peer best block hash + pub head: String, +} + +/// Sync status +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum SyncStatus { + /// Info when syncing + Info(SyncInfo), + /// Not syncing + None, +} + +impl<'de> Deserialize<'de> for SyncStatus { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(untagged)] + enum Syncing { + /// When client is synced to the highest block, eth_syncing with return "false" + None(bool), + IsSyncing(SyncInfo), + } + + match Syncing::deserialize(deserializer)? { + Syncing::None(false) => Ok(SyncStatus::None), + Syncing::None(true) => Err(serde::de::Error::custom( + "eth_syncing returned `true` that is undefined value.", + )), + Syncing::IsSyncing(sync) => Ok(SyncStatus::Info(sync)), + } + } +} + +impl Serialize for SyncStatus { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + SyncStatus::Info(info) => info.serialize(serializer), + SyncStatus::None => serializer.serialize_bool(false), + } + } +} + +/// Propagation statistics for pending transaction. +#[derive(Debug, Clone, Default, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TransactionStats { + /// Block no this transaction was first seen. + pub first_seen: u64, + /// Peers this transaction was propagated to with count. + pub propagated_to: BTreeMap, +} + +/// Chain status. +#[derive(Debug, Clone, Default, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ChainStatus { + /// Describes the gap in the blockchain, if there is one: (first, last) + pub block_gap: Option<(U256, U256)>, +} diff --git a/crates/rpc-types/src/eth/transaction/access_list.rs b/crates/rpc-types/src/eth/transaction/access_list.rs new file mode 100644 index 00000000000..3ee3c677ac1 --- /dev/null +++ b/crates/rpc-types/src/eth/transaction/access_list.rs @@ -0,0 +1,107 @@ +use alloy_primitives::{Address, B256, U256}; +use serde::{Deserialize, Serialize}; + +/// A list of addresses and storage keys that the transaction plans to access. +/// Accesses outside the list are possible, but become more expensive. +#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, Hash, Default)] +#[serde(rename_all = "camelCase")] +pub struct AccessListItem { + /// Account addresses that would be loaded at the start of execution + pub address: Address, + /// Keys of storage that would be loaded at the start of execution + pub storage_keys: Vec, +} + +/// AccessList as defined in EIP-2930 +#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, Hash, Default)] +pub struct AccessList(pub Vec); + +impl AccessList { + /// Converts the list into a vec, expected by revm + pub fn flattened(&self) -> Vec<(Address, Vec)> { + self.flatten().collect() + } + + /// Consumes the type and converts the list into a vec, expected by revm + pub fn into_flattened(self) -> Vec<(Address, Vec)> { + self.into_flatten().collect() + } + + /// Consumes the type and returns an iterator over the list's addresses and storage keys. + pub fn into_flatten(self) -> impl Iterator)> { + self.0.into_iter().map(|item| { + ( + item.address, + item.storage_keys + .into_iter() + .map(|slot| U256::from_be_bytes(slot.0)) + .collect(), + ) + }) + } + + /// Returns an iterator over the list's addresses and storage keys. + pub fn flatten(&self) -> impl Iterator)> + '_ { + self.0.iter().map(|item| { + ( + item.address, + item.storage_keys + .iter() + .map(|slot| U256::from_be_bytes(slot.0)) + .collect(), + ) + }) + } +} + +/// Access list with gas used appended. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)] +#[serde(rename_all = "camelCase")] +pub struct AccessListWithGasUsed { + /// List with accounts accessed during transaction. + pub access_list: AccessList, + /// Estimated gas used with access list. + pub gas_used: U256, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn access_list_serde() { + let list = AccessList(vec![ + AccessListItem { + address: Address::ZERO, + storage_keys: vec![B256::ZERO], + }, + AccessListItem { + address: Address::ZERO, + storage_keys: vec![B256::ZERO], + }, + ]); + let json = serde_json::to_string(&list).unwrap(); + let list2 = serde_json::from_str::(&json).unwrap(); + assert_eq!(list, list2); + } + + #[test] + fn access_list_with_gas_used() { + let list = AccessListWithGasUsed { + access_list: AccessList(vec![ + AccessListItem { + address: Address::ZERO, + storage_keys: vec![B256::ZERO], + }, + AccessListItem { + address: Address::ZERO, + storage_keys: vec![B256::ZERO], + }, + ]), + gas_used: U256::from(100), + }; + let json = serde_json::to_string(&list).unwrap(); + let list2 = serde_json::from_str::(&json).unwrap(); + assert_eq!(list, list2); + } +} diff --git a/crates/rpc-types/src/eth/transaction/common.rs b/crates/rpc-types/src/eth/transaction/common.rs new file mode 100644 index 00000000000..b6217bdb82f --- /dev/null +++ b/crates/rpc-types/src/eth/transaction/common.rs @@ -0,0 +1,19 @@ +//! Commonly used additional types that are not part of the JSON RPC spec but are often required +//! when working with RPC types, such as [Transaction](crate::Transaction) + +use alloy_primitives::{TxHash, B256}; + +/// Additional fields in the context of a block that contains this transaction. +#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)] +pub struct TransactionInfo { + /// Hash of the transaction. + pub hash: Option, + /// Index of the transaction in the block + pub index: Option, + /// Hash of the block. + pub block_hash: Option, + /// Number of the block. + pub block_number: Option, + /// Base fee of the block. + pub base_fee: Option, +} diff --git a/crates/rpc-types/src/eth/transaction/mod.rs b/crates/rpc-types/src/eth/transaction/mod.rs new file mode 100644 index 00000000000..937e0d3b0ff --- /dev/null +++ b/crates/rpc-types/src/eth/transaction/mod.rs @@ -0,0 +1,154 @@ +pub use access_list::{AccessList, AccessListItem, AccessListWithGasUsed}; +use alloy_primitives::{Address, Bytes, B256, U128, U256, U64}; +pub use common::TransactionInfo; +pub use receipt::TransactionReceipt; +pub use request::TransactionRequest; +use serde::{Deserialize, Serialize}; +pub use signature::{Parity, Signature}; +pub use typed::*; + +mod access_list; +mod common; +mod receipt; +mod request; +mod signature; +mod typed; + +/// Transaction object used in RPC +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Transaction { + /// Hash + pub hash: B256, + /// Nonce + pub nonce: U64, + /// Block hash + pub block_hash: Option, + /// Block number + pub block_number: Option, + /// Transaction Index + pub transaction_index: Option, + /// Sender + pub from: Address, + /// Recipient + pub to: Option
, + /// Transferred value + pub value: U256, + /// Gas Price + #[serde(skip_serializing_if = "Option::is_none")] + pub gas_price: Option, + /// Gas amount + pub gas: U256, + /// Max BaseFeePerGas the user is willing to pay. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_fee_per_gas: Option, + /// The miner's tip. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_priority_fee_per_gas: Option, + /// Configured max fee per blob gas for eip-4844 transactions + #[serde(skip_serializing_if = "Option::is_none")] + 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. + #[serde(flatten, skip_serializing_if = "Option::is_none")] + pub signature: Option, + /// The chain id of the transaction, if any. + pub chain_id: Option, + /// Contains the blob hashes for eip-4844 transactions. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub blob_versioned_hashes: Vec, + /// EIP2930 + /// + /// Pre-pay to warm storage access. + #[serde(skip_serializing_if = "Option::is_none")] + pub access_list: Option>, + /// EIP2718 + /// + /// Transaction type, Some(2) for EIP-1559 transaction, + /// Some(1) for AccessList transaction, None for Legacy + #[serde(rename = "type", skip_serializing_if = "Option::is_none")] + pub transaction_type: Option, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::eth::transaction::signature::Parity; + + #[test] + fn serde_transaction() { + let transaction = Transaction { + hash: B256::with_last_byte(1), + nonce: U64::from(2), + block_hash: Some(B256::with_last_byte(3)), + block_number: Some(U256::from(4)), + transaction_index: Some(U256::from(5)), + from: Address::with_last_byte(6), + to: Some(Address::with_last_byte(7)), + value: U256::from(8), + gas_price: Some(U128::from(9)), + gas: U256::from(10), + input: Bytes::from(vec![11, 12, 13]), + signature: Some(Signature { + v: U256::from(14), + r: U256::from(14), + s: U256::from(14), + y_parity: None, + }), + chain_id: Some(U64::from(17)), + blob_versioned_hashes: vec![], + access_list: None, + transaction_type: Some(U64::from(20)), + max_fee_per_gas: Some(U128::from(21)), + max_priority_fee_per_gas: Some(U128::from(22)), + max_fee_per_blob_gas: None, + }; + 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"}"# + ); + let deserialized: Transaction = serde_json::from_str(&serialized).unwrap(); + assert_eq!(transaction, deserialized); + } + + #[test] + fn serde_transaction_with_parity_bit() { + let transaction = Transaction { + hash: B256::with_last_byte(1), + nonce: U64::from(2), + block_hash: Some(B256::with_last_byte(3)), + block_number: Some(U256::from(4)), + transaction_index: Some(U256::from(5)), + from: Address::with_last_byte(6), + to: Some(Address::with_last_byte(7)), + value: U256::from(8), + gas_price: Some(U128::from(9)), + gas: U256::from(10), + input: Bytes::from(vec![11, 12, 13]), + signature: Some(Signature { + v: U256::from(14), + r: U256::from(14), + s: U256::from(14), + y_parity: Some(Parity(true)), + }), + chain_id: Some(U64::from(17)), + blob_versioned_hashes: vec![], + access_list: None, + transaction_type: Some(U64::from(20)), + max_fee_per_gas: Some(U128::from(21)), + max_priority_fee_per_gas: Some(U128::from(22)), + max_fee_per_blob_gas: None, + }; + 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"}"# + ); + let deserialized: Transaction = serde_json::from_str(&serialized).unwrap(); + assert_eq!(transaction, deserialized); + } +} diff --git a/crates/rpc-types/src/eth/transaction/receipt.rs b/crates/rpc-types/src/eth/transaction/receipt.rs new file mode 100644 index 00000000000..7fae30855eb --- /dev/null +++ b/crates/rpc-types/src/eth/transaction/receipt.rs @@ -0,0 +1,54 @@ +use crate::Log; +use alloy_primitives::{Address, Bloom, B256, U128, U256, U64, U8}; +use serde::{Deserialize, Serialize}; + +/// Transaction receipt +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TransactionReceipt { + /// Transaction Hash. + pub transaction_hash: Option, + /// Index within the block. + pub transaction_index: U64, + /// Hash of the block this transaction was included within. + pub block_hash: Option, + /// Number of the block this transaction was included within. + pub block_number: Option, + /// Cumulative gas used within the block after this was executed. + pub cumulative_gas_used: U256, + /// Gas used by this transaction alone. + pub gas_used: Option, + /// The price paid post-execution by the transaction (i.e. base fee + priority fee). Both + /// fields in 1559-style transactions are maximums (max fee + max priority fee), the amount + /// that's actually paid by users can only be determined post-execution + pub effective_gas_price: U128, + /// Blob gas used by the eip-4844 transaction + /// + /// This is None for non eip-4844 transactions + #[serde(skip_serializing_if = "Option::is_none")] + pub blob_gas_used: Option, + /// The price paid by the eip-4844 transaction per blob gas. + #[serde(skip_serializing_if = "Option::is_none")] + pub blob_gas_price: Option, + /// Address of the sender + pub from: Address, + /// Address of the receiver. null when its a contract creation transaction. + pub to: Option
, + /// Contract address created, or None if not a deployment. + pub contract_address: Option
, + /// Logs emitted by this transaction. + pub logs: Vec, + /// Logs bloom + pub logs_bloom: Bloom, + /// The post-transaction stateroot (pre Byzantium) + /// + /// EIP98 makes this optional field, if it's missing then skip serializing it + #[serde(skip_serializing_if = "Option::is_none", rename = "root")] + pub state_root: Option, + /// Status: either 1 (success) or 0 (failure). Only present after activation of EIP-658 + #[serde(skip_serializing_if = "Option::is_none", rename = "status")] + pub status_code: Option, + /// EIP-2718 Transaction type, Some(1) for AccessList transaction, None for Legacy + #[serde(rename = "type")] + pub transaction_type: U8, +} diff --git a/crates/rpc-types/src/eth/transaction/request.rs b/crates/rpc-types/src/eth/transaction/request.rs new file mode 100644 index 00000000000..d9aadd69f12 --- /dev/null +++ b/crates/rpc-types/src/eth/transaction/request.rs @@ -0,0 +1,176 @@ +use crate::eth::transaction::{ + typed::{ + EIP1559TransactionRequest, EIP2930TransactionRequest, LegacyTransactionRequest, + TransactionKind, TypedTransactionRequest, + }, + AccessList, +}; +use alloy_primitives::{Address, Bytes, U128, U256, U64, U8}; +use serde::{Deserialize, Serialize}; + +/// Represents _all_ transaction requests received from RPC +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "camelCase")] +pub struct TransactionRequest { + /// from address + pub from: Option
, + /// to address + pub to: Option
, + /// legacy, gas Price + #[serde(default)] + pub gas_price: Option, + /// max base fee per gas sender is willing to pay + #[serde(default)] + pub max_fee_per_gas: Option, + /// miner tip + #[serde(default)] + pub max_priority_fee_per_gas: Option, + /// gas + pub gas: Option, + /// value of th tx in wei + pub value: Option, + /// Any additional data sent + #[serde(alias = "input")] + pub data: Option, + /// Transaction nonce + pub nonce: Option, + /// warm storage access pre-payment + #[serde(default)] + pub access_list: Option, + /// EIP-2718 type + #[serde(rename = "type")] + pub transaction_type: Option, +} + +// == impl TransactionRequest == + +impl TransactionRequest { + /// Converts the request into a [`TypedTransactionRequest`] + /// + /// Returns None if mutual exclusive fields `gasPrice` and `max_fee_per_gas` are either missing + /// or both set. + pub fn into_typed_request(self) -> Option { + let TransactionRequest { + to, + gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, + gas, + value, + data, + nonce, + mut access_list, + .. + } = self; + match (gas_price, max_fee_per_gas, access_list.take()) { + // legacy transaction + (Some(_), None, None) => { + Some(TypedTransactionRequest::Legacy(LegacyTransactionRequest { + nonce: nonce.unwrap_or_default(), + gas_price: gas_price.unwrap_or_default(), + gas_limit: gas.unwrap_or_default(), + value: value.unwrap_or_default(), + input: data.unwrap_or_default(), + kind: match to { + Some(to) => TransactionKind::Call(to), + None => TransactionKind::Create, + }, + chain_id: None, + })) + } + // EIP2930 + (_, None, Some(access_list)) => Some(TypedTransactionRequest::EIP2930( + EIP2930TransactionRequest { + nonce: nonce.unwrap_or_default(), + gas_price: gas_price.unwrap_or_default(), + gas_limit: gas.unwrap_or_default(), + value: value.unwrap_or_default(), + input: data.unwrap_or_default(), + kind: match to { + Some(to) => TransactionKind::Call(to), + None => TransactionKind::Create, + }, + chain_id: 0, + access_list, + }, + )), + // EIP1559 + (None, Some(_), access_list) | (None, None, access_list @ None) => { + // Empty fields fall back to the canonical transaction schema. + Some(TypedTransactionRequest::EIP1559( + EIP1559TransactionRequest { + nonce: nonce.unwrap_or_default(), + max_fee_per_gas: max_fee_per_gas.unwrap_or_default(), + max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(), + gas_limit: gas.unwrap_or_default(), + value: value.unwrap_or_default(), + input: data.unwrap_or_default(), + kind: match to { + Some(to) => TransactionKind::Call(to), + None => TransactionKind::Create, + }, + chain_id: 0, + access_list: access_list.unwrap_or_default(), + }, + )) + } + + _ => None, + } + } + + /// Sets the gas limit for the transaction. + pub fn gas_limit(mut self, gas_limit: u64) -> Self { + self.gas = Some(U256::from(gas_limit)); + self + } + + /// Sets the nonce for the transaction. + pub fn nonce(mut self, nonce: u64) -> Self { + self.nonce = Some(U64::from(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)); + 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)); + self + } + + /// Sets the recipient address for the transaction. + pub fn to(mut self, to: Address) -> Self { + self.to = Some(to); + self + } + /// Sets the value (amount) for the transaction. + + pub fn value(mut self, value: u128) -> Self { + self.value = Some(U256::from(value)); + self + } + + /// Sets the access list for the transaction. + pub fn access_list(mut self, access_list: AccessList) -> Self { + self.access_list = Some(access_list); + self + } + + /// Sets the input data for the transaction. + pub fn input(mut self, input: Bytes) -> Self { + self.data = Some(input); + self + } + + /// Sets the transactions type for the transactions. + pub fn transaction_type(mut self, transaction_type: u8) -> Self { + self.transaction_type = Some(U8::from(transaction_type)); + self + } +} diff --git a/crates/rpc-types/src/eth/transaction/signature.rs b/crates/rpc-types/src/eth/transaction/signature.rs new file mode 100644 index 00000000000..4878433cd2c --- /dev/null +++ b/crates/rpc-types/src/eth/transaction/signature.rs @@ -0,0 +1,193 @@ +//! Signature related RPC values +use alloy_primitives::U256; +use serde::{Deserialize, Serialize}; + +/// Container type for all signature fields in RPC +#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Serialize, 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. + #[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. +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct Parity( + #[serde( + serialize_with = "serialize_parity", + deserialize_with = "deserialize_parity" + )] + pub bool, +); + +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"`. +fn deserialize_parity<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + let s = 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 + ))), + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use super::*; + + #[test] + 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] + 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] + 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] + 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] + 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] + 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] + 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/src/eth/transaction/typed.rs b/crates/rpc-types/src/eth/transaction/typed.rs new file mode 100644 index 00000000000..b7281fe6d35 --- /dev/null +++ b/crates/rpc-types/src/eth/transaction/typed.rs @@ -0,0 +1,115 @@ +#![allow(missing_docs)] +//! The [`TransactionRequest`][crate::TransactionRequest] is a universal representation for a +//! transaction deserialized from the json input of an RPC call. Depending on what fields are set, +//! it can be converted into the container type [`TypedTransactionRequest`]. + +use crate::eth::transaction::AccessList; +use alloy_primitives::{Address, Bytes, U128, U256, U64}; +use alloy_rlp::{BufMut, Decodable, Encodable, Error as RlpError}; +use serde::{Deserialize, Serialize}; + +/// Container type for various Ethereum transaction requests +/// +/// Its variants correspond to specific allowed transactions: +/// 1. Legacy (pre-EIP2718) [`LegacyTransactionRequest`] +/// 2. EIP2930 (state access lists) [`EIP2930TransactionRequest`] +/// 3. EIP1559 [`EIP1559TransactionRequest`] +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum TypedTransactionRequest { + Legacy(LegacyTransactionRequest), + EIP2930(EIP2930TransactionRequest), + EIP1559(EIP1559TransactionRequest), +} + +/// Represents a legacy transaction request +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LegacyTransactionRequest { + pub nonce: U64, + pub gas_price: U128, + pub gas_limit: U256, + pub kind: TransactionKind, + pub value: U256, + pub input: Bytes, + pub chain_id: Option, +} + +/// Represents an EIP-2930 transaction request +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EIP2930TransactionRequest { + pub chain_id: u64, + pub nonce: U64, + pub gas_price: U128, + pub gas_limit: U256, + pub kind: TransactionKind, + pub value: U256, + pub input: Bytes, + pub access_list: AccessList, +} + +/// Represents an EIP-1559 transaction request +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EIP1559TransactionRequest { + pub chain_id: u64, + pub nonce: U64, + pub max_priority_fee_per_gas: U128, + pub max_fee_per_gas: U128, + pub gas_limit: U256, + pub kind: TransactionKind, + pub value: U256, + pub input: Bytes, + pub access_list: AccessList, +} + +/// Represents the `to` field of a transaction request +/// +/// This determines what kind of transaction this is +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum TransactionKind { + /// Transaction will call this address or transfer funds to this address + Call(Address), + /// No `to` field set, this transaction will create a contract + Create, +} + +// == impl TransactionKind == + +impl TransactionKind { + /// If this transaction is a call this returns the address of the callee + pub fn as_call(&self) -> Option<&Address> { + match self { + TransactionKind::Call(to) => Some(to), + TransactionKind::Create => None, + } + } +} + +impl Encodable for TransactionKind { + fn encode(&self, out: &mut dyn BufMut) { + match self { + TransactionKind::Call(to) => to.encode(out), + TransactionKind::Create => ([]).encode(out), + } + } + fn length(&self) -> usize { + match self { + TransactionKind::Call(to) => to.length(), + TransactionKind::Create => ([]).length(), + } + } +} + +impl Decodable for TransactionKind { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + if let Some(&first) = buf.first() { + if first == 0x80 { + *buf = &buf[1..]; + Ok(TransactionKind::Create) + } else { + let addr =
::decode(buf)?; + Ok(TransactionKind::Call(addr)) + } + } else { + Err(RlpError::InputTooShort) + } + } +} diff --git a/crates/rpc-types/src/eth/withdrawal.rs b/crates/rpc-types/src/eth/withdrawal.rs new file mode 100644 index 00000000000..0ea2322b7d5 --- /dev/null +++ b/crates/rpc-types/src/eth/withdrawal.rs @@ -0,0 +1,125 @@ +//! Withdrawal type and serde helpers. + +use std::mem; + +use crate::serde_helpers::u64_hex; +use alloy_primitives::{Address, U256}; +use alloy_rlp::{RlpDecodable, RlpEncodable}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde_with::{serde_as, DeserializeAs, DisplayFromStr, SerializeAs}; + +/// Multiplier for converting gwei to wei. +pub const GWEI_TO_WEI: u64 = 1_000_000_000; + +/// Withdrawal represents a validator withdrawal from the consensus layer. +#[derive( + Debug, Clone, PartialEq, Eq, Default, Hash, RlpEncodable, RlpDecodable, Serialize, Deserialize, +)] +pub struct Withdrawal { + /// Monotonically increasing identifier issued by consensus layer. + #[serde(with = "u64_hex")] + pub index: u64, + /// Index of validator associated with withdrawal. + #[serde(with = "u64_hex", rename = "validatorIndex")] + pub validator_index: u64, + /// Target address for withdrawn ether. + pub address: Address, + /// Value of the withdrawal in gwei. + #[serde(with = "u64_hex")] + pub amount: u64, +} + +impl Withdrawal { + /// Return the withdrawal amount in wei. + pub fn amount_wei(&self) -> U256 { + U256::from(self.amount) * U256::from(GWEI_TO_WEI) + } + + /// Calculate a heuristic for the in-memory size of the [Withdrawal]. + #[inline] + pub fn size(&self) -> usize { + mem::size_of::() + } +} + +/// Same as [Withdrawal] but respects the Beacon API format which uses snake-case and quoted +/// decimals. +#[serde_as] +#[derive(Serialize, Deserialize)] +pub(crate) struct BeaconAPIWithdrawal { + #[serde_as(as = "DisplayFromStr")] + index: u64, + #[serde_as(as = "DisplayFromStr")] + validator_index: u64, + address: Address, + #[serde_as(as = "DisplayFromStr")] + amount: u64, +} + +impl SerializeAs for BeaconAPIWithdrawal { + fn serialize_as(source: &Withdrawal, serializer: S) -> Result + where + S: Serializer, + { + beacon_api_withdrawals::serialize(source, serializer) + } +} + +impl<'de> DeserializeAs<'de, Withdrawal> for BeaconAPIWithdrawal { + fn deserialize_as(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + beacon_api_withdrawals::deserialize(deserializer) + } +} + +/// A helper serde module to convert from/to the Beacon API which uses quoted decimals rather than +/// big-endian hex. +pub mod beacon_api_withdrawals { + use super::*; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + /// Serialize the payload attributes for the beacon API. + pub fn serialize(payload_attributes: &Withdrawal, serializer: S) -> Result + where + S: Serializer, + { + let withdrawal = BeaconAPIWithdrawal { + index: payload_attributes.index, + validator_index: payload_attributes.validator_index, + address: payload_attributes.address, + amount: payload_attributes.amount, + }; + withdrawal.serialize(serializer) + } + + /// Deserialize the payload attributes for the beacon API. + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let withdrawal = BeaconAPIWithdrawal::deserialize(deserializer)?; + Ok(Withdrawal { + index: withdrawal.index, + validator_index: withdrawal.validator_index, + address: withdrawal.address, + amount: withdrawal.amount, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // + #[test] + fn test_withdrawal_serde_roundtrip() { + let input = r#"[{"index":"0x0","validatorIndex":"0x0","address":"0x0000000000000000000000000000000000001000","amount":"0x1"},{"index":"0x1","validatorIndex":"0x1","address":"0x0000000000000000000000000000000000001001","amount":"0x1"},{"index":"0x2","validatorIndex":"0x2","address":"0x0000000000000000000000000000000000001002","amount":"0x1"},{"index":"0x3","validatorIndex":"0x3","address":"0x0000000000000000000000000000000000001003","amount":"0x1"},{"index":"0x4","validatorIndex":"0x4","address":"0x0000000000000000000000000000000000001004","amount":"0x1"},{"index":"0x5","validatorIndex":"0x5","address":"0x0000000000000000000000000000000000001005","amount":"0x1"},{"index":"0x6","validatorIndex":"0x6","address":"0x0000000000000000000000000000000000001006","amount":"0x1"},{"index":"0x7","validatorIndex":"0x7","address":"0x0000000000000000000000000000000000001007","amount":"0x1"},{"index":"0x8","validatorIndex":"0x8","address":"0x0000000000000000000000000000000000001008","amount":"0x1"},{"index":"0x9","validatorIndex":"0x9","address":"0x0000000000000000000000000000000000001009","amount":"0x1"},{"index":"0xa","validatorIndex":"0xa","address":"0x000000000000000000000000000000000000100a","amount":"0x1"},{"index":"0xb","validatorIndex":"0xb","address":"0x000000000000000000000000000000000000100b","amount":"0x1"},{"index":"0xc","validatorIndex":"0xc","address":"0x000000000000000000000000000000000000100c","amount":"0x1"},{"index":"0xd","validatorIndex":"0xd","address":"0x000000000000000000000000000000000000100d","amount":"0x1"},{"index":"0xe","validatorIndex":"0xe","address":"0x000000000000000000000000000000000000100e","amount":"0x1"},{"index":"0xf","validatorIndex":"0xf","address":"0x000000000000000000000000000000000000100f","amount":"0x1"}]"#; + + let withdrawals: Vec = serde_json::from_str(input).unwrap(); + let s = serde_json::to_string(&withdrawals).unwrap(); + assert_eq!(input, s); + } +} diff --git a/crates/rpc-types/src/lib.rs b/crates/rpc-types/src/lib.rs new file mode 100644 index 00000000000..34be570b13f --- /dev/null +++ b/crates/rpc-types/src/lib.rs @@ -0,0 +1,21 @@ +//! Alloy RPC type definitions. +//! +//! Provides all relevant types for the various RPC endpoints, grouped by namespace. + +#![doc(issue_tracker_base_url = "https://github.com/alloy-rs/alloy/issues/")] +#![warn( + missing_debug_implementations, + missing_docs, + unreachable_pub, + rustdoc::all +)] +#![deny(unused_must_use, rust_2018_idioms)] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +mod eth; +mod rpc; +mod serde_helpers; + +pub use eth::*; +pub use rpc::*; +pub use serde_helpers::*; diff --git a/crates/rpc-types/src/rpc.rs b/crates/rpc-types/src/rpc.rs new file mode 100644 index 00000000000..d754a23c803 --- /dev/null +++ b/crates/rpc-types/src/rpc.rs @@ -0,0 +1,41 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// Represents the `rpc_modules` response, which returns the +/// list of all available modules on that transport and their version +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(transparent)] +pub struct RpcModules { + module_map: HashMap, +} + +impl RpcModules { + /// Create a new instance of RPCModules + pub fn new(module_map: HashMap) -> Self { + Self { module_map } + } + + /// Consumes self and returns the inner hashmap mapping module names to their versions + pub fn into_modules(self) -> HashMap { + self.module_map + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_parse_module_versions_roundtrip() { + let s = r#"{"txpool":"1.0","trace":"1.0","eth":"1.0","web3":"1.0","net":"1.0"}"#; + let module_map = HashMap::from([ + ("txpool".to_owned(), "1.0".to_owned()), + ("trace".to_owned(), "1.0".to_owned()), + ("eth".to_owned(), "1.0".to_owned()), + ("web3".to_owned(), "1.0".to_owned()), + ("net".to_owned(), "1.0".to_owned()), + ]); + let m = RpcModules::new(module_map); + let de_serialized: RpcModules = serde_json::from_str(s).unwrap(); + assert_eq!(de_serialized, m); + } +} diff --git a/crates/rpc-types/src/serde_helpers/json_u256.rs b/crates/rpc-types/src/serde_helpers/json_u256.rs new file mode 100644 index 00000000000..162d55a821e --- /dev/null +++ b/crates/rpc-types/src/serde_helpers/json_u256.rs @@ -0,0 +1,93 @@ +//! Json U256 serde helpers. + +use alloy_primitives::U256; +use serde::{ + de::{Error, Visitor}, + Deserialize, Deserializer, Serialize, Serializer, +}; +use std::{fmt, str::FromStr}; + +/// Wrapper around primitive U256 type that also supports deserializing numbers +#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] +pub struct JsonU256(pub U256); + +impl From for U256 { + fn from(value: JsonU256) -> Self { + value.0 + } +} + +impl From for JsonU256 { + fn from(value: U256) -> Self { + JsonU256(value) + } +} + +impl Serialize for JsonU256 { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.0.serialize(serializer) + } +} + +impl<'a> Deserialize<'a> for JsonU256 { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'a>, + { + deserializer.deserialize_any(JsonU256Visitor) + } +} + +struct JsonU256Visitor; + +impl<'a> Visitor<'a> for JsonU256Visitor { + type Value = JsonU256; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(formatter, "a hex encoding or decimal number") + } + + fn visit_u64(self, value: u64) -> Result + where + E: Error, + { + Ok(JsonU256(U256::from(value))) + } + + fn visit_str(self, value: &str) -> Result + where + E: Error, + { + let value = match value.len() { + 0 => U256::ZERO, + 2 if value.starts_with("0x") => U256::ZERO, + _ if value.starts_with("0x") => U256::from_str(value).map_err(|e| { + Error::custom(format!("Parsing JsonU256 as hex failed {value}: {e}")) + })?, + _ => U256::from_str_radix(value, 10).map_err(|e| { + Error::custom(format!("Parsing JsonU256 as decimal failed {value}: {e:?}")) + })?, + }; + + Ok(JsonU256(value)) + } + + fn visit_string(self, value: String) -> Result + where + E: Error, + { + self.visit_str(value.as_ref()) + } +} + +/// Supports parsing `U256` numbers as strings via [JsonU256] +pub fn deserialize_json_u256<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let num = JsonU256::deserialize(deserializer)?; + Ok(num.into()) +} diff --git a/crates/rpc-types/src/serde_helpers/mod.rs b/crates/rpc-types/src/serde_helpers/mod.rs new file mode 100644 index 00000000000..1c45b0d56d4 --- /dev/null +++ b/crates/rpc-types/src/serde_helpers/mod.rs @@ -0,0 +1,30 @@ +//! Serde helpers for primitive types. + +use alloy_primitives::U256; +use serde::{Deserialize, Deserializer, Serializer}; + +pub mod json_u256; +pub mod num; +/// Storage related helpers. +pub mod storage; +pub mod u64_hex; + +/// Deserializes the input into a U256, accepting both 0x-prefixed hex and decimal strings with +/// arbitrary precision, defined by serde_json's [`Number`](serde_json::Number). +pub fn from_int_or_hex<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + num::NumberOrHexU256::deserialize(deserializer)?.try_into_u256() +} + +/// Serialize a byte vec as a hex string _without_ the "0x" prefix. +/// +/// This behaves the same as [`hex::encode`](alloy_primitives::hex::encode). +pub fn serialize_hex_string_no_prefix(x: T, s: S) -> Result +where + S: Serializer, + T: AsRef<[u8]>, +{ + s.serialize_str(&alloy_primitives::hex::encode(x.as_ref())) +} diff --git a/crates/rpc-types/src/serde_helpers/num.rs b/crates/rpc-types/src/serde_helpers/num.rs new file mode 100644 index 00000000000..669d33f03ed --- /dev/null +++ b/crates/rpc-types/src/serde_helpers/num.rs @@ -0,0 +1,139 @@ +//! Numeric serde helpers. + +use alloy_primitives::{U256, U64}; +use serde::{de, Deserialize, Deserializer, Serialize}; +use std::str::FromStr; + +/// A `u64` wrapper type that deserializes from hex or a u64 and serializes as hex. +/// +/// +/// ```rust +/// use alloy_rpc_types::num::U64HexOrNumber; +/// let number_json = "100"; +/// let hex_json = "\"0x64\""; +/// +/// let number: U64HexOrNumber = serde_json::from_str(number_json).unwrap(); +/// let hex: U64HexOrNumber = serde_json::from_str(hex_json).unwrap(); +/// assert_eq!(number, hex); +/// assert_eq!(hex.to(), 100); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] +pub struct U64HexOrNumber(U64); + +impl U64HexOrNumber { + /// Returns the wrapped u64 + pub fn to(self) -> u64 { + self.0.to() + } +} + +impl From for U64HexOrNumber { + fn from(value: u64) -> Self { + Self(U64::from(value)) + } +} + +impl From for U64HexOrNumber { + fn from(value: U64) -> Self { + Self(value) + } +} + +impl From for u64 { + fn from(value: U64HexOrNumber) -> Self { + value.to() + } +} + +impl From for U64 { + fn from(value: U64HexOrNumber) -> Self { + value.0 + } +} + +impl<'de> Deserialize<'de> for U64HexOrNumber { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(untagged)] + enum NumberOrHexU64 { + Hex(U64), + Int(u64), + } + match NumberOrHexU64::deserialize(deserializer)? { + NumberOrHexU64::Int(val) => Ok(val.into()), + NumberOrHexU64::Hex(val) => Ok(val.into()), + } + } +} + +/// serde functions for handling primitive optional `u64` as [U64] +pub mod u64_hex_or_decimal_opt { + use crate::serde_helpers::num::U64HexOrNumber; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + /// Deserializes an `u64` accepting a hex quantity string with optional 0x prefix or + /// a number + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + match Option::::deserialize(deserializer)? { + Some(val) => Ok(Some(val.into())), + None => Ok(None), + } + } + + /// Serializes u64 as hex string + pub fn serialize(value: &Option, s: S) -> Result { + match value { + Some(val) => U64HexOrNumber::from(*val).serialize(s), + None => s.serialize_none(), + } + } +} + +/// Deserializes the input into an `Option`, using [`from_int_or_hex`] to deserialize the +/// inner value. +pub fn from_int_or_hex_opt<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + match Option::::deserialize(deserializer)? { + Some(val) => val.try_into_u256().map(Some), + None => Ok(None), + } +} + +/// An enum that represents either a [serde_json::Number] integer, or a hex [U256]. +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum NumberOrHexU256 { + /// An integer + Int(serde_json::Number), + /// A hex U256 + Hex(U256), +} + +impl NumberOrHexU256 { + /// Tries to convert this into a [U256]]. + pub fn try_into_u256(self) -> Result { + match self { + NumberOrHexU256::Int(num) => { + U256::from_str(num.to_string().as_str()).map_err(E::custom) + } + NumberOrHexU256::Hex(val) => Ok(val), + } + } +} + +/// Deserializes the input into a U256, accepting both 0x-prefixed hex and decimal strings with +/// arbitrary precision, defined by serde_json's [`Number`](serde_json::Number). +pub fn from_int_or_hex<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + NumberOrHexU256::deserialize(deserializer)?.try_into_u256() +} diff --git a/crates/rpc-types/src/serde_helpers/storage.rs b/crates/rpc-types/src/serde_helpers/storage.rs new file mode 100644 index 00000000000..952f5ebe0ec --- /dev/null +++ b/crates/rpc-types/src/serde_helpers/storage.rs @@ -0,0 +1,102 @@ +use alloy_primitives::{Bytes, B256, U256}; +use serde::{Deserialize, Deserializer, Serialize}; +use std::{collections::HashMap, fmt::Write}; + +/// A storage key type that can be serialized to and from a hex string up to 32 bytes. Used for +/// `eth_getStorageAt` and `eth_getProof` RPCs. +/// +/// This is a wrapper type meant to mirror geth's serialization and deserialization behavior for +/// storage keys. +/// +/// In `eth_getStorageAt`, this is used for deserialization of the `index` field. Internally, the +/// index is a [B256], but in `eth_getStorageAt` requests, its serialization can be _up to_ 32 +/// bytes. To support this, the storage key is deserialized first as a U256, and converted to a +/// B256 for use internally. +/// +/// `eth_getProof` also takes storage keys up to 32 bytes as input, so the `keys` field is +/// similarly deserialized. However, geth populates the storage proof `key` fields in the response +/// by mirroring the `key` field used in the input. +/// * See how `storageKey`s (the input) are populated in the `StorageResult` (the output): +/// +/// +/// The contained [B256] and From implementation for String are used to preserve the input and +/// implement this behavior from geth. +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +#[serde(from = "U256", into = "String")] +pub struct JsonStorageKey(pub B256); + +impl From for JsonStorageKey { + fn from(value: U256) -> Self { + // SAFETY: Address (B256) and U256 have the same number of bytes + JsonStorageKey(B256::from(value.to_be_bytes())) + } +} + +impl From for String { + fn from(value: JsonStorageKey) -> Self { + // SAFETY: Address (B256) and U256 have the same number of bytes + let uint = U256::from_be_bytes(value.0 .0); + + // serialize byte by byte + // + // this is mainly so we can return an output that hive testing expects, because the + // `eth_getProof` implementation in geth simply mirrors the input + // + // see the use of `hexKey` in the `eth_getProof` response: + // + let bytes = uint.to_be_bytes_trimmed_vec(); + let mut hex = String::with_capacity(2 + bytes.len() * 2); + hex.push_str("0x"); + for byte in bytes { + write!(hex, "{:02x}", byte).unwrap(); + } + hex + } +} + +/// Converts a Bytes value into a B256, accepting inputs that are less than 32 bytes long. These +/// inputs will be left padded with zeros. +pub fn from_bytes_to_b256<'de, D>(bytes: Bytes) -> Result +where + D: Deserializer<'de>, +{ + if bytes.0.len() > 32 { + return Err(serde::de::Error::custom("input too long to be a B256")); + } + + // left pad with zeros to 32 bytes + let mut padded = [0u8; 32]; + padded[32 - bytes.0.len()..].copy_from_slice(&bytes.0); + + // then convert to B256 without a panic + Ok(B256::from_slice(&padded)) +} + +/// Deserializes the input into an Option>, using [from_bytes_to_b256] which +/// allows cropped values: +/// +/// ```json +/// { +/// "0x0000000000000000000000000000000000000000000000000000000000000001": "0x22" +/// } +/// ``` +pub fn deserialize_storage_map<'de, D>( + deserializer: D, +) -> Result>, D::Error> +where + D: Deserializer<'de>, +{ + let map = Option::>::deserialize(deserializer)?; + match map { + Some(mut map) => { + let mut res_map = HashMap::with_capacity(map.len()); + for (k, v) in map.drain() { + let k_deserialized = from_bytes_to_b256::<'de, D>(k)?; + let v_deserialized = from_bytes_to_b256::<'de, D>(v)?; + res_map.insert(k_deserialized, v_deserialized); + } + Ok(Some(res_map)) + } + None => Ok(None), + } +} diff --git a/crates/rpc-types/src/serde_helpers/u64_hex.rs b/crates/rpc-types/src/serde_helpers/u64_hex.rs new file mode 100644 index 00000000000..dc8f9a96ef9 --- /dev/null +++ b/crates/rpc-types/src/serde_helpers/u64_hex.rs @@ -0,0 +1,18 @@ +//! Helper to deserialize an `u64` from [U64] accepting a hex quantity string with optional 0x +//! prefix + +use alloy_primitives::U64; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +/// Deserializes an `u64` from [U64] accepting a hex quantity string with optional 0x prefix +pub fn deserialize<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + U64::deserialize(deserializer).map(|val| val.to()) +} + +/// Serializes u64 as hex string +pub fn serialize(value: &u64, s: S) -> Result { + U64::from(*value).serialize(s) +} diff --git a/crates/rpc-types/test_data/call_tracer/default.json b/crates/rpc-types/test_data/call_tracer/default.json new file mode 100644 index 00000000000..553b2a39795 --- /dev/null +++ b/crates/rpc-types/test_data/call_tracer/default.json @@ -0,0 +1,21 @@ +{ + "calls": [ + { + "from": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "gas": "0x6d05", + "gasUsed": "0x0", + "input": "0x", + "to": "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "type": "CALL", + "value": "0x6f05b59d3b20000" + } + ], + "from": "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb", + "gas": "0x10738", + "gasUsed": "0x9751", + "input": "0x63e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "to": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "type": "CALL", + "value": "0x0" +} diff --git a/crates/rpc-types/test_data/call_tracer/legacy.json b/crates/rpc-types/test_data/call_tracer/legacy.json new file mode 100644 index 00000000000..b89e7ae86c4 --- /dev/null +++ b/crates/rpc-types/test_data/call_tracer/legacy.json @@ -0,0 +1,19 @@ +{ + "calls": [ + { + "from": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "input": "0x", + "to": "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "type": "CALL", + "value": "0x6f05b59d3b20000" + } + ], + "from": "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb", + "gas": "0x10738", + "gasUsed": "0x9751", + "input": "0x63e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "to": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "type": "CALL", + "value": "0x0" +} diff --git a/crates/rpc-types/test_data/call_tracer/only_top_call.json b/crates/rpc-types/test_data/call_tracer/only_top_call.json new file mode 100644 index 00000000000..327bb427874 --- /dev/null +++ b/crates/rpc-types/test_data/call_tracer/only_top_call.json @@ -0,0 +1,10 @@ +{ + "from": "0x4f5777744b500616697cb655dcb02ee6cd51deb5", + "gas": "0x2dced", + "gasUsed": "0x1a9e5", + "to": "0x200edd17f30485a8735878661960cd7a9a95733f", + "input": "0xba51a6df0000000000000000000000000000000000000000000000000000000000000000", + "output": "0xba51a6df00000000000000000000000000000000000000000000000000000000", + "value": "0x8ac7230489e80000", + "type": "CALL" +} diff --git a/crates/rpc-types/test_data/call_tracer/with_log.json b/crates/rpc-types/test_data/call_tracer/with_log.json new file mode 100644 index 00000000000..2528bbc0484 --- /dev/null +++ b/crates/rpc-types/test_data/call_tracer/with_log.json @@ -0,0 +1,20 @@ +{ + "from": "0xd1220a0cf47c7b9be7a2e6ba89f429762e7b9adb", + "gas": "0x1f36d", + "gasUsed": "0xc6a5", + "to": "0xf4eced2f682ce333f96f2d8966c613ded8fc95dd", + "input": "0xa9059cbb000000000000000000000000dbf03b407c01e7cd3cbea99509d93f8dddc8c6fb0000000000000000000000000000000000000000000000000000000000989680", + "logs": [ + { + "address": "0xf4eced2f682ce333f96f2d8966c613ded8fc95dd", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000d1220a0cf47c7b9be7a2e6ba89f429762e7b9adb", + "0x000000000000000000000000dbf03b407c01e7cd3cbea99509d93f8dddc8c6fb" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000989680" + } + ], + "value": "0x0", + "type": "CALL" +} diff --git a/crates/rpc-types/test_data/default/structlogs_01.json b/crates/rpc-types/test_data/default/structlogs_01.json new file mode 100644 index 00000000000..1812c5d3e33 --- /dev/null +++ b/crates/rpc-types/test_data/default/structlogs_01.json @@ -0,0 +1 @@ +{"structLogs":[{"pc":0,"op":"PUSH1","gas":24595,"gasCost":3,"depth":1,"stack":[],"memory":[]},{"pc":2,"op":"PUSH1","gas":24592,"gasCost":3,"depth":1,"stack":["0x80"],"memory":[]},{"pc":4,"op":"MSTORE","gas":24589,"gasCost":12,"depth":1,"stack":["0x80","0x40"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000"]},{"pc":5,"op":"CALLVALUE","gas":24577,"gasCost":2,"depth":1,"stack":[],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":6,"op":"DUP1","gas":24575,"gasCost":3,"depth":1,"stack":["0x0"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":7,"op":"ISZERO","gas":24572,"gasCost":3,"depth":1,"stack":["0x0","0x0"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8,"op":"PUSH2","gas":24569,"gasCost":3,"depth":1,"stack":["0x0","0x1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":11,"op":"JUMPI","gas":24566,"gasCost":10,"depth":1,"stack":["0x0","0x1","0x10"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":16,"op":"JUMPDEST","gas":24556,"gasCost":1,"depth":1,"stack":["0x0"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":17,"op":"POP","gas":24555,"gasCost":2,"depth":1,"stack":["0x0"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":18,"op":"PUSH1","gas":24553,"gasCost":3,"depth":1,"stack":[],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":20,"op":"CALLDATASIZE","gas":24550,"gasCost":2,"depth":1,"stack":["0x4"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":21,"op":"LT","gas":24548,"gasCost":3,"depth":1,"stack":["0x4","0x44"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":22,"op":"PUSH2","gas":24545,"gasCost":3,"depth":1,"stack":["0x0"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":25,"op":"JUMPI","gas":24542,"gasCost":10,"depth":1,"stack":["0x0","0x1fb"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":26,"op":"PUSH1","gas":24532,"gasCost":3,"depth":1,"stack":[],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":28,"op":"CALLDATALOAD","gas":24529,"gasCost":3,"depth":1,"stack":["0x0"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":29,"op":"PUSH1","gas":24526,"gasCost":3,"depth":1,"stack":["0xa22cb46500000000000000000000000000000000000111abe46ff893f3b2fdf1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":31,"op":"SHR","gas":24523,"gasCost":3,"depth":1,"stack":["0xa22cb46500000000000000000000000000000000000111abe46ff893f3b2fdf1","0xe0"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":32,"op":"DUP1","gas":24520,"gasCost":3,"depth":1,"stack":["0xa22cb465"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":33,"op":"PUSH4","gas":24517,"gasCost":3,"depth":1,"stack":["0xa22cb465","0xa22cb465"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":38,"op":"GT","gas":24514,"gasCost":3,"depth":1,"stack":["0xa22cb465","0xa22cb465","0x6352211e"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":39,"op":"PUSH2","gas":24511,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x0"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":42,"op":"JUMPI","gas":24508,"gasCost":10,"depth":1,"stack":["0xa22cb465","0x0","0x11a"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":43,"op":"DUP1","gas":24498,"gasCost":3,"depth":1,"stack":["0xa22cb465"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":44,"op":"PUSH4","gas":24495,"gasCost":3,"depth":1,"stack":["0xa22cb465","0xa22cb465"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":49,"op":"GT","gas":24492,"gasCost":3,"depth":1,"stack":["0xa22cb465","0xa22cb465","0x95d89b41"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":50,"op":"PUSH2","gas":24489,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x0"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":53,"op":"JUMPI","gas":24486,"gasCost":10,"depth":1,"stack":["0xa22cb465","0x0","0xad"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":54,"op":"DUP1","gas":24476,"gasCost":3,"depth":1,"stack":["0xa22cb465"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":55,"op":"PUSH4","gas":24473,"gasCost":3,"depth":1,"stack":["0xa22cb465","0xa22cb465"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":60,"op":"GT","gas":24470,"gasCost":3,"depth":1,"stack":["0xa22cb465","0xa22cb465","0xdb006a75"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":61,"op":"PUSH2","gas":24467,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":64,"op":"JUMPI","gas":24464,"gasCost":10,"depth":1,"stack":["0xa22cb465","0x1","0x7c"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":124,"op":"JUMPDEST","gas":24454,"gasCost":1,"depth":1,"stack":["0xa22cb465"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":125,"op":"DUP1","gas":24453,"gasCost":3,"depth":1,"stack":["0xa22cb465"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":126,"op":"PUSH4","gas":24450,"gasCost":3,"depth":1,"stack":["0xa22cb465","0xa22cb465"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":131,"op":"EQ","gas":24447,"gasCost":3,"depth":1,"stack":["0xa22cb465","0xa22cb465","0x95d89b41"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":132,"op":"PUSH2","gas":24444,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x0"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":135,"op":"JUMPI","gas":24441,"gasCost":10,"depth":1,"stack":["0xa22cb465","0x0","0x3c7"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":136,"op":"DUP1","gas":24431,"gasCost":3,"depth":1,"stack":["0xa22cb465"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":137,"op":"PUSH4","gas":24428,"gasCost":3,"depth":1,"stack":["0xa22cb465","0xa22cb465"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":142,"op":"EQ","gas":24425,"gasCost":3,"depth":1,"stack":["0xa22cb465","0xa22cb465","0xa22cb465"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":143,"op":"PUSH2","gas":24422,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":146,"op":"JUMPI","gas":24419,"gasCost":10,"depth":1,"stack":["0xa22cb465","0x1","0x3cf"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":975,"op":"JUMPDEST","gas":24409,"gasCost":1,"depth":1,"stack":["0xa22cb465"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":976,"op":"PUSH2","gas":24408,"gasCost":3,"depth":1,"stack":["0xa22cb465"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":979,"op":"PUSH2","gas":24405,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":982,"op":"CALLDATASIZE","gas":24402,"gasCost":2,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":983,"op":"PUSH1","gas":24400,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":985,"op":"PUSH2","gas":24397,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":988,"op":"JUMP","gas":24394,"gasCost":8,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x2231"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8753,"op":"JUMPDEST","gas":24386,"gasCost":1,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8754,"op":"PUSH1","gas":24385,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8756,"op":"DUP1","gas":24382,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8757,"op":"PUSH1","gas":24379,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8759,"op":"DUP4","gas":24376,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x40"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8760,"op":"DUP6","gas":24373,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x40","0x4"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8761,"op":"SUB","gas":24370,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x40","0x4","0x44"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8762,"op":"SLT","gas":24367,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x40","0x40"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8763,"op":"ISZERO","gas":24364,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x0"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8764,"op":"PUSH2","gas":24361,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8767,"op":"JUMPI","gas":24358,"gasCost":10,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x1","0x2243"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8771,"op":"JUMPDEST","gas":24348,"gasCost":1,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8772,"op":"PUSH2","gas":24347,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8775,"op":"DUP4","gas":24344,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x224c"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8776,"op":"PUSH2","gas":24341,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x224c","0x4"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8779,"op":"JUMP","gas":24338,"gasCost":8,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x224c","0x4","0x211a"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8474,"op":"JUMPDEST","gas":24330,"gasCost":1,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x224c","0x4"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8475,"op":"DUP1","gas":24329,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x224c","0x4"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8476,"op":"CALLDATALOAD","gas":24326,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x224c","0x4","0x4"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8477,"op":"PUSH1","gas":24323,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x224c","0x4","0x111abe46ff893f3b2fdf1f759a8a8"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8479,"op":"PUSH1","gas":24320,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x224c","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8481,"op":"PUSH1","gas":24317,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x224c","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8483,"op":"SHL","gas":24314,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x224c","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x1","0xa0"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8484,"op":"SUB","gas":24311,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x224c","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x10000000000000000000000000000000000000000"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8485,"op":"DUP2","gas":24308,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x224c","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0xffffffffffffffffffffffffffffffffffffffff"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8486,"op":"AND","gas":24305,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x224c","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0xffffffffffffffffffffffffffffffffffffffff","0x111abe46ff893f3b2fdf1f759a8a8"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8487,"op":"DUP2","gas":24302,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x224c","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x111abe46ff893f3b2fdf1f759a8a8"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8488,"op":"EQ","gas":24299,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x224c","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x111abe46ff893f3b2fdf1f759a8a8","0x111abe46ff893f3b2fdf1f759a8a8"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8489,"op":"PUSH2","gas":24296,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x224c","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8492,"op":"JUMPI","gas":24293,"gasCost":10,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x224c","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x18b8"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":6328,"op":"JUMPDEST","gas":24283,"gasCost":1,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x224c","0x4","0x111abe46ff893f3b2fdf1f759a8a8"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":6329,"op":"SWAP2","gas":24282,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x224c","0x4","0x111abe46ff893f3b2fdf1f759a8a8"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":6330,"op":"SWAP1","gas":24279,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x111abe46ff893f3b2fdf1f759a8a8","0x4","0x224c"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":6331,"op":"POP","gas":24276,"gasCost":2,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x111abe46ff893f3b2fdf1f759a8a8","0x224c","0x4"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":6332,"op":"JUMP","gas":24274,"gasCost":8,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x111abe46ff893f3b2fdf1f759a8a8","0x224c"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8780,"op":"JUMPDEST","gas":24266,"gasCost":1,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x111abe46ff893f3b2fdf1f759a8a8"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8781,"op":"SWAP2","gas":24265,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x0","0x0","0x111abe46ff893f3b2fdf1f759a8a8"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8782,"op":"POP","gas":24262,"gasCost":2,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x0","0x0"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8783,"op":"PUSH1","gas":24260,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x0"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8785,"op":"DUP4","gas":24257,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x0","0x20"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8786,"op":"ADD","gas":24254,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x0","0x20","0x4"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8787,"op":"CALLDATALOAD","gas":24251,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x0","0x24"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8788,"op":"DUP1","gas":24248,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x0","0x1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8789,"op":"ISZERO","gas":24245,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x0","0x1","0x1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8790,"op":"ISZERO","gas":24242,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x0","0x1","0x0"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8791,"op":"DUP2","gas":24239,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x0","0x1","0x1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8792,"op":"EQ","gas":24236,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x0","0x1","0x1","0x1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8793,"op":"PUSH2","gas":24233,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x0","0x1","0x1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8796,"op":"JUMPI","gas":24230,"gasCost":10,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x0","0x1","0x1","0x2260"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8800,"op":"JUMPDEST","gas":24220,"gasCost":1,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x0","0x1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8801,"op":"DUP1","gas":24219,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x0","0x1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8802,"op":"SWAP2","gas":24216,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x0","0x1","0x1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8803,"op":"POP","gas":24213,"gasCost":2,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x1","0x0"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8804,"op":"POP","gas":24211,"gasCost":2,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8805,"op":"SWAP3","gas":24209,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x44","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8806,"op":"POP","gas":24206,"gasCost":2,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x1","0x4","0x111abe46ff893f3b2fdf1f759a8a8","0x44"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8807,"op":"SWAP3","gas":24204,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x3dd","0x1","0x4","0x111abe46ff893f3b2fdf1f759a8a8"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8808,"op":"SWAP1","gas":24201,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x4","0x3dd"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8809,"op":"POP","gas":24198,"gasCost":2,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x3dd","0x4"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":8810,"op":"JUMP","gas":24196,"gasCost":8,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x3dd"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":989,"op":"JUMPDEST","gas":24188,"gasCost":1,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":990,"op":"PUSH2","gas":24187,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":993,"op":"JUMP","gas":24184,"gasCost":8,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xc94"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3220,"op":"JUMPDEST","gas":24176,"gasCost":1,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3221,"op":"PUSH1","gas":24175,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3223,"op":"PUSH1","gas":24172,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3225,"op":"PUSH1","gas":24169,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x1","0x1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3227,"op":"SHL","gas":24166,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x1","0x1","0xa0"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3228,"op":"SUB","gas":24163,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x1","0x10000000000000000000000000000000000000000"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3229,"op":"DUP3","gas":24160,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xffffffffffffffffffffffffffffffffffffffff"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3230,"op":"AND","gas":24157,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xffffffffffffffffffffffffffffffffffffffff","0x111abe46ff893f3b2fdf1f759a8a8"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3231,"op":"CALLER","gas":24154,"gasCost":2,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x111abe46ff893f3b2fdf1f759a8a8"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3232,"op":"EQ","gas":24152,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x111abe46ff893f3b2fdf1f759a8a8","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3233,"op":"ISZERO","gas":24149,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x0"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3234,"op":"PUSH2","gas":24146,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3237,"op":"JUMPI","gas":24143,"gasCost":10,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x1","0xced"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3309,"op":"JUMPDEST","gas":24133,"gasCost":1,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3310,"op":"CALLER","gas":24132,"gasCost":2,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3311,"op":"PUSH1","gas":24130,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3313,"op":"DUP2","gas":24127,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3314,"op":"DUP2","gas":24124,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3315,"op":"MSTORE","gas":24121,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0"],"memory":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3316,"op":"PUSH1","gas":24118,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0"],"memory":["000000000000000000000000a7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3318,"op":"PUSH1","gas":24115,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0","0x5"],"memory":["000000000000000000000000a7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3320,"op":"SWAP1","gas":24112,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0","0x5","0x20"],"memory":["000000000000000000000000a7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3321,"op":"DUP2","gas":24109,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0","0x20","0x5"],"memory":["000000000000000000000000a7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3322,"op":"MSTORE","gas":24106,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0","0x20","0x5","0x20"],"memory":["000000000000000000000000a7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3323,"op":"PUSH1","gas":24103,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0","0x20"],"memory":["000000000000000000000000a7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0000000000000000000000000000000000000000000000000000000000000005","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3325,"op":"DUP1","gas":24100,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0","0x20","0x40"],"memory":["000000000000000000000000a7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0000000000000000000000000000000000000000000000000000000000000005","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3326,"op":"DUP4","gas":24097,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0","0x20","0x40","0x40"],"memory":["000000000000000000000000a7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0000000000000000000000000000000000000000000000000000000000000005","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3327,"op":"KECCAK256","gas":24094,"gasCost":42,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0","0x20","0x40","0x40","0x0"],"memory":["000000000000000000000000a7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0000000000000000000000000000000000000000000000000000000000000005","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3328,"op":"PUSH1","gas":24052,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0","0x20","0x40","0x7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2"],"memory":["000000000000000000000000a7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0000000000000000000000000000000000000000000000000000000000000005","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3330,"op":"PUSH1","gas":24049,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0","0x20","0x40","0x7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0x1"],"memory":["000000000000000000000000a7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0000000000000000000000000000000000000000000000000000000000000005","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3332,"op":"PUSH1","gas":24046,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0","0x20","0x40","0x7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0x1","0x1"],"memory":["000000000000000000000000a7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0000000000000000000000000000000000000000000000000000000000000005","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3334,"op":"SHL","gas":24043,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0","0x20","0x40","0x7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0x1","0x1","0xa0"],"memory":["000000000000000000000000a7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0000000000000000000000000000000000000000000000000000000000000005","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3335,"op":"SUB","gas":24040,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0","0x20","0x40","0x7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0x1","0x10000000000000000000000000000000000000000"],"memory":["000000000000000000000000a7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0000000000000000000000000000000000000000000000000000000000000005","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3336,"op":"DUP8","gas":24037,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0","0x20","0x40","0x7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0xffffffffffffffffffffffffffffffffffffffff"],"memory":["000000000000000000000000a7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0000000000000000000000000000000000000000000000000000000000000005","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3337,"op":"AND","gas":24034,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0","0x20","0x40","0x7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0xffffffffffffffffffffffffffffffffffffffff","0x111abe46ff893f3b2fdf1f759a8a8"],"memory":["000000000000000000000000a7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0000000000000000000000000000000000000000000000000000000000000005","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3338,"op":"DUP1","gas":24031,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0","0x20","0x40","0x7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0x111abe46ff893f3b2fdf1f759a8a8"],"memory":["000000000000000000000000a7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0000000000000000000000000000000000000000000000000000000000000005","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3339,"op":"DUP6","gas":24028,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0","0x20","0x40","0x7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0x111abe46ff893f3b2fdf1f759a8a8","0x111abe46ff893f3b2fdf1f759a8a8"],"memory":["000000000000000000000000a7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0000000000000000000000000000000000000000000000000000000000000005","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3340,"op":"MSTORE","gas":24025,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0","0x20","0x40","0x7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0x111abe46ff893f3b2fdf1f759a8a8","0x111abe46ff893f3b2fdf1f759a8a8","0x0"],"memory":["000000000000000000000000a7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0000000000000000000000000000000000000000000000000000000000000005","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3341,"op":"SWAP1","gas":24022,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0","0x20","0x40","0x7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0x111abe46ff893f3b2fdf1f759a8a8"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","0000000000000000000000000000000000000000000000000000000000000005","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3342,"op":"DUP4","gas":24019,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0","0x20","0x40","0x111abe46ff893f3b2fdf1f759a8a8","0x7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","0000000000000000000000000000000000000000000000000000000000000005","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3343,"op":"MSTORE","gas":24016,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0","0x20","0x40","0x111abe46ff893f3b2fdf1f759a8a8","0x7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0x20"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","0000000000000000000000000000000000000000000000000000000000000005","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3344,"op":"SWAP3","gas":24013,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x0","0x20","0x40","0x111abe46ff893f3b2fdf1f759a8a8"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3345,"op":"DUP2","gas":24010,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x111abe46ff893f3b2fdf1f759a8a8","0x20","0x40","0x0"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3346,"op":"SWAP1","gas":24007,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x111abe46ff893f3b2fdf1f759a8a8","0x20","0x40","0x0","0x40"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3347,"op":"KECCAK256","gas":24004,"gasCost":42,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x111abe46ff893f3b2fdf1f759a8a8","0x20","0x40","0x40","0x0"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3348,"op":"DUP1","gas":23962,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x111abe46ff893f3b2fdf1f759a8a8","0x20","0x40","0x6693dabf5ec7ab1a0d1c5bc58451f85d5e44d504c9ffeb75799bfdb61aa2997a"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3349,"op":"SLOAD","gas":23959,"gasCost":2100,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x111abe46ff893f3b2fdf1f759a8a8","0x20","0x40","0x6693dabf5ec7ab1a0d1c5bc58451f85d5e44d504c9ffeb75799bfdb61aa2997a","0x6693dabf5ec7ab1a0d1c5bc58451f85d5e44d504c9ffeb75799bfdb61aa2997a"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080"],"storage":{"6693dabf5ec7ab1a0d1c5bc58451f85d5e44d504c9ffeb75799bfdb61aa2997a":"0000000000000000000000000000000000000000000000000000000000000000"}},{"pc":3350,"op":"PUSH1","gas":21859,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x111abe46ff893f3b2fdf1f759a8a8","0x20","0x40","0x6693dabf5ec7ab1a0d1c5bc58451f85d5e44d504c9ffeb75799bfdb61aa2997a","0x0"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3352,"op":"NOT","gas":21856,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x111abe46ff893f3b2fdf1f759a8a8","0x20","0x40","0x6693dabf5ec7ab1a0d1c5bc58451f85d5e44d504c9ffeb75799bfdb61aa2997a","0x0","0xff"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3353,"op":"AND","gas":21853,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x111abe46ff893f3b2fdf1f759a8a8","0x20","0x40","0x6693dabf5ec7ab1a0d1c5bc58451f85d5e44d504c9ffeb75799bfdb61aa2997a","0x0","0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3354,"op":"DUP7","gas":21850,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x111abe46ff893f3b2fdf1f759a8a8","0x20","0x40","0x6693dabf5ec7ab1a0d1c5bc58451f85d5e44d504c9ffeb75799bfdb61aa2997a","0x0"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3355,"op":"ISZERO","gas":21847,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x111abe46ff893f3b2fdf1f759a8a8","0x20","0x40","0x6693dabf5ec7ab1a0d1c5bc58451f85d5e44d504c9ffeb75799bfdb61aa2997a","0x0","0x1"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3356,"op":"ISZERO","gas":21844,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x111abe46ff893f3b2fdf1f759a8a8","0x20","0x40","0x6693dabf5ec7ab1a0d1c5bc58451f85d5e44d504c9ffeb75799bfdb61aa2997a","0x0","0x0"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3357,"op":"SWAP1","gas":21841,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x111abe46ff893f3b2fdf1f759a8a8","0x20","0x40","0x6693dabf5ec7ab1a0d1c5bc58451f85d5e44d504c9ffeb75799bfdb61aa2997a","0x0","0x1"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3358,"op":"DUP2","gas":21838,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x111abe46ff893f3b2fdf1f759a8a8","0x20","0x40","0x6693dabf5ec7ab1a0d1c5bc58451f85d5e44d504c9ffeb75799bfdb61aa2997a","0x1","0x0"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3359,"op":"OR","gas":21835,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x111abe46ff893f3b2fdf1f759a8a8","0x20","0x40","0x6693dabf5ec7ab1a0d1c5bc58451f85d5e44d504c9ffeb75799bfdb61aa2997a","0x1","0x0","0x1"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3360,"op":"SWAP1","gas":21832,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x111abe46ff893f3b2fdf1f759a8a8","0x20","0x40","0x6693dabf5ec7ab1a0d1c5bc58451f85d5e44d504c9ffeb75799bfdb61aa2997a","0x1","0x1"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3361,"op":"SWAP2","gas":21829,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x111abe46ff893f3b2fdf1f759a8a8","0x20","0x40","0x6693dabf5ec7ab1a0d1c5bc58451f85d5e44d504c9ffeb75799bfdb61aa2997a","0x1","0x1"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3362,"op":"SSTORE","gas":21826,"gasCost":20000,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x111abe46ff893f3b2fdf1f759a8a8","0x20","0x40","0x1","0x1","0x6693dabf5ec7ab1a0d1c5bc58451f85d5e44d504c9ffeb75799bfdb61aa2997a"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080"],"storage":{"6693dabf5ec7ab1a0d1c5bc58451f85d5e44d504c9ffeb75799bfdb61aa2997a":"0000000000000000000000000000000000000000000000000000000000000001"}},{"pc":3363,"op":"SWAP1","gas":1826,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x111abe46ff893f3b2fdf1f759a8a8","0x20","0x40","0x1"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3364,"op":"MLOAD","gas":1823,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x111abe46ff893f3b2fdf1f759a8a8","0x20","0x1","0x40"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3365,"op":"SWAP1","gas":1820,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x111abe46ff893f3b2fdf1f759a8a8","0x20","0x1","0x80"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3366,"op":"DUP2","gas":1817,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x111abe46ff893f3b2fdf1f759a8a8","0x20","0x80","0x1"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080"]},{"pc":3367,"op":"MSTORE","gas":1814,"gasCost":9,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x111abe46ff893f3b2fdf1f759a8a8","0x20","0x80","0x1","0x80"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000"]},{"pc":3368,"op":"SWAP2","gas":1805,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x111abe46ff893f3b2fdf1f759a8a8","0x20","0x80"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000001"]},{"pc":3369,"op":"SWAP3","gas":1802,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x80","0x20","0x111abe46ff893f3b2fdf1f759a8a8"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000001"]},{"pc":3370,"op":"SWAP2","gas":1799,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x111abe46ff893f3b2fdf1f759a8a8","0x80","0x20","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000001"]},{"pc":3371,"op":"PUSH32","gas":1796,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x111abe46ff893f3b2fdf1f759a8a8","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x20","0x80"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000001"]},{"pc":3404,"op":"SWAP2","gas":1793,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x111abe46ff893f3b2fdf1f759a8a8","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x20","0x80","0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000001"]},{"pc":3405,"op":"ADD","gas":1790,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x111abe46ff893f3b2fdf1f759a8a8","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31","0x80","0x20"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000001"]},{"pc":3406,"op":"PUSH1","gas":1787,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x111abe46ff893f3b2fdf1f759a8a8","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31","0xa0"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000001"]},{"pc":3408,"op":"MLOAD","gas":1784,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x111abe46ff893f3b2fdf1f759a8a8","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31","0xa0","0x40"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000001"]},{"pc":3409,"op":"DUP1","gas":1781,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x111abe46ff893f3b2fdf1f759a8a8","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31","0xa0","0x80"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000001"]},{"pc":3410,"op":"SWAP2","gas":1778,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x111abe46ff893f3b2fdf1f759a8a8","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31","0xa0","0x80","0x80"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000001"]},{"pc":3411,"op":"SUB","gas":1775,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x111abe46ff893f3b2fdf1f759a8a8","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31","0x80","0x80","0xa0"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000001"]},{"pc":3412,"op":"SWAP1","gas":1772,"gasCost":3,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x111abe46ff893f3b2fdf1f759a8a8","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31","0x80","0x20"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000001"]},{"pc":3413,"op":"LOG3","gas":1769,"gasCost":1756,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1","0x111abe46ff893f3b2fdf1f759a8a8","0xa7194f8a5f509ed2c95ade0b4efb6940a45d7a11","0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31","0x20","0x80"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000001"]},{"pc":3414,"op":"POP","gas":13,"gasCost":2,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8","0x1"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000001"]},{"pc":3415,"op":"POP","gas":11,"gasCost":2,"depth":1,"stack":["0xa22cb465","0x27b","0x111abe46ff893f3b2fdf1f759a8a8"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000001"]},{"pc":3416,"op":"JUMP","gas":9,"gasCost":8,"depth":1,"stack":["0xa22cb465","0x27b"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000001"]},{"pc":635,"op":"JUMPDEST","gas":1,"gasCost":1,"depth":1,"stack":["0xa22cb465"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000001"]},{"pc":636,"op":"STOP","gas":0,"gasCost":0,"depth":1,"stack":["0xa22cb465"],"memory":["00000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a8","7d3429278e27616819652c726b56f6b8ffeea2d2c23cf663064312a58b0422d2","0000000000000000000000000000000000000000000000000000000000000080","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000001"]}],"gas":46107,"failed":false,"returnValue":""} \ No newline at end of file diff --git a/crates/rpc-types/test_data/pre_state_tracer/default.json b/crates/rpc-types/test_data/pre_state_tracer/default.json new file mode 100644 index 00000000000..43e69b11bdb --- /dev/null +++ b/crates/rpc-types/test_data/pre_state_tracer/default.json @@ -0,0 +1,20 @@ +{ + "0x082d4cdf07f386ffa9258f52a5c49db4ac321ec6": { + "balance": "0xc820f93200f4000", + "nonce": 94 + }, + "0x332b656504f4eabb44c8617a42af37461a34e9dc": { + "balance": "0x11faea4f35e5af80000", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "0x52bc44d5378309ee2abf1539bf71de1b7d7be3b5": { + "balance": "0xbf681825be002ac452", + "nonce": 28922 + }, + "0x82effbaaaf28614e55b2ba440fb198e0e5789b0f": { + "balance": "0xb3d0ac5cb94df6f6b0", + "nonce": 1 + } +} diff --git a/crates/rpc-types/test_data/pre_state_tracer/diff_mode.json b/crates/rpc-types/test_data/pre_state_tracer/diff_mode.json new file mode 100644 index 00000000000..0654d26f546 --- /dev/null +++ b/crates/rpc-types/test_data/pre_state_tracer/diff_mode.json @@ -0,0 +1,41 @@ +{ + "pre": { + "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": { + "balance": "0x0", + "nonce": 22 + }, + "0x1585936b53834b021f68cc13eeefdec2efc8e724": { + "balance": "0x0" + }, + "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe": { + "balance": "0x4d87094125a369d9bd5", + "nonce": 1, + "code": "0x606060405236156100935763ffffffff60e060020a60003504166311ee8382811461009c57806313af4035146100be5780631f5e8f4c146100ee57806324daddc5146101125780634921a91a1461013b57806363e4bff414610157578063764978f91461017f578063893d20e8146101a1578063ba40aaa1146101cd578063cebc9a82146101f4578063e177246e14610216575b61009a5b5b565b005b34156100a457fe5b6100ac61023d565b60408051918252519081900360200190f35b34156100c657fe5b6100da600160a060020a0360043516610244565b604080519115158252519081900360200190f35b34156100f657fe5b6100da610307565b604080519115158252519081900360200190f35b341561011a57fe5b6100da6004351515610318565b604080519115158252519081900360200190f35b6100da6103d6565b604080519115158252519081900360200190f35b6100da600160a060020a0360043516610420565b604080519115158252519081900360200190f35b341561018757fe5b6100ac61046c565b60408051918252519081900360200190f35b34156101a957fe5b6101b1610473565b60408051600160a060020a039092168252519081900360200190f35b34156101d557fe5b6100da600435610483565b604080519115158252519081900360200190f35b34156101fc57fe5b6100ac61050d565b60408051918252519081900360200190f35b341561021e57fe5b6100da600435610514565b604080519115158252519081900360200190f35b6003545b90565b60006000610250610473565b600160a060020a031633600160a060020a03161415156102705760006000fd5b600160a060020a03831615156102865760006000fd5b50600054600160a060020a0390811690831681146102fb57604051600160a060020a0380851691908316907ffcf23a92150d56e85e3a3d33b357493246e55783095eb6a733eb8439ffc752c890600090a360008054600160a060020a031916600160a060020a03851617905560019150610300565b600091505b5b50919050565b60005460a060020a900460ff165b90565b60006000610324610473565b600160a060020a031633600160a060020a03161415156103445760006000fd5b5060005460a060020a900460ff16801515831515146102fb576000546040805160a060020a90920460ff1615158252841515602083015280517fe6cd46a119083b86efc6884b970bfa30c1708f53ba57b86716f15b2f4551a9539281900390910190a16000805460a060020a60ff02191660a060020a8515150217905560019150610300565b600091505b5b50919050565b60006103e0610307565b801561040557506103ef610473565b600160a060020a031633600160a060020a031614155b156104105760006000fd5b610419336105a0565b90505b5b90565b600061042a610307565b801561044f5750610439610473565b600160a060020a031633600160a060020a031614155b1561045a5760006000fd5b610463826105a0565b90505b5b919050565b6001545b90565b600054600160a060020a03165b90565b6000600061048f610473565b600160a060020a031633600160a060020a03161415156104af5760006000fd5b506001548281146102fb57604080518281526020810185905281517f79a3746dde45672c9e8ab3644b8bb9c399a103da2dc94b56ba09777330a83509929181900390910190a160018381559150610300565b600091505b5b50919050565b6002545b90565b60006000610520610473565b600160a060020a031633600160a060020a03161415156105405760006000fd5b506002548281146102fb57604080518281526020810185905281517ff6991a728965fedd6e927fdf16bdad42d8995970b4b31b8a2bf88767516e2494929181900390910190a1600283905560019150610300565b600091505b5b50919050565b60006000426105ad61023d565b116102fb576105c46105bd61050d565b4201610652565b6105cc61046c565b604051909150600160a060020a038416908290600081818185876187965a03f1925050501561063d57604080518281529051600160a060020a038516917f9bca65ce52fdef8a470977b51f247a2295123a4807dfa9e502edf0d30722da3b919081900360200190a260019150610300565b6102fb42610652565b5b600091505b50919050565b60038190555b505600a165627a7a72305820f3c973c8b7ed1f62000b6701bd5b708469e19d0f1d73fde378a56c07fd0b19090029", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000000000000000000000000000000000005a37b834" + } + }, + "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb": { + "balance": "0x1780d77678137ac1b775", + "nonce": 29072 + } + }, + "post": { + "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": { + "balance": "0x6f05b59d3b20000" + }, + "0x1585936b53834b021f68cc13eeefdec2efc8e724": { + "balance": "0x420eed1bd6c00" + }, + "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe": { + "balance": "0x4d869a3b70062eb9bd5", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000000000000000000000000000000000005a37b95e" + } + }, + "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb": { + "balance": "0x1780d7725724a9044b75", + "nonce": 29073 + } + } +} diff --git a/crates/rpc-types/test_data/pre_state_tracer/legacy.json b/crates/rpc-types/test_data/pre_state_tracer/legacy.json new file mode 100644 index 00000000000..dbefb198c40 --- /dev/null +++ b/crates/rpc-types/test_data/pre_state_tracer/legacy.json @@ -0,0 +1,25 @@ +{ + "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": { + "balance": "0x0", + "code": "0x", + "nonce": 22, + "storage": {} + }, + "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe": { + "balance": "0x4d87094125a369d9bd5", + "code": "0x606060405236156100935763ffffffff60e060020a60003504166311ee8382811461009c57806313af4035146100be5780631f5e8f4c146100ee57806324daddc5146101125780634921a91a1461013b57806363e4bff414610157578063764978f91461017f578063893d20e8146101a1578063ba40aaa1146101cd578063cebc9a82146101f4578063e177246e14610216575b61009a5b5b565b005b34156100a457fe5b6100ac61023d565b60408051918252519081900360200190f35b34156100c657fe5b6100da600160a060020a0360043516610244565b604080519115158252519081900360200190f35b34156100f657fe5b6100da610307565b604080519115158252519081900360200190f35b341561011a57fe5b6100da6004351515610318565b604080519115158252519081900360200190f35b6100da6103d6565b604080519115158252519081900360200190f35b6100da600160a060020a0360043516610420565b604080519115158252519081900360200190f35b341561018757fe5b6100ac61046c565b60408051918252519081900360200190f35b34156101a957fe5b6101b1610473565b60408051600160a060020a039092168252519081900360200190f35b34156101d557fe5b6100da600435610483565b604080519115158252519081900360200190f35b34156101fc57fe5b6100ac61050d565b60408051918252519081900360200190f35b341561021e57fe5b6100da600435610514565b604080519115158252519081900360200190f35b6003545b90565b60006000610250610473565b600160a060020a031633600160a060020a03161415156102705760006000fd5b600160a060020a03831615156102865760006000fd5b50600054600160a060020a0390811690831681146102fb57604051600160a060020a0380851691908316907ffcf23a92150d56e85e3a3d33b357493246e55783095eb6a733eb8439ffc752c890600090a360008054600160a060020a031916600160a060020a03851617905560019150610300565b600091505b5b50919050565b60005460a060020a900460ff165b90565b60006000610324610473565b600160a060020a031633600160a060020a03161415156103445760006000fd5b5060005460a060020a900460ff16801515831515146102fb576000546040805160a060020a90920460ff1615158252841515602083015280517fe6cd46a119083b86efc6884b970bfa30c1708f53ba57b86716f15b2f4551a9539281900390910190a16000805460a060020a60ff02191660a060020a8515150217905560019150610300565b600091505b5b50919050565b60006103e0610307565b801561040557506103ef610473565b600160a060020a031633600160a060020a031614155b156104105760006000fd5b610419336105a0565b90505b5b90565b600061042a610307565b801561044f5750610439610473565b600160a060020a031633600160a060020a031614155b1561045a5760006000fd5b610463826105a0565b90505b5b919050565b6001545b90565b600054600160a060020a03165b90565b6000600061048f610473565b600160a060020a031633600160a060020a03161415156104af5760006000fd5b506001548281146102fb57604080518281526020810185905281517f79a3746dde45672c9e8ab3644b8bb9c399a103da2dc94b56ba09777330a83509929181900390910190a160018381559150610300565b600091505b5b50919050565b6002545b90565b60006000610520610473565b600160a060020a031633600160a060020a03161415156105405760006000fd5b506002548281146102fb57604080518281526020810185905281517ff6991a728965fedd6e927fdf16bdad42d8995970b4b31b8a2bf88767516e2494929181900390910190a1600283905560019150610300565b600091505b5b50919050565b60006000426105ad61023d565b116102fb576105c46105bd61050d565b4201610652565b6105cc61046c565b604051909150600160a060020a038416908290600081818185876187965a03f1925050501561063d57604080518281529051600160a060020a038516917f9bca65ce52fdef8a470977b51f247a2295123a4807dfa9e502edf0d30722da3b919081900360200190a260019150610300565b6102fb42610652565b5b600091505b50919050565b60038190555b505600a165627a7a72305820f3c973c8b7ed1f62000b6701bd5b708469e19d0f1d73fde378a56c07fd0b19090029", + "nonce": 1, + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000001b436ba50d378d4bbc8660d312a13df6af6e89dfb", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000000000000000000000000000006f05b59d3b20000", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x000000000000000000000000000000000000000000000000000000000000003c", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000000000000000000000000000000000005a37b834" + } + }, + "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb": { + "balance": "0x1780d77678137ac1b775", + "code": "0x", + "nonce": 29072, + "storage": {} + } +} diff --git a/crates/transports/src/transports/json.rs b/crates/transports/src/transports/json.rs index 6601034b62f..7168344b479 100644 --- a/crates/transports/src/transports/json.rs +++ b/crates/transports/src/transports/json.rs @@ -49,7 +49,6 @@ where fn call(&mut self, req: RequestPacket) -> Self::Future { let replacement = self.inner.clone(); let mut client = std::mem::replace(&mut self.inner, replacement); - match to_json_raw_value(&req) { Ok(raw) => JsonRpcFuture { state: States::Pending { diff --git a/deny.toml b/deny.toml index b1e21c2a96f..2d26a8d1111 100644 --- a/deny.toml +++ b/deny.toml @@ -37,6 +37,8 @@ exceptions = [ # so we prefer to not have dependencies using it # https://tldrlegal.com/license/creative-commons-cc0-1.0-universal { allow = ["CC0-1.0"], name = "tiny-keccak" }, + { allow = ["CC0-1.0"], name = "secp256k1-sys" }, + { allow = ["CC0-1.0"], name = "secp256k1" }, ] [[licenses.clarify]]