From 313e80fb0bf07852fe134c0284ebdde3b9ce9e31 Mon Sep 17 00:00:00 2001 From: Enrique Ortiz Date: Fri, 17 Nov 2023 10:46:28 -0400 Subject: [PATCH] feat: new RPC types, and ergonomics --- crates/rpc-types/src/eth/account.rs | 87 +++ crates/rpc-types/src/eth/block.rs | 13 + crates/rpc-types/src/eth/call.rs | 4 +- crates/rpc-types/src/eth/filter.rs | 12 +- crates/rpc-types/src/eth/mod.rs | 5 + crates/rpc-types/src/eth/pubsub.rs | 2 +- crates/rpc-types/src/eth/state.rs | 2 +- crates/rpc-types/src/eth/trace/common.rs | 14 + crates/rpc-types/src/eth/trace/filter.rs | 183 ++++++ crates/rpc-types/src/eth/trace/geth/call.rs | 105 ++++ .../rpc-types/src/eth/trace/geth/four_byte.rs | 34 ++ crates/rpc-types/src/eth/trace/geth/mod.rs | 556 ++++++++++++++++++ crates/rpc-types/src/eth/trace/geth/noop.rs | 32 + .../rpc-types/src/eth/trace/geth/pre_state.rs | 348 +++++++++++ crates/rpc-types/src/eth/trace/mod.rs | 12 + crates/rpc-types/src/eth/trace/parity.rs | 472 +++++++++++++++ .../rpc-types/src/eth/trace/tracerequest.rs | 84 +++ .../src/eth/transaction/signature.rs | 6 + crates/rpc-types/src/eth/transaction/typed.rs | 7 +- crates/rpc-types/src/eth/txpool.rs | 520 ++++++++++++++++ 20 files changed, 2489 insertions(+), 9 deletions(-) create mode 100644 crates/rpc-types/src/eth/account.rs create mode 100644 crates/rpc-types/src/eth/trace/common.rs create mode 100644 crates/rpc-types/src/eth/trace/filter.rs create mode 100644 crates/rpc-types/src/eth/trace/geth/call.rs create mode 100644 crates/rpc-types/src/eth/trace/geth/four_byte.rs create mode 100644 crates/rpc-types/src/eth/trace/geth/mod.rs create mode 100644 crates/rpc-types/src/eth/trace/geth/noop.rs create mode 100644 crates/rpc-types/src/eth/trace/geth/pre_state.rs create mode 100644 crates/rpc-types/src/eth/trace/mod.rs create mode 100644 crates/rpc-types/src/eth/trace/parity.rs create mode 100644 crates/rpc-types/src/eth/trace/tracerequest.rs create mode 100644 crates/rpc-types/src/eth/txpool.rs diff --git a/crates/rpc-types/src/eth/account.rs b/crates/rpc-types/src/eth/account.rs new file mode 100644 index 00000000000..7188dc98ab2 --- /dev/null +++ b/crates/rpc-types/src/eth/account.rs @@ -0,0 +1,87 @@ +#![allow(missing_docs)] +use crate::serde_helpers::storage::JsonStorageKey; +use alloy_primitives::{Address, Bytes, B256, B512, U256, U64}; +use serde::{Deserialize, Serialize}; + +/// Account information. +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct AccountInfo { + /// Account name + pub name: String, +} + +/// Data structure with proof for one single storage-entry +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct EIP1186StorageProof { + /// Storage key. + pub key: JsonStorageKey, + /// Value that the key holds + pub value: U256, + /// proof for the pair + pub proof: Vec, +} + +/// Response for EIP-1186 account proof `eth_getProof` +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct EIP1186AccountProofResponse { + pub address: Address, + pub balance: U256, + pub code_hash: B256, + pub nonce: U64, + pub storage_hash: B256, + pub account_proof: Vec, + pub storage_proof: Vec, +} + +/// Extended account information (used by `parity_allAccountInfo`). +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct ExtAccountInfo { + /// Account name + pub name: String, + /// Account meta JSON + pub meta: String, + /// Account UUID (`None` for address book entries) + #[serde(skip_serializing_if = "Option::is_none")] + pub uuid: Option, +} + +/// account derived from a signature +/// as well as information that tells if it is valid for +/// the current chain +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RecoveredAccount { + /// address of the recovered account + pub address: Address, + /// public key of the recovered account + pub public_key: B512, + /// If the signature contains chain replay protection, + /// And the chain_id encoded within the signature + /// matches the current chain this would be true, otherwise false. + pub is_valid_for_current_chain: bool, +} + +#[test] +fn test_eip_1186_account_without_storage_proof() { + let response = r#"{ + "address":"0xc36442b4a4522e871399cd717abdd847ab11fe88", + "accountProof":["0xf90211a0a3deb2d4417de23e3c64a80ab58fa1cf4b62d7f193e36e507c8cf3794477b5fba0fc7ce8769dcfa9ae8d9d9537098c5cc5477b5920ed494e856049f5783c843c50a0f7d083f1e79a4c0ba1686b97a0e27c79c3a49432d333dc3574d5879cad1ca897a0cd36cf391201df64a786187d99013bdbaf5f0da6bfb8f5f2d6f0f60504f76ad9a03a9f09c92c3cefe87840938dc15fe68a3586d3b28b0f47c7037b6413c95a9feda0decb7e1969758d401af2d1cab14c0951814c094a3da108dd9f606a96840bae2ba060bf0c44ccc3ccbb5ab674841858cc5ea16495529442061295f1cecefd436659a039f8b307e0a295d6d03df089ee8211b52c5ae510d071f17ae5734a7055858002a0508040aef23dfe9c8ab16813258d95c4e765b4a557c2987fb7f3751693f34f4fa0c07e58aa6cd257695cdf147acd800c6197c235e2b5242c22e9da5d86b169d56aa00f2e89ddd874d28e62326ba365fd4f26a86cbd9f867ec0b3de69441ef8870f4ea06c1eb5455e43a36ec41a0372bde915f889cee070b8c8b8a78173d4d7df3ccebaa0cee4848c4119ed28e165e963c5b46ffa6dbeb0b14c8c51726124e7d26ff3f27aa0fc5b82dce2ee5a1691aa92b91dbeec7b2ba94df8116ea985dd7d3f4d5b8292c0a03675e148c987494e22a9767b931611fb1b7c7c287af128ea23aa70b88a1c458ba04f269f556f0f8d9cb2a9a6de52d35cf5a9098f7bb8badb1dc1d496096236aed880", + "0xf90211a0715ed9b0b002d050084eaecb878f457a348ccd47c7a597134766a7d705303de9a0c49f0fe23b0ca61892d75aebaf7277f00fdfd2022e746bab94de5d049a96edfca0b01f9c91f2bc1373862d7936198a5d11efaf370e2b9bb1dac2134b8e256ecdafa0888395aa7e0f699bb632215f08cdf92840b01e5d8e9a61d18355098cdfd50283a0ba748d609b0018667d311527a2302267209a38b08378f7d833fdead048de0defa098878e5d1461ceddeddf62bd8277586b120b5097202aa243607bc3fc8f30fc0ba0ad4111ee1952b6db0939a384986ee3fb34e0a5fc522955588fc22e159949196fa00fc948964dff427566bad468d62b0498c59df7ca7ae799ab29555d5d829d3742a0766922a88ebc6db7dfb06b03a5b17d0773094e46e42e7f2ba6a0b8567d9f1000a0db25676c4a36591f37c5e16f7199ab16559d82a2bed8c0c6a35f528a3c166bfda0149a5d50d238722e7d44c555169ed32a7f182fcb487ea378b4410a46a63a4e66a06b2298bbfe4972113e7e18cac0a8a39792c1a940ea128218343b8f88057d90aea096b2adb84105ae2aca8a7edf937e91e40872070a8641a74891e64db94d059df0a0ddbb162125ecfbd42edad8d8ef5d5e97ca7c72f54ddc404a61ae318bad0d2108a00e9a68f3e2b0c793d5fcd607edc5c55226d53fdfacd713077d6e01cb38d00d5ba05dc099f1685b2a4b7308e063e8e7905994f5c36969b1c6bfe3780c9878a4d85c80", + "0xf90211a05fc921be4d63ee07fe47a509e1abf2d69b00b6ea582a755467bf4371c2d2bd1fa0d552faa477e95f4631e2f7247aeb58693d90b03b2eee57e3fe8a9ddbd19ee42da028682c15041aa6ced1a5306aff311f5dbb8bbf7e77615994305ab3132e7842b5a0e5e0316b5046bde22d09676210885c5bea6a71703bf3b4dbac2a7199910f54faa0527fccccef17df926ccfb608f76d3c259848ed43cd24857a59c2a9352b6f1fa4a02b3863355b927b78c80ca379a4f7165bbe1644aaefed8a0bfa2001ae6284b392a09964c73eccc3d12e44dba112e31d8bd3eacbc6a42b4f17985d5b99dff968f24ea0cc426479c7ff0573629dcb2872e57f7438a28bd112a5c3fb2241bdda8031432ba04987fe755f260c2f7218640078af5f6ac4d98c2d0c001e398debc30221b14668a0e811d046c21c6cbaee464bf55553cbf88e70c2bda6951800c75c3896fdeb8e13a04aa8d0ab4946ac86e784e29000a0842cd6eebddaf8a82ece8aa69b72c98cfff5a0dfc010051ddceeec55e4146027c0eb4c72d7c242a103bf1977033ebe00a57b5da039e4da79576281284bf46ce6ca90d47832e4aefea4846615d7a61a7b976c8e3ea0dad1dfff731f7dcf37c499f4afbd5618247289c2e8c14525534b826a13b0a5a6a025f356cbc0469cb4dc326d98479e3b756e4418a67cbbb8ffb2d1abab6b1910e9a03f4082bf1da27b2a76f6bdc930eaaaf1e3f0e4d3135c2a9fb85e301f47f5174d80", + "0xf90211a0df6448f21c4e19da33f9c64c90bbcc02a499866d344c73576f63e3b4cbd4c000a010efb3b0f1d6365e2e4a389965e114e2a508ef8901f7d6c7564ba88793ff974aa0295bef2313a4f603614a5d5af3c659f63edfaa5b59a6ea2ac1da05f69ff4657ba0d8f16d5ddf4ba09616008148d2993dc50658accc2edf9111b6f464112db5d369a084604d9e06ddb53aeb7b13bb70fbe91f60df6bdc30f59bc7dc57ff37b6fe3325a04c64bd1dbeaecc54f18b23ab1ade2200970757f437e75e285f79a8c405315a14a0868075fc7f73b13863fc653c806f9a20f8e52dce44c15d2c4f94d6711021b985a01e85c49da7a8c91068468779e79b267d93d4fad01f44183353a381207304723ea05fcf186d55c53413f6988b16aa34721f0539f1cf0917f02e9d1a6ec8d3e191ffa00ad581842eab665351913e0afb3bfc070b9e4fad4d354c073f44c4f2a0c425c9a0000cb2066d81bf07f80703a40a5c5012e2c4b387bc53d381d37ee1d0f0a6643ba061f221d01c98721e79c525af5fc2eb9cc648c2ca54bb70520b868e2bdc037967a0e580f297c477df46362eb8e20371d8f0528091454bb5ad00d40368ca3ffdbd1fa079a13d35f79699f9e51d4fa07d03cd9b9dec4de9906559c0470629a663181652a0dbb402183633dbaa73e6e6a6b66bfffc4570763b264d3a702de165032298b858a065d5321015531309bb3abe0235f825d5be4270d2e511dca3b984d1e70ef308d880", + "0xf90211a06d0adafe89896724704275a42a8a63f0910dce83188add0073f621b8ca1167aaa00de7d4efad36d08f5a0320cdfd964484eba803d9933efae12c292d3ff2d06a20a083341fc12fffccf4b11df314b14f7bcead154525a097493fdf15dde4ec0c0d2aa088b7759fe3aef617828e7abd9e554add2e84ef3e2e024b1a0e2f537fce7d37f9a01e73c28722d825063304c6b51be3a8c7b6312ba8be4c6e99602e623993c014c0a0e50fbe12ddbaf184f3ba0cda971675a55abbf44c73f771bc5824b393262e5255a0b1a937d4c50528cb6aeb80aa5fe83bcfa8c294124a086302caf42cead1f99f96a04c4376b13859af218b5b09ffb33e3465288837c37fa254a46f8d0e75afecae10a0f158c0171bdb454eab6bb6dc5e276e749b6aa550f53b497492c0a392425035c3a0ac496050db1fbb1d34180ee7fd7bed18efa4cf43299390a72dcf530cc3422630a02cacb30ac3b4bab293d31833be4865cd1d1de8db8630edac4af056979cc903aea090cbb538f0f4601289db4cf49485ab3a178044daeae325c525bc3978714a7219a0542021427adbe890896fcc888418a747a555b2a7121fe3c683e07dcf5012e96ca006569c5e3715f52f62dd856dec2136e60c49bbadc1cf9fb625930da3e8f1c16ea0a2539ebb66a2c10c3809626181a2389f043e0b54867cd356eb5f20daaeb521b4a0ab49972dced10010275f2604e6182722dbc426ca1b0ae128defe80c0baefd3c080", + "0xf90211a006c1d8a7c5deeb435ea0b080aea8b7acb58d2d898e12e3560d399594a77863a1a088105243bc96e1f10baa73d670929a834c51eb7f695cf43f4fab94e73c9a5b8da0fce3a21f09b62d65607bbdabb8d675d58a5f3bfb19ae46510a4ea2205070aa03a0039ae7a999ed83bfdb49b6df7074589059ba6c2eed22bfc6dac8ff5241c71bd7a09feca6f7331b6c147f4fd7bd94de496144b85543d868f47be6345330b3f8ccd3a00e55c30d16438567979c92d387a2b99e51a4026192ccfda2ac87a190c3aee511a0a86c5bb52651e490203c63670b569b2337e838e4d80d455cc83e64571e2552f1a0cfb31ae59b691c15ffd97658bab646ff4b90dbc72a81ec52731b3fbd38d0dd5ba0d83936fc4143cc885be5fa420ef22fb97f6a8dd24e9ece9af965792565a7b2c8a0abb179481f4b29578adb8768aa4f6ba6ed6bd43c7572d7c3405c879a362f1ab1a0506651daa07d44901dfd76c12d302b2242e5ceac385f95ea928f20a0336eccf6a010e8a7f461231438987fb26adc4c5004721dc401dc2b77e9b79d26b1308d0079a09174afa82e6d27dfdde74f556d0e782ae6222dc66104d84ea0f1e21e093578c4a0391e24ed0033cc58f149af753b485de3c8b9e4b3c8e145c308db60e51cabbefca03b0991359019197dd53e3798e55a14c8795d655b0693efd37404cf8f8d979cfba0594d95bbfe8e2ea5040b571010549a233bc33bf959792e1e41c515c65abac14480", + "0xf90151a0e8ed81735d358657020dd6bc4bc58cf751cc037fa57e1d0c668bf24049e720d280a03e8bf7abdd8a4190a0ee5f92a78bf1dba529312ed66dd7ead7c9be55c81a2db480a006312425a007cda585740355f52db74d0ae43c21d562c599112546e3ffe22f01a023bbbb0ffb33c7a5477ab514c0f4f3c94ba1748a5ea1dc3edc7c4b5330cd70fe80a03ed45ab6045a10fa00b2fba662914f4dedbf3f3a5f2ce1e6e53a12ee3ea21235a01e02c98684cea92a7c0b04a01658530a09d268b395840a66263923e44b93d2b5a0a585db4a911fe6452a4540bf7dc143981ca31035ccb2c51d02eccd021a6163a480a06032919dcb44e22852b6367473bbc3f43311226ac28991a90b9c9da669f9e08a80a0146aee58a46c30bc84f6e99cd76bf29b3bd238053102679498a3ea15d4ff6d53a04cf57cfdc046c135004b9579059c84b2d902a51fb6feaed51ea272f0ca1cdc648080", + "0xf871a059ce2e1f470580853d88511bf8672f9ffaefadd80bc07b2e3d5a18c3d7812007a0867e978faf3461d2238ccf8d6a138406cb6d8bd36dfa60caddb62af14447a6f880808080a0fc6209fdaa57d224ee35f73e96469a7f95760a54d5de3da07953430b001aee6980808080808080808080", + "0xf8669d20852b2b985cd8c252fddae2acb4f798d0fecdcb1e2da53726332eb559b846f8440180a079fe22fe88fc4b45db10ce94d975e02e8a42b57dc190f8ae15e321f72bbc08eaa0692e658b31cbe3407682854806658d315d61a58c7e4933a2f91d383dc00736c6"], + "balance":"0x0", + "codeHash":"0x692e658b31cbe3407682854806658d315d61a58c7e4933a2f91d383dc00736c6", + "nonce":"0x1", + "storageHash":"0x79fe22fe88fc4b45db10ce94d975e02e8a42b57dc190f8ae15e321f72bbc08ea", + "storageProof":[] + }"#; + let val = serde_json::from_str::(response).unwrap(); + serde_json::to_value(val).unwrap(); +} diff --git a/crates/rpc-types/src/eth/block.rs b/crates/rpc-types/src/eth/block.rs index aedde7d0086..8135a693e63 100644 --- a/crates/rpc-types/src/eth/block.rs +++ b/crates/rpc-types/src/eth/block.rs @@ -131,6 +131,13 @@ pub struct Block { pub withdrawals: Option>, } +impl Block { + /// Converts a block with Tx hashes into a full block. + pub fn into_full_block(self, txs: Vec) -> Self { + Self { transactions: BlockTransactions::Full(txs), ..self } + } +} + /// Block header representation. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] #[serde(rename_all = "camelCase")] @@ -415,6 +422,12 @@ impl From for BlockId { } } +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) diff --git a/crates/rpc-types/src/eth/call.rs b/crates/rpc-types/src/eth/call.rs index 2c41dcf2825..73f48d62699 100644 --- a/crates/rpc-types/src/eth/call.rs +++ b/crates/rpc-types/src/eth/call.rs @@ -88,7 +88,7 @@ impl<'de> Deserialize<'de> for TransactionIndex { } /// Call request for `eth_call` and adjacent methods. -#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Hash)] #[serde(default, rename_all = "camelCase")] pub struct CallRequest { /// From @@ -147,7 +147,7 @@ impl CallRequest { /// /// 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)] +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Hash)] pub struct CallInput { /// Transaction data pub input: Option, diff --git a/crates/rpc-types/src/eth/filter.rs b/crates/rpc-types/src/eth/filter.rs index 1c625acc95d..6c714e05d0e 100644 --- a/crates/rpc-types/src/eth/filter.rs +++ b/crates/rpc-types/src/eth/filter.rs @@ -41,6 +41,14 @@ impl From for FilterSet { } } +impl Hash for FilterSet { + fn hash(&self, state: &mut H) { + for value in &self.0 { + value.hash(state); + } + } +} + impl From> for FilterSet { fn from(src: Vec) -> Self { FilterSet(HashSet::from_iter(src.into_iter().map(Into::into))) @@ -244,8 +252,8 @@ impl FilterBlockOption { } } -/// Filter for -#[derive(Default, Debug, PartialEq, Eq, Clone)] +/// Filter for logs. +#[derive(Default, Debug, PartialEq, Eq, Clone, Hash)] pub struct Filter { /// Filter block options, specifying on which blocks the filter should /// match. diff --git a/crates/rpc-types/src/eth/mod.rs b/crates/rpc-types/src/eth/mod.rs index affebdff93a..569dde4ca41 100644 --- a/crates/rpc-types/src/eth/mod.rs +++ b/crates/rpc-types/src/eth/mod.rs @@ -1,5 +1,6 @@ //! Ethereum related types +pub mod account; mod block; mod call; mod fee; @@ -9,9 +10,12 @@ pub mod pubsub; pub mod raw_log; pub mod state; mod syncing; +pub mod trace; mod transaction; +pub mod txpool; pub mod withdrawal; +pub use account::*; pub use block::*; pub use call::{Bundle, CallInput, CallInputError, CallRequest, EthCallResponse, StateContext}; pub use fee::{FeeHistory, TxGasAndReward}; @@ -20,4 +24,5 @@ pub use log::Log; pub use raw_log::{logs_bloom, Log as RawLog}; pub use syncing::*; pub use transaction::*; +pub use txpool::*; pub use withdrawal::Withdrawal; diff --git a/crates/rpc-types/src/eth/pubsub.rs b/crates/rpc-types/src/eth/pubsub.rs index faed5e16bfa..2fef14834ce 100644 --- a/crates/rpc-types/src/eth/pubsub.rs +++ b/crates/rpc-types/src/eth/pubsub.rs @@ -1,4 +1,4 @@ -//! Ethereum types for pub-sub. +//! Ethereum types for pub-sub use crate::{ eth::{Filter, Transaction}, diff --git a/crates/rpc-types/src/eth/state.rs b/crates/rpc-types/src/eth/state.rs index f88d68479fb..dc93393229b 100644 --- a/crates/rpc-types/src/eth/state.rs +++ b/crates/rpc-types/src/eth/state.rs @@ -1,4 +1,4 @@ -//! Bindings for `eth_call` state overrides. +//! bindings for state overrides in eth_call use alloy_primitives::{Address, Bytes, B256, U256, U64}; use serde::{Deserialize, Serialize}; diff --git a/crates/rpc-types/src/eth/trace/common.rs b/crates/rpc-types/src/eth/trace/common.rs new file mode 100644 index 00000000000..78ee088ce28 --- /dev/null +++ b/crates/rpc-types/src/eth/trace/common.rs @@ -0,0 +1,14 @@ +//! Types used by tracing backends + +use serde::{Deserialize, Serialize}; + +/// The result of a single transaction trace. +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +#[allow(missing_docs)] +pub enum TraceResult { + /// Untagged success variant + Success { result: Ok }, + /// Untagged error variant + Error { error: Err }, +} diff --git a/crates/rpc-types/src/eth/trace/filter.rs b/crates/rpc-types/src/eth/trace/filter.rs new file mode 100644 index 00000000000..f61dc5b5fa3 --- /dev/null +++ b/crates/rpc-types/src/eth/trace/filter.rs @@ -0,0 +1,183 @@ +//! `trace_filter` types and support +use crate::serde_helpers::num::u64_hex_or_decimal_opt; +use alloy_primitives::Address; +use serde::{Deserialize, Serialize}; +use std::collections::HashSet; + +/// Trace filter. +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Default)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "camelCase")] +pub struct TraceFilter { + /// From block + #[serde(with = "u64_hex_or_decimal_opt")] + pub from_block: Option, + /// To block + #[serde(with = "u64_hex_or_decimal_opt")] + pub to_block: Option, + /// From address + #[serde(default)] + pub from_address: Vec
, + /// To address + #[serde(default)] + pub to_address: Vec
, + /// How to apply `from_address` and `to_address` filters. + #[serde(default)] + pub mode: TraceFilterMode, + /// Output offset + pub after: Option, + /// Output amount + pub count: Option, +} + +// === impl TraceFilter === + +impl TraceFilter { + /// Returns a `TraceFilterMatcher` for this filter. + pub fn matcher(&self) -> TraceFilterMatcher { + let from_addresses = self.from_address.iter().cloned().collect(); + let to_addresses = self.to_address.iter().cloned().collect(); + TraceFilterMatcher { mode: self.mode, from_addresses, to_addresses } + } +} + +/// How to apply `from_address` and `to_address` filters. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum TraceFilterMode { + /// Return traces for transactions with matching `from` OR `to` addresses. + #[default] + Union, + /// Only return traces for transactions with matching `from` _and_ `to` addresses. + Intersection, +} + +/// Helper type for matching `from` and `to` addresses. Empty sets match all addresses. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TraceFilterMatcher { + mode: TraceFilterMode, + from_addresses: HashSet
, + to_addresses: HashSet
, +} + +impl TraceFilterMatcher { + /// Returns `true` if the given `from` and `to` addresses match this filter. + pub fn matches(&self, from: Address, to: Option
) -> bool { + match (self.from_addresses.is_empty(), self.to_addresses.is_empty()) { + (true, true) => true, + (false, true) => self.from_addresses.contains(&from), + (true, false) => to.map_or(false, |to_addr| self.to_addresses.contains(&to_addr)), + (false, false) => match self.mode { + TraceFilterMode::Union => { + self.from_addresses.contains(&from) + || to.map_or(false, |to_addr| self.to_addresses.contains(&to_addr)) + } + TraceFilterMode::Intersection => { + self.from_addresses.contains(&from) + && to.map_or(false, |to_addr| self.to_addresses.contains(&to_addr)) + } + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn test_parse_filter() { + let s = r#"{"fromBlock": "0x3","toBlock": "0x5"}"#; + let filter: TraceFilter = serde_json::from_str(s).unwrap(); + assert_eq!(filter.from_block, Some(3)); + assert_eq!(filter.to_block, Some(5)); + } + + #[test] + fn test_filter_matcher_addresses_unspecified() { + let test_addr_d8 = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".parse().unwrap(); + let test_addr_16 = "0x160f5f00288e9e1cc8655b327e081566e580a71d".parse().unwrap(); + let filter_json = json!({ + "fromBlock": "0x3", + "toBlock": "0x5", + }); + let filter: TraceFilter = + serde_json::from_value(filter_json).expect("Failed to parse filter"); + let matcher = filter.matcher(); + assert!(matcher.matches(test_addr_d8, None)); + assert!(matcher.matches(test_addr_16, None)); + assert!(matcher.matches(test_addr_d8, Some(test_addr_16))); + assert!(matcher.matches(test_addr_16, Some(test_addr_d8))); + } + + #[test] + fn test_filter_matcher_from_address() { + let test_addr_d8 = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".parse().unwrap(); + let test_addr_16 = "0x160f5f00288e9e1cc8655b327e081566e580a71d".parse().unwrap(); + let filter_json = json!({ + "fromBlock": "0x3", + "toBlock": "0x5", + "fromAddress": [test_addr_d8] + }); + let filter: TraceFilter = serde_json::from_value(filter_json).unwrap(); + let matcher = filter.matcher(); + assert!(matcher.matches(test_addr_d8, None)); + assert!(!matcher.matches(test_addr_16, None)); + assert!(matcher.matches(test_addr_d8, Some(test_addr_16))); + assert!(!matcher.matches(test_addr_16, Some(test_addr_d8))); + } + + #[test] + fn test_filter_matcher_to_address() { + let test_addr_d8 = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".parse().unwrap(); + let test_addr_16 = "0x160f5f00288e9e1cc8655b327e081566e580a71d".parse().unwrap(); + let filter_json = json!({ + "fromBlock": "0x3", + "toBlock": "0x5", + "toAddress": [test_addr_d8], + }); + let filter: TraceFilter = serde_json::from_value(filter_json).unwrap(); + let matcher = filter.matcher(); + assert!(matcher.matches(test_addr_16, Some(test_addr_d8))); + assert!(!matcher.matches(test_addr_16, None)); + assert!(!matcher.matches(test_addr_d8, Some(test_addr_16))); + } + + #[test] + fn test_filter_matcher_both_addresses_union() { + let test_addr_d8 = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".parse().unwrap(); + let test_addr_16 = "0x160f5f00288e9e1cc8655b327e081566e580a71d".parse().unwrap(); + let filter_json = json!({ + "fromBlock": "0x3", + "toBlock": "0x5", + "fromAddress": [test_addr_16], + "toAddress": [test_addr_d8], + }); + let filter: TraceFilter = serde_json::from_value(filter_json).unwrap(); + let matcher = filter.matcher(); + assert!(matcher.matches(test_addr_16, Some(test_addr_d8))); + assert!(matcher.matches(test_addr_16, None)); + assert!(matcher.matches(test_addr_d8, Some(test_addr_d8))); + assert!(!matcher.matches(test_addr_d8, Some(test_addr_16))); + } + + #[test] + fn test_filter_matcher_both_addresses_intersection() { + let test_addr_d8 = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".parse().unwrap(); + let test_addr_16 = "0x160f5f00288e9e1cc8655b327e081566e580a71d".parse().unwrap(); + let filter_json = json!({ + "fromBlock": "0x3", + "toBlock": "0x5", + "fromAddress": [test_addr_16], + "toAddress": [test_addr_d8], + "mode": "intersection", + }); + let filter: TraceFilter = serde_json::from_value(filter_json).unwrap(); + let matcher = filter.matcher(); + assert!(matcher.matches(test_addr_16, Some(test_addr_d8))); + assert!(!matcher.matches(test_addr_16, None)); + assert!(!matcher.matches(test_addr_d8, Some(test_addr_d8))); + assert!(!matcher.matches(test_addr_d8, Some(test_addr_16))); + } +} diff --git a/crates/rpc-types/src/eth/trace/geth/call.rs b/crates/rpc-types/src/eth/trace/geth/call.rs new file mode 100644 index 00000000000..1d2d7541947 --- /dev/null +++ b/crates/rpc-types/src/eth/trace/geth/call.rs @@ -0,0 +1,105 @@ +use crate::serde_helpers::num::from_int_or_hex; +use alloy_primitives::{Address, Bytes, B256, U256}; +use serde::{Deserialize, Serialize}; + +/// The response object for `debug_traceTransaction` with `"tracer": "callTracer"` +/// +/// +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct CallFrame { + pub from: Address, + #[serde(default, deserialize_with = "from_int_or_hex")] + pub gas: U256, + #[serde(default, deserialize_with = "from_int_or_hex", rename = "gasUsed")] + pub gas_used: U256, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub to: Option
, + pub input: Bytes, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub output: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error: Option, + #[serde(default, rename = "revertReason", skip_serializing_if = "Option::is_none")] + pub revert_reason: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub calls: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub logs: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub value: Option, + #[serde(rename = "type")] + pub typ: String, +} + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct CallLogFrame { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub address: Option
, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub topics: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub data: Option, +} + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CallConfig { + /// When set to true, this will only trace the primary (top-level) call and not any sub-calls. + /// It eliminates the additional processing for each call frame + #[serde(default, skip_serializing_if = "Option::is_none")] + pub only_top_call: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub with_log: Option, +} + +impl CallConfig { + /// Sets the only top call flag + pub fn only_top_call(mut self) -> Self { + self.only_top_call = Some(true); + self + } + + /// Sets the with log flag + pub fn with_log(mut self) -> Self { + self.with_log = Some(true); + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::trace::geth::*; + + // See + const DEFAULT: &str = include_str!("../../../../test_data/call_tracer/default.json"); + const LEGACY: &str = include_str!("../../../../test_data/call_tracer/legacy.json"); + const ONLY_TOP_CALL: &str = + include_str!("../../../../test_data/call_tracer/only_top_call.json"); + const WITH_LOG: &str = include_str!("../../../../test_data/call_tracer/with_log.json"); + + #[test] + fn test_serialize_call_trace() { + let mut opts = GethDebugTracingCallOptions::default(); + opts.tracing_options.config.disable_storage = Some(false); + opts.tracing_options.tracer = + Some(GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::CallTracer)); + opts.tracing_options.tracer_config = + serde_json::to_value(CallConfig { only_top_call: Some(true), with_log: Some(true) }) + .unwrap() + .into(); + + assert_eq!( + serde_json::to_string(&opts).unwrap(), + r#"{"disableStorage":false,"tracer":"callTracer","tracerConfig":{"onlyTopCall":true,"withLog":true}}"# + ); + } + + #[test] + fn test_deserialize_call_trace() { + let _trace: CallFrame = serde_json::from_str(DEFAULT).unwrap(); + let _trace: CallFrame = serde_json::from_str(LEGACY).unwrap(); + let _trace: CallFrame = serde_json::from_str(ONLY_TOP_CALL).unwrap(); + let _trace: CallFrame = serde_json::from_str(WITH_LOG).unwrap(); + } +} diff --git a/crates/rpc-types/src/eth/trace/geth/four_byte.rs b/crates/rpc-types/src/eth/trace/geth/four_byte.rs new file mode 100644 index 00000000000..927df7b76cb --- /dev/null +++ b/crates/rpc-types/src/eth/trace/geth/four_byte.rs @@ -0,0 +1,34 @@ +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +/// +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct FourByteFrame(pub BTreeMap); + +#[cfg(test)] +mod tests { + use super::*; + use crate::trace::geth::*; + + const DEFAULT: &str = r#"{ + "0x27dc297e-128": 1, + "0x38cc4831-0": 2, + "0x524f3889-96": 1, + "0xadf59f99-288": 1, + "0xc281d19e-0": 1 + }"#; + + #[test] + fn test_serialize_four_byte_trace() { + let mut opts = GethDebugTracingCallOptions::default(); + opts.tracing_options.tracer = + Some(GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::FourByteTracer)); + + assert_eq!(serde_json::to_string(&opts).unwrap(), r#"{"tracer":"4byteTracer"}"#); + } + + #[test] + fn test_deserialize_four_byte_trace() { + let _trace: FourByteFrame = serde_json::from_str(DEFAULT).unwrap(); + } +} diff --git a/crates/rpc-types/src/eth/trace/geth/mod.rs b/crates/rpc-types/src/eth/trace/geth/mod.rs new file mode 100644 index 00000000000..c091a0d0bf1 --- /dev/null +++ b/crates/rpc-types/src/eth/trace/geth/mod.rs @@ -0,0 +1,556 @@ +//! Geth tracing types +#![allow(missing_docs)] + +use crate::{state::StateOverride, BlockOverrides}; +use alloy_primitives::{Bytes, B256, U256}; +use serde::{de::DeserializeOwned, ser::SerializeMap, Deserialize, Serialize, Serializer}; +use std::{collections::BTreeMap, time::Duration}; + +// re-exports +pub use self::{ + call::{CallConfig, CallFrame, CallLogFrame}, + four_byte::FourByteFrame, + noop::NoopFrame, + pre_state::{ + AccountChangeKind, AccountState, DiffMode, DiffStateKind, PreStateConfig, PreStateFrame, + PreStateMode, + }, +}; + +pub mod call; +pub mod four_byte; +pub mod noop; +pub mod pre_state; + +/// Result type for geth style transaction trace +pub type TraceResult = crate::trace::common::TraceResult; + +/// blockTraceResult represents the results of tracing a single block when an entire chain is being +/// traced. ref +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct BlockTraceResult { + /// Block number corresponding to the trace task + pub block: U256, + /// Block hash corresponding to the trace task + pub hash: B256, + /// Trace results produced by the trace task + pub traces: Vec, +} + +/// Geth Default struct log trace frame +/// +/// +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DefaultFrame { + pub failed: bool, + pub gas: u64, + #[serde(serialize_with = "crate::serde_helpers::serialize_hex_string_no_prefix")] + pub return_value: Bytes, + pub struct_logs: Vec, +} + +/// Represents a struct log entry in a trace +/// +/// +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct StructLog { + /// program counter + pub pc: u64, + /// opcode to be executed + pub op: String, + /// remaining gas + pub gas: u64, + /// cost for executing op + #[serde(rename = "gasCost")] + pub gas_cost: u64, + /// ref + #[serde(default, skip_serializing_if = "Option::is_none")] + pub memory: Option>, + /// Size of memory. + #[serde(default, rename = "memSize", skip_serializing_if = "Option::is_none")] + pub memory_size: Option, + /// EVM stack + #[serde(default, skip_serializing_if = "Option::is_none")] + pub stack: Option>, + /// Last call's return data. Enabled via enableReturnData + #[serde(default, rename = "returnData", skip_serializing_if = "Option::is_none")] + pub return_data: Option, + /// Storage slots of current contract read from and written to. Only emitted for SLOAD and + /// SSTORE. Disabled via disableStorage + #[serde( + default, + skip_serializing_if = "Option::is_none", + serialize_with = "serialize_string_storage_map_opt" + )] + pub storage: Option>, + /// Current call depth + pub depth: u64, + /// Refund counter + #[serde(default, rename = "refund", skip_serializing_if = "Option::is_none")] + pub refund_counter: Option, + /// Error message if any + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error: Option, +} + +/// Tracing response objects +/// +/// Note: This deserializes untagged, so it's possible that a custom javascript tracer response +/// matches another variant, for example a js tracer that returns `{}` would be deserialized as +/// [GethTrace::NoopTracer] +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] +#[serde(untagged)] +pub enum GethTrace { + /// The response for the default struct log tracer + Default(DefaultFrame), + /// The response for call tracer + CallTracer(CallFrame), + /// The response for four byte tracer + FourByteTracer(FourByteFrame), + /// The response for pre-state byte tracer + PreStateTracer(PreStateFrame), + /// An empty json response + NoopTracer(NoopFrame), + /// Any other trace response, such as custom javascript response objects + JS(serde_json::Value), +} + +impl From for GethTrace { + fn from(value: DefaultFrame) -> Self { + GethTrace::Default(value) + } +} + +impl From for GethTrace { + fn from(value: FourByteFrame) -> Self { + GethTrace::FourByteTracer(value) + } +} + +impl From for GethTrace { + fn from(value: CallFrame) -> Self { + GethTrace::CallTracer(value) + } +} + +impl From for GethTrace { + fn from(value: PreStateFrame) -> Self { + GethTrace::PreStateTracer(value) + } +} + +impl From for GethTrace { + fn from(value: NoopFrame) -> Self { + GethTrace::NoopTracer(value) + } +} + +/// Available built-in tracers +/// +/// See +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] +pub enum GethDebugBuiltInTracerType { + /// The 4byteTracer collects the function selectors of every function executed in the lifetime + /// of a transaction, along with the size of the supplied call data. The result is a + /// [FourByteFrame] where the keys are SELECTOR-CALLDATASIZE and the values are number of + /// occurrences of this key. + #[serde(rename = "4byteTracer")] + FourByteTracer, + /// The callTracer tracks all the call frames executed during a transaction, including depth 0. + /// The result will be a nested list of call frames, resembling how EVM works. They form a tree + /// with the top-level call at root and sub-calls as children of the higher levels. + #[serde(rename = "callTracer")] + CallTracer, + /// The prestate tracer has two modes: prestate and diff. The prestate mode returns the + /// accounts necessary to execute a given transaction. diff mode returns the differences + /// between the transaction's pre and post-state (i.e. what changed because the transaction + /// happened). The prestateTracer defaults to prestate mode. It reexecutes the given + /// transaction and tracks every part of state that is touched. This is similar to the concept + /// of a stateless witness, the difference being this tracer doesn't return any cryptographic + /// proof, rather only the trie leaves. The result is an object. The keys are addresses of + /// accounts. + #[serde(rename = "prestateTracer")] + PreStateTracer, + /// This tracer is noop. It returns an empty object and is only meant for testing the setup. + #[serde(rename = "noopTracer")] + NoopTracer, +} + +/// Available tracers +/// +/// See and +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] +#[serde(untagged)] +pub enum GethDebugTracerType { + /// built-in tracer + BuiltInTracer(GethDebugBuiltInTracerType), + /// custom JS tracer + JsTracer(String), +} + +impl From for GethDebugTracerType { + fn from(value: GethDebugBuiltInTracerType) -> Self { + GethDebugTracerType::BuiltInTracer(value) + } +} + +/// Configuration of the tracer +/// +/// This is a simple wrapper around serde_json::Value. +/// with helpers for deserializing tracer configs. +#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)] +#[serde(transparent)] +pub struct GethDebugTracerConfig(pub serde_json::Value); + +// === impl GethDebugTracerConfig === + +impl GethDebugTracerConfig { + /// Returns if this is a null object + pub fn is_null(&self) -> bool { + self.0.is_null() + } + + /// Consumes the config and tries to deserialize it into the given type. + pub fn from_value(self) -> Result { + serde_json::from_value(self.0) + } + + /// Returns the [CallConfig] if it is a call config. + pub fn into_call_config(self) -> Result { + if self.0.is_null() { + return Ok(Default::default()); + } + self.from_value() + } + + /// Returns the raw json value + pub fn into_json(self) -> serde_json::Value { + self.0 + } + + /// Returns the [PreStateConfig] if it is a call config. + pub fn into_pre_state_config(self) -> Result { + if self.0.is_null() { + return Ok(Default::default()); + } + self.from_value() + } +} + +impl From for GethDebugTracerConfig { + fn from(value: serde_json::Value) -> Self { + GethDebugTracerConfig(value) + } +} + +/// Bindings for additional `debug_traceTransaction` options +/// +/// See +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct GethDebugTracingOptions { + #[serde(default, flatten)] + pub config: GethDefaultTracingOptions, + /// The custom tracer to use. + /// + /// If `None` then the default structlog tracer is used. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tracer: Option, + /// Config specific to given `tracer`. + /// + /// Note default struct logger config are historically embedded in main object. + /// + /// tracerConfig is slated for Geth v1.11.0 + /// See + /// + /// This could be [CallConfig] or [PreStateConfig] depending on the tracer. + #[serde(default, skip_serializing_if = "GethDebugTracerConfig::is_null")] + pub tracer_config: GethDebugTracerConfig, + /// A string of decimal integers that overrides the JavaScript-based tracing calls default + /// timeout of 5 seconds. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub timeout: Option, +} + +impl GethDebugTracingOptions { + /// Sets the tracer to use + pub fn with_tracer(mut self, tracer: GethDebugTracerType) -> Self { + self.tracer = Some(tracer); + self + } + + /// Sets the timeout to use for tracing + pub fn with_timeout(mut self, duration: Duration) -> Self { + self.timeout = Some(format!("{}ms", duration.as_millis())); + self + } + + /// Configures a [CallConfig] + pub fn call_config(mut self, config: CallConfig) -> Self { + self.tracer_config = + GethDebugTracerConfig(serde_json::to_value(config).expect("is serializable")); + self + } + + /// Configures a [PreStateConfig] + pub fn prestate_config(mut self, config: PreStateConfig) -> Self { + self.tracer_config = + GethDebugTracerConfig(serde_json::to_value(config).expect("is serializable")); + self + } +} + +/// Default tracing options for the struct looger. +/// +/// These are all known general purpose tracer options that may or not be supported by a given +/// tracer. For example, the `enableReturnData` option is a noop on regular +/// `debug_trace{Transaction,Block}` calls. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct GethDefaultTracingOptions { + /// enable memory capture + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enable_memory: Option, + /// Disable memory capture + /// + /// This is the opposite of `enable_memory`. + /// + /// Note: memory capture used to be enabled by default on geth, but has since been flipped and is now disabled by default. + /// However, at the time of writing this, erigon still defaults to enabled and supports the + /// `disableMemory` option. So we keep this option for compatibility, but if it's missing + /// OR `enableMemory` is present `enableMemory` takes precedence. + /// + /// See also + #[serde(default, skip_serializing_if = "Option::is_none")] + pub disable_memory: Option, + /// disable stack capture + #[serde(default, skip_serializing_if = "Option::is_none")] + pub disable_stack: Option, + /// Disable storage capture + #[serde(default, skip_serializing_if = "Option::is_none")] + pub disable_storage: Option, + /// Enable return data capture + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enable_return_data: Option, + /// Disable return data capture + /// + /// This is the opposite of `enable_return_data`, and only supported for compatibility reasons. + /// See also `disable_memory`. + /// + /// If `enable_return_data` is present, `enable_return_data` always takes precedence. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub disable_return_data: Option, + /// print output during capture end + #[serde(default, skip_serializing_if = "Option::is_none")] + pub debug: Option, + /// maximum length of output, but zero means unlimited + #[serde(default, skip_serializing_if = "Option::is_none")] + pub limit: Option, +} + +impl GethDefaultTracingOptions { + /// Enables memory capture. + pub fn enable_memory(self) -> Self { + self.with_enable_memory(true) + } + + /// Disables memory capture. + pub fn disable_memory(self) -> Self { + self.with_disable_memory(true) + } + + /// Disables stack capture. + pub fn disable_stack(self) -> Self { + self.with_disable_stack(true) + } + + /// Disables storage capture. + pub fn disable_storage(self) -> Self { + self.with_disable_storage(true) + } + + /// Enables return data capture. + pub fn enable_return_data(self) -> Self { + self.with_enable_return_data(true) + } + + /// Disables return data capture. + pub fn disable_return_data(self) -> Self { + self.with_disable_return_data(true) + } + + /// Enables debug mode. + pub fn debug(self) -> Self { + self.with_debug(true) + } + + /// Sets the enable_memory field. + pub fn with_enable_memory(mut self, enable: bool) -> Self { + self.enable_memory = Some(enable); + self + } + + /// Sets the disable_memory field. + pub fn with_disable_memory(mut self, disable: bool) -> Self { + self.disable_memory = Some(disable); + self + } + + /// Sets the disable_stack field. + pub fn with_disable_stack(mut self, disable: bool) -> Self { + self.disable_stack = Some(disable); + self + } + + /// Sets the disable_storage field. + pub fn with_disable_storage(mut self, disable: bool) -> Self { + self.disable_storage = Some(disable); + self + } + + /// Sets the enable_return_data field. + pub fn with_enable_return_data(mut self, enable: bool) -> Self { + self.enable_return_data = Some(enable); + self + } + + /// Sets the disable_return_data field. + pub fn with_disable_return_data(mut self, disable: bool) -> Self { + self.disable_return_data = Some(disable); + self + } + + /// Sets the debug field. + pub fn with_debug(mut self, debug: bool) -> Self { + self.debug = Some(debug); + self + } + + /// Sets the limit field. + pub fn with_limit(mut self, limit: u64) -> Self { + self.limit = Some(limit); + self + } + /// Returns `true` if return data capture is enabled + pub fn is_return_data_enabled(&self) -> bool { + self.enable_return_data + .or_else(|| self.disable_return_data.map(|disable| !disable)) + .unwrap_or(false) + } + + /// Returns `true` if memory capture is enabled + pub fn is_memory_enabled(&self) -> bool { + self.enable_memory.or_else(|| self.disable_memory.map(|disable| !disable)).unwrap_or(false) + } + + /// Returns `true` if stack capture is enabled + pub fn is_stack_enabled(&self) -> bool { + !self.disable_stack.unwrap_or(false) + } + + /// Returns `true` if storage capture is enabled + pub fn is_storage_enabled(&self) -> bool { + !self.disable_storage.unwrap_or(false) + } +} +/// Bindings for additional `debug_traceCall` options +/// +/// See +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct GethDebugTracingCallOptions { + #[serde(flatten)] + pub tracing_options: GethDebugTracingOptions, + /// The state overrides to apply + #[serde(default, skip_serializing_if = "Option::is_none")] + pub state_overrides: Option, + /// The block overrides to apply + #[serde(default, skip_serializing_if = "Option::is_none")] + pub block_overrides: Option, +} + +/// Serializes a storage map as a list of key-value pairs _without_ 0x-prefix +fn serialize_string_storage_map_opt( + storage: &Option>, + s: S, +) -> Result { + match storage { + None => s.serialize_none(), + Some(storage) => { + let mut m = s.serialize_map(Some(storage.len()))?; + for (key, val) in storage.iter() { + let key = format!("{:?}", key); + let val = format!("{:?}", val); + // skip the 0x prefix + m.serialize_entry(&key.as_str()[2..], &val.as_str()[2..])?; + } + m.end() + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_tracer_config() { + let s = "{\"tracer\": \"callTracer\"}"; + let opts = serde_json::from_str::(s).unwrap(); + assert_eq!( + opts.tracer, + Some(GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::CallTracer)) + ); + let _call_config = opts.tracer_config.clone().into_call_config().unwrap(); + let _prestate_config = opts.tracer_config.into_pre_state_config().unwrap(); + } + + #[test] + fn test_memory_capture() { + let mut config = GethDefaultTracingOptions::default(); + + // by default false + assert!(!config.is_memory_enabled()); + + config.disable_memory = Some(false); + // disable == false -> enable + assert!(config.is_memory_enabled()); + + config.enable_memory = Some(false); + // enable == false -> disable + assert!(!config.is_memory_enabled()); + } + + #[test] + fn test_return_data_capture() { + let mut config = GethDefaultTracingOptions::default(); + + // by default false + assert!(!config.is_return_data_enabled()); + + config.disable_return_data = Some(false); + // disable == false -> enable + assert!(config.is_return_data_enabled()); + + config.enable_return_data = Some(false); + // enable == false -> disable + assert!(!config.is_return_data_enabled()); + } + + // + #[test] + fn serde_default_frame() { + let input = include_str!("../../../../test_data/default/structlogs_01.json"); + let _frame: DefaultFrame = serde_json::from_str(input).unwrap(); + } + + #[test] + fn test_serialize_storage_map() { + let s = r#"{"pc":3349,"op":"SLOAD","gas":23959,"gasCost":2100,"depth":1,"stack":[],"memory":[],"storage":{"6693dabf5ec7ab1a0d1c5bc58451f85d5e44d504c9ffeb75799bfdb61aa2997a":"0000000000000000000000000000000000000000000000000000000000000000"}}"#; + let log: StructLog = serde_json::from_str(s).unwrap(); + let val = serde_json::to_value(&log).unwrap(); + let input = serde_json::from_str::(s).unwrap(); + similar_asserts::assert_eq!(input, val); + } +} diff --git a/crates/rpc-types/src/eth/trace/geth/noop.rs b/crates/rpc-types/src/eth/trace/geth/noop.rs new file mode 100644 index 00000000000..a9db367350f --- /dev/null +++ b/crates/rpc-types/src/eth/trace/geth/noop.rs @@ -0,0 +1,32 @@ +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +/// An empty frame response that's only an empty json object `{}` +/// +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct NoopFrame(BTreeMap); + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)] +struct Null; + +#[cfg(test)] +mod tests { + use super::*; + use crate::trace::geth::*; + + const DEFAULT: &str = r"{}"; + + #[test] + fn test_serialize_noop_trace() { + let mut opts = GethDebugTracingCallOptions::default(); + opts.tracing_options.tracer = + Some(GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::NoopTracer)); + + assert_eq!(serde_json::to_string(&opts).unwrap(), r#"{"tracer":"noopTracer"}"#); + } + + #[test] + fn test_deserialize_noop_trace() { + let _trace: NoopFrame = serde_json::from_str(DEFAULT).unwrap(); + } +} diff --git a/crates/rpc-types/src/eth/trace/geth/pre_state.rs b/crates/rpc-types/src/eth/trace/geth/pre_state.rs new file mode 100644 index 00000000000..5d0f460cc2e --- /dev/null +++ b/crates/rpc-types/src/eth/trace/geth/pre_state.rs @@ -0,0 +1,348 @@ +use crate::serde_helpers::num::from_int_or_hex_opt; +use alloy_primitives::{Address, Bytes, B256, U256}; +use serde::{Deserialize, Serialize}; +use std::collections::{btree_map, BTreeMap}; + +/// A tracer that records [AccountState]s. +/// The prestate tracer has two modes: prestate and diff +/// +/// +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] +#[serde(untagged)] +pub enum PreStateFrame { + /// The default mode returns the accounts necessary to execute a given transaction. + /// + /// It re-executes the given transaction and tracks every part of state that is touched. + Default(PreStateMode), + /// Diff mode returns the differences between the transaction's pre and post-state (i.e. what + /// changed because the transaction happened). + Diff(DiffMode), +} + +impl PreStateFrame { + /// Returns true if this trace was requested without diffmode. + pub fn is_default(&self) -> bool { + matches!(self, PreStateFrame::Default(_)) + } + + /// Returns true if this trace was requested with diffmode. + pub fn is_diff(&self) -> bool { + matches!(self, PreStateFrame::Diff(_)) + } + + /// Returns the account states after the transaction is executed if this trace was requested + /// without diffmode. + pub fn as_default(&self) -> Option<&PreStateMode> { + match self { + PreStateFrame::Default(mode) => Some(mode), + _ => None, + } + } + + /// Returns the account states before and after the transaction is executed if this trace was + /// requested with diffmode. + pub fn as_diff(&self) -> Option<&DiffMode> { + match self { + PreStateFrame::Diff(mode) => Some(mode), + _ => None, + } + } +} + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct PreStateMode(pub BTreeMap); + +/// Represents the account states before and after the transaction is executed. +/// +/// This corresponds to the [DiffMode] of the [PreStateConfig]. +/// +/// This will only contain changed [AccountState]s, created accounts will not be included in the pre +/// state and selfdestructed accounts will not be included in the post state. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct DiffMode { + /// The account states after the transaction is executed. + pub post: BTreeMap, + /// The account states before the transaction is executed. + pub pre: BTreeMap, +} + +// === impl DiffMode === + +impl DiffMode { + /// The sets of the [DiffMode] should only contain changed [AccountState]s. + /// + /// This will remove all unchanged [AccountState]s from the sets. + /// + /// In other words it removes entries that are equal (unchanged) in both the pre and post sets. + pub fn retain_changed(&mut self) -> &mut Self { + self.pre.retain(|address, pre| { + if let btree_map::Entry::Occupied(entry) = self.post.entry(*address) { + if entry.get() == pre { + // remove unchanged account state from both sets + entry.remove(); + return false; + } + } + + true + }); + self + } + + /// Removes all zero values from the storage of the [AccountState]s. + pub fn remove_zero_storage_values(&mut self) { + self.pre.values_mut().for_each(|state| { + state.storage.retain(|_, value| *value != B256::ZERO); + }); + self.post.values_mut().for_each(|state| { + state.storage.retain(|_, value| *value != B256::ZERO); + }); + } +} + +/// Helper type for [DiffMode] to represent a specific set +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DiffStateKind { + /// Corresponds to the pre state of the [DiffMode] + Pre, + /// Corresponds to the post state of the [DiffMode] + Post, +} + +impl DiffStateKind { + /// Returns true if this is the pre state of the [DiffMode] + pub fn is_pre(&self) -> bool { + matches!(self, DiffStateKind::Pre) + } + + /// Returns true if this is the post state of the [DiffMode] + pub fn is_post(&self) -> bool { + matches!(self, DiffStateKind::Post) + } +} + +/// Represents the state of an account +#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct AccountState { + #[serde( + default, + deserialize_with = "from_int_or_hex_opt", + skip_serializing_if = "Option::is_none" + )] + pub balance: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub code: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub nonce: Option, + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub storage: BTreeMap, +} + +impl AccountState { + /// Creates a new `AccountState` with the given account info. + /// + /// If balance is zero, it will be omitted. + /// If nonce is zero, it will be omitted. + /// If code is empty, it will be omitted. + pub fn from_account_info(nonce: u64, balance: U256, code: Option) -> Self { + Self { + balance: Some(balance), + code: code.filter(|code| !code.is_empty()), + nonce: (nonce != 0).then_some(nonce), + storage: Default::default(), + } + } + + /// Removes balance,nonce or code if they match the given account info. + /// + /// This is useful for comparing pre vs post state and only keep changed values in post state. + pub fn remove_matching_account_info(&mut self, other: &AccountState) { + if self.balance == other.balance { + self.balance = None; + } + if self.nonce == other.nonce { + self.nonce = None; + } + if self.code == other.code { + self.code = None; + } + } +} + +/// Helper type to track the kind of change of an [AccountState]. +#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum AccountChangeKind { + #[default] + Modify, + Create, + SelfDestruct, +} + +impl AccountChangeKind { + /// Returns true if the account was created + pub fn is_created(&self) -> bool { + matches!(self, AccountChangeKind::Create) + } + + /// Returns true the account was modified + pub fn is_modified(&self) -> bool { + matches!(self, AccountChangeKind::Modify) + } + + /// Returns true the account was modified + pub fn is_selfdestruct(&self) -> bool { + matches!(self, AccountChangeKind::SelfDestruct) + } +} + +/// The config for the prestate tracer. +/// +/// If `diffMode` is set to true, the response frame includes all the account and storage diffs for +/// the transaction. If it's missing or set to false it only returns the accounts and storage +/// necessary to execute the transaction. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PreStateConfig { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub diff_mode: Option, +} + +impl PreStateConfig { + /// Returns true if this trace was requested with diffmode. + #[inline] + pub fn is_diff_mode(&self) -> bool { + self.diff_mode.unwrap_or_default() + } + + /// Is default mode if diff_mode is not set + #[inline] + pub fn is_default_mode(&self) -> bool { + !self.is_diff_mode() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::trace::geth::*; + + // See + const DEFAULT: &str = include_str!("../../../../test_data/pre_state_tracer/default.json"); + const LEGACY: &str = include_str!("../../../../test_data/pre_state_tracer/legacy.json"); + const DIFF_MODE: &str = include_str!("../../../../test_data/pre_state_tracer/diff_mode.json"); + + #[test] + fn test_serialize_pre_state_trace() { + let mut opts = GethDebugTracingCallOptions::default(); + opts.tracing_options.config.disable_storage = Some(false); + opts.tracing_options.tracer = + Some(GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::PreStateTracer)); + opts.tracing_options.tracer_config = + serde_json::to_value(PreStateConfig { diff_mode: Some(true) }).unwrap().into(); + + assert_eq!( + serde_json::to_string(&opts).unwrap(), + r#"{"disableStorage":false,"tracer":"prestateTracer","tracerConfig":{"diffMode":true}}"# + ); + } + + #[test] + fn test_deserialize_pre_state_trace() { + let trace: PreStateFrame = serde_json::from_str(DEFAULT).unwrap(); + match trace { + PreStateFrame::Default(PreStateMode(_)) => {} + _ => unreachable!(), + } + let _trace: PreStateFrame = serde_json::from_str(LEGACY).unwrap(); + let trace: PreStateFrame = serde_json::from_str(DIFF_MODE).unwrap(); + match trace { + PreStateFrame::Diff(DiffMode { pre: _pre, post: _post }) => {} + _ => unreachable!(), + } + } + + #[test] + fn test_is_diff_mode() { + assert!(PreStateConfig { diff_mode: Some(true) }.is_diff_mode()); + assert!(!PreStateConfig { diff_mode: Some(false) }.is_diff_mode()); + assert!(!PreStateConfig { diff_mode: None }.is_diff_mode()); + } + + #[test] + fn parse_prestate_default_resp() { + let s = r#"{ + "0x0000000000000000000000000000000000000002": { + "balance": "0x0" + }, + "0x008b3b2f992c0e14edaa6e2c662bec549caa8df1": { + "balance": "0x2638035a26d133809" + }, + "0x35a9f94af726f07b5162df7e828cc9dc8439e7d0": { + "balance": "0x7a48734599f7284", + "nonce": 1133 + }, + "0xc8ba32cab1757528daf49033e3673fae77dcf05d": { + "balance": "0x0", + "code": "0x", + "nonce": 1, + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000000000000000000000000000000000000024aea6", + "0x59fb7853eb21f604d010b94c123acbeae621f09ce15ee5d7616485b1e78a72e9": "0x00000000000000c42b56a52aedf18667c8ae258a0280a8912641c80c48cd9548", + "0x8d8ebb65ec00cb973d4fe086a607728fd1b9de14aa48208381eed9592f0dee9a": "0x00000000000000784ae4881e40b1f5ebb4437905fbb8a5914454123b0293b35f", + "0xff896b09014882056009dedb136458f017fcef9a4729467d0d00b4fd413fb1f1": "0x000000000000000e78ac39cb1c20e9edc753623b153705d0ccc487e31f9d6749" + } + } +} +"#; + let pre_state: PreStateFrame = serde_json::from_str(s).unwrap(); + assert!(pre_state.is_default()); + } + #[test] + fn parse_prestate_diff_resp() { + let s = r#"{ + "post": { + "0x35a9f94af726f07b5162df7e828cc9dc8439e7d0": { + "nonce": 1135 + } + }, + "pre": { + "0x35a9f94af726f07b5162df7e828cc9dc8439e7d0": { + "balance": "0x7a48429e177130a", + "nonce": 1134 + } + } +} +"#; + let pre_state: PreStateFrame = serde_json::from_str(s).unwrap(); + assert!(pre_state.is_diff()); + } + + #[test] + fn test_retain_changed_accounts() { + let s = r#"{ + "post": { + "0x35a9f94af726f07b5162df7e828cc9dc8439e7d0": { + "nonce": 1135 + } + }, + "pre": { + "0x35a9f94af726f07b5162df7e828cc9dc8439e7d0": { + "balance": "0x7a48429e177130a", + "nonce": 1134 + } + } +} +"#; + let diff: DiffMode = serde_json::from_str(s).unwrap(); + let mut diff_changed = diff.clone(); + diff_changed.retain_changed(); + // different entries + assert_eq!(diff_changed, diff); + + diff_changed.pre = diff_changed.post.clone(); + diff_changed.retain_changed(); + assert!(diff_changed.post.is_empty()); + assert!(diff_changed.pre.is_empty()); + } +} diff --git a/crates/rpc-types/src/eth/trace/mod.rs b/crates/rpc-types/src/eth/trace/mod.rs new file mode 100644 index 00000000000..7777219595f --- /dev/null +++ b/crates/rpc-types/src/eth/trace/mod.rs @@ -0,0 +1,12 @@ +//! Types for tracing + +pub mod common; +pub mod filter; +pub mod geth; +pub mod parity; +pub mod tracerequest; + +pub use filter::*; +pub use geth::*; +pub use parity::*; +pub use tracerequest::*; diff --git a/crates/rpc-types/src/eth/trace/parity.rs b/crates/rpc-types/src/eth/trace/parity.rs new file mode 100644 index 00000000000..0cf5bef5cb1 --- /dev/null +++ b/crates/rpc-types/src/eth/trace/parity.rs @@ -0,0 +1,472 @@ +#![allow(missing_docs)] +//! Types for trace module. +//! +//! See + +use alloy_primitives::{Address, Bytes, B256, U256, U64}; +use serde::{Deserialize, Serialize}; +use std::{ + collections::BTreeMap, + ops::{Deref, DerefMut}, +}; + +/// Different Trace diagnostic targets. +#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum TraceType { + /// Default trace + Trace, + /// Provides a full trace of the VM’s state throughout the execution of the transaction, + /// including for any subcalls. + VmTrace, + /// Provides information detailing all altered portions of the Ethereum state made due to the + /// execution of the transaction. + StateDiff, +} + +/// The Outcome of a traced transaction with optional settings +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TraceResults { + /// Output of the trace + pub output: Bytes, + /// Enabled if [TraceType::StateDiff] is provided + pub state_diff: Option, + /// Enabled if [TraceType::Trace] is provided, otherwise an empty vec + #[serde(default)] + pub trace: Vec, + /// Enabled if [TraceType::VmTrace] is provided + pub vm_trace: Option, +} + +// === impl TraceResults === + +impl TraceResults { + /// Sets the gas used of the root trace. + /// + /// The root trace's gasUsed should mirror the actual gas used by the transaction. + /// + /// This allows setting it manually by consuming the execution result's gas for example. + pub fn set_root_trace_gas_used(&mut self, gas_used: u64) { + if let Some(r) = self.trace.first_mut().and_then(|t| t.result.as_mut()) { + r.set_gas_used(gas_used) + } + } +} + +/// A `FullTrace` with an additional transaction hash +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TraceResultsWithTransactionHash { + #[serde(flatten)] + pub full_trace: TraceResults, + pub transaction_hash: B256, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct ChangedType { + pub from: T, + pub to: T, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +pub enum Delta { + #[default] + #[serde(rename = "=")] + Unchanged, + #[serde(rename = "+")] + Added(T), + #[serde(rename = "-")] + Removed(T), + #[serde(rename = "*")] + Changed(ChangedType), +} + +// === impl Delta === + +impl Delta { + /// Creates a new [Delta::Changed] variant + pub fn changed(from: T, to: T) -> Self { + Self::Changed(ChangedType { from, to }) + } + + /// Returns true if the value is unchanged + pub fn is_unchanged(&self) -> bool { + matches!(self, Delta::Unchanged) + } + + /// Returns true if the value is added + pub fn is_added(&self) -> bool { + matches!(self, Delta::Added(_)) + } + + /// Returns true if the value is removed + pub fn is_removed(&self) -> bool { + matches!(self, Delta::Removed(_)) + } + + /// Returns true if the value is changed + pub fn is_changed(&self) -> bool { + matches!(self, Delta::Changed(_)) + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AccountDiff { + pub balance: Delta, + pub code: Delta, + pub nonce: Delta, + pub storage: BTreeMap>, +} + +/// New-type for list of account diffs +#[derive(Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize)] +#[serde(transparent)] +pub struct StateDiff(pub BTreeMap); + +impl Deref for StateDiff { + type Target = BTreeMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for StateDiff { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", tag = "type", content = "action")] +pub enum Action { + Call(CallAction), + Create(CreateAction), + /// Parity style traces never renamed suicide to selfdestruct: + /// + /// For compatibility reasons, this is serialized as `suicide`: + #[serde(rename = "suicide", alias = "selfdestruct")] + Selfdestruct(SelfdestructAction), + Reward(RewardAction), +} + +impl Action { + /// Returns true if this is a call action + pub fn is_call(&self) -> bool { + matches!(self, Action::Call(_)) + } + + /// Returns true if this is a create action + pub fn is_create(&self) -> bool { + matches!(self, Action::Call(_)) + } + + /// Returns true if this is a selfdestruct action + pub fn is_selfdestruct(&self) -> bool { + matches!(self, Action::Selfdestruct(_)) + } + /// Returns true if this is a reward action + pub fn is_reward(&self) -> bool { + matches!(self, Action::Reward(_)) + } +} + +/// An external action type. +/// +/// Used as enum identifier for [Action] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ActionType { + /// Contract call. + Call, + /// Contract creation. + Create, + /// Contract suicide/selfdestruct. + #[serde(rename = "suicide", alias = "selfdestruct")] + Selfdestruct, + /// A block reward. + Reward, +} + +/// Call type. +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum CallType { + /// None + #[default] + None, + /// Call + Call, + /// Call code + CallCode, + /// Delegate call + DelegateCall, + /// Static call + StaticCall, +} + +/// Represents a certain [CallType] of a _call_ or message transaction. +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CallAction { + /// Address of the sending account. + pub from: Address, + /// The type of the call. + pub call_type: CallType, + /// The gas available for executing the call. + pub gas: U64, + /// The input data provided to the call. + pub input: Bytes, + /// Address of the destination/target account. + pub to: Address, + /// Value transferred to the destination account. + pub value: U256, +} + +/// Represents a _create_ action, either a `CREATE` operation or a CREATE transaction. +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateAction { + /// The address of the creator. + pub from: Address, + /// The value with which the new account is endowed. + pub value: U256, + /// The gas available for the creation init code. + pub gas: U64, + /// The init code. + pub init: Bytes, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum RewardType { + Block, + Uncle, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RewardAction { + /// Author's address. + pub author: Address, + /// Reward amount. + pub value: U256, + /// Reward type. + pub reward_type: RewardType, +} + +/// Represents a _selfdestruct_ action fka `suicide`. +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SelfdestructAction { + /// destroyed/suicided address. + pub address: Address, + /// destroyed contract heir. + pub refund_address: Address, + /// Balance of the contract just before it was destroyed. + pub balance: U256, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CallOutput { + pub gas_used: U64, + pub output: Bytes, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateOutput { + pub gas_used: U64, + pub code: Bytes, + pub address: Address, +} + +/// Represents the output of a trace. +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum TraceOutput { + /// Output of a regular call transaction. + Call(CallOutput), + /// Output of a CREATE transaction. + Create(CreateOutput), +} + +// === impl TraceOutput === + +impl TraceOutput { + /// Returns the gas used by this trace. + pub fn gas_used(&self) -> U64 { + match self { + TraceOutput::Call(call) => call.gas_used, + TraceOutput::Create(create) => create.gas_used, + } + } + + /// Sets the gas used by this trace. + pub fn set_gas_used(&mut self, gas_used: u64) { + match self { + TraceOutput::Call(call) => call.gas_used = U64::from(gas_used), + TraceOutput::Create(create) => create.gas_used = U64::from(gas_used), + } + } +} + +/// A parity style trace of a transaction. +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TransactionTrace { + #[serde(flatten)] + pub action: Action, + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + pub result: Option, + pub subtraces: usize, + pub trace_address: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct LocalizedTransactionTrace { + #[serde(flatten)] + pub trace: TransactionTrace, + /// Hash of the block, if not pending + /// + /// Note: this deviates from which always returns a block number + #[serde(skip_serializing_if = "Option::is_none")] + pub block_hash: Option, + /// Block number the transaction is included in, None if pending. + /// + /// Note: this deviates from which always returns a block number + #[serde(skip_serializing_if = "Option::is_none")] + pub block_number: Option, + /// Hash of the transaction + #[serde(skip_serializing_if = "Option::is_none")] + pub transaction_hash: Option, + /// Transaction index within the block, None if pending. + #[serde(skip_serializing_if = "Option::is_none")] + pub transaction_position: Option, +} + +/// A record of a full VM trace for a CALL/CREATE. +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VmTrace { + /// The code to be executed. + pub code: Bytes, + /// All executed instructions. + pub ops: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VmInstruction { + /// The gas cost for this instruction. + pub cost: u64, + /// Information concerning the execution of the operation. + pub ex: Option, + /// The program counter. + pub pc: usize, + /// Subordinate trace of the CALL/CREATE if applicable. + pub sub: Option, + /// Stringified opcode. + #[serde(skip_serializing_if = "Option::is_none")] + pub op: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub idx: Option, +} + +/// A record of an executed VM operation. +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VmExecutedOperation { + /// The total gas used. + pub used: u64, + /// The stack item placed, if any. + pub push: Vec, + /// If altered, the memory delta. + pub mem: Option, + /// The altered storage value, if any. + pub store: Option, +} + +/// A diff of some chunk of memory. +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MemoryDelta { + /// Offset into memory the change begins. + pub off: usize, + /// The changed data. + pub data: Bytes, +} + +/// A diff of some storage value. +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct StorageDelta { + pub key: U256, + pub val: U256, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_transaction_trace() { + let s = r#"{ + "action": { + "from": "0x66e29f0b6b1b07071f2fde4345d512386cb66f5f", + "callType": "call", + "gas": "0x10bfc", + "input": "0xf6cd1e8d0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ec6952892271c8ee13f12e118484e03149281c9600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010480862479000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000160f5f00288e9e1cc8655b327e081566e580a71d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000011c37937e080000fffffffffffffffffffffffffffffffffffffffffffffffffee3c86c81f8000000000000000000000000000000000000000000000000000000000000", + "to": "0x160f5f00288e9e1cc8655b327e081566e580a71d", + "value": "0x244b" + }, + "error": "Reverted", + "result": { + "gasUsed": "0x9daf", + "output": "0x000000000000000000000000000000000000000000000000011c37937e080000" + }, + "subtraces": 3, + "traceAddress": [], + "type": "call" + }"#; + let val = serde_json::from_str::(s).unwrap(); + serde_json::to_value(val).unwrap(); + } + + #[test] + fn test_selfdestruct_suicide() { + let input = r#"{ + "action": { + "address": "0x66e29f0b6b1b07071f2fde4345d512386cb66f5f", + "refundAddress": "0x66e29f0b6b1b07071f2fde4345d512386cb66f5f", + "balance": "0x244b" + }, + "error": "Reverted", + "result": { + "gasUsed": "0x9daf", + "output": "0x000000000000000000000000000000000000000000000000011c37937e080000" + }, + "subtraces": 3, + "traceAddress": [], + "type": "suicide" + }"#; + let val = serde_json::from_str::(input).unwrap(); + assert!(val.action.is_selfdestruct()); + + let json = serde_json::to_value(val.clone()).unwrap(); + let expect = serde_json::from_str::(input).unwrap(); + similar_asserts::assert_eq!(json, expect); + let s = serde_json::to_string(&val).unwrap(); + let json = serde_json::from_str::(&s).unwrap(); + similar_asserts::assert_eq!(json, expect); + + let input = input.replace("suicide", "selfdestruct"); + let val = serde_json::from_str::(&input).unwrap(); + assert!(val.action.is_selfdestruct()); + } +} diff --git a/crates/rpc-types/src/eth/trace/tracerequest.rs b/crates/rpc-types/src/eth/trace/tracerequest.rs new file mode 100644 index 00000000000..3c2c2563afe --- /dev/null +++ b/crates/rpc-types/src/eth/trace/tracerequest.rs @@ -0,0 +1,84 @@ +//! Builder style functions for `trace_call` + +use crate::{ + eth::block::BlockId, state::StateOverride, trace::parity::TraceType, BlockOverrides, + CallRequest, +}; +use serde::{Deserialize, Serialize}; +use std::collections::HashSet; + +/// Container type for `trace_call` arguments +#[derive(Debug, Serialize, Deserialize)] +pub struct TraceCallRequest { + /// call request object + pub call: CallRequest, + /// trace types + pub trace_types: HashSet, + /// Optional: blockId + pub block_id: Option, + /// Optional: StateOverride + pub state_overrides: Option, + /// Optional: BlockOverrides + pub block_overrides: Option>, +} + +impl TraceCallRequest { + /// Returns a new [`TraceCallRequest`] given a [`CallRequest`] and [`HashSet`] + pub fn new(call: CallRequest) -> Self { + Self { + call, + trace_types: HashSet::new(), + block_id: None, + state_overrides: None, + block_overrides: None, + } + } + + /// Sets the [`BlockId`] + /// Note: this is optional + pub fn with_block_id(mut self, block_id: BlockId) -> Self { + self.block_id = Some(block_id); + self + } + + /// Sets the [`StateOverride`] + /// Note: this is optional + pub fn with_state_override(mut self, state_overrides: StateOverride) -> Self { + self.state_overrides = Some(state_overrides); + self + } + + /// Sets the [`BlockOverrides`] + /// Note: this is optional + pub fn with_block_overrides(mut self, block_overrides: Box) -> Self { + self.block_overrides = Some(block_overrides); + self + } + + /// Inserts a single trace type. + pub fn with_trace_type(mut self, trace_type: TraceType) -> Self { + self.trace_types.insert(trace_type); + self + } + + /// Inserts multiple trace types from an iterator. + pub fn with_trace_types>(mut self, trace_types: I) -> Self { + self.trace_types.extend(trace_types); + self + } + + /// Inserts [`TraceType::Trace`] + pub fn with_trace(self) -> Self { + self.with_trace_type(TraceType::Trace) + } + + /// Inserts [`TraceType::VmTrace`] + pub fn with_vm_trace(self) -> Self { + self.with_trace_type(TraceType::VmTrace) + } + + /// Inserts [`TraceType::StateDiff`] + pub fn with_statediff(self) -> Self { + self.with_trace_type(TraceType::StateDiff) + } +} diff --git a/crates/rpc-types/src/eth/transaction/signature.rs b/crates/rpc-types/src/eth/transaction/signature.rs index d447e3427f6..82866a639a6 100644 --- a/crates/rpc-types/src/eth/transaction/signature.rs +++ b/crates/rpc-types/src/eth/transaction/signature.rs @@ -31,6 +31,12 @@ pub struct Parity( #[serde(serialize_with = "serialize_parity", deserialize_with = "deserialize_parity")] pub bool, ); +impl From for Parity { + fn from(b: bool) -> Self { + Self(b) + } +} + fn serialize_parity(parity: &bool, serializer: S) -> Result where S: serde::Serializer, diff --git a/crates/rpc-types/src/eth/transaction/typed.rs b/crates/rpc-types/src/eth/transaction/typed.rs index 7c75b4e7ceb..e529d37b9f9 100644 --- a/crates/rpc-types/src/eth/transaction/typed.rs +++ b/crates/rpc-types/src/eth/transaction/typed.rs @@ -1,10 +1,8 @@ +#![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`]. -// TODO: -#![allow(missing_docs)] - use crate::eth::transaction::AccessList; use alloy_primitives::{Address, Bytes, U128, U256, U64}; use alloy_rlp::{BufMut, Decodable, Encodable, Error as RlpError}; @@ -18,8 +16,11 @@ use serde::{Deserialize, Serialize}; /// 3. EIP1559 [`EIP1559TransactionRequest`] #[derive(Debug, Clone, Eq, PartialEq)] pub enum TypedTransactionRequest { + /// A Legacy Transaction request. Legacy(LegacyTransactionRequest), + /// An EIP2930 transaction request. EIP2930(EIP2930TransactionRequest), + /// An EIP1559 Transaction Request. EIP1559(EIP1559TransactionRequest), } diff --git a/crates/rpc-types/src/eth/txpool.rs b/crates/rpc-types/src/eth/txpool.rs new file mode 100644 index 00000000000..c277b089774 --- /dev/null +++ b/crates/rpc-types/src/eth/txpool.rs @@ -0,0 +1,520 @@ +//! Types for the `txpool` namespace: + +use crate::Transaction; +use alloy_primitives::{Address, U256, U64}; +use serde::{ + de::{self, Deserializer, Visitor}, + Deserialize, Serialize, +}; +use std::{collections::BTreeMap, fmt, str::FromStr}; + +/// Transaction summary as found in the Txpool Inspection property. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TxpoolInspectSummary { + /// Recipient (None when contract creation) + pub to: Option
, + /// Transferred value + pub value: U256, + /// Gas amount + pub gas: U256, + /// Gas Price + pub gas_price: U256, +} + +/// Visitor struct for TxpoolInspectSummary. +struct TxpoolInspectSummaryVisitor; + +/// Walk through the deserializer to parse a txpool inspection summary into the +/// `TxpoolInspectSummary` struct. +impl<'de> Visitor<'de> for TxpoolInspectSummaryVisitor { + type Value = TxpoolInspectSummary; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("to: value wei + gasLimit gas × gas_price wei") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + let addr_split: Vec<&str> = value.split(": ").collect(); + if addr_split.len() != 2 { + return Err(de::Error::custom("invalid format for TxpoolInspectSummary: to")); + } + let value_split: Vec<&str> = addr_split[1].split(" wei + ").collect(); + if value_split.len() != 2 { + return Err(de::Error::custom("invalid format for TxpoolInspectSummary: gasLimit")); + } + let gas_split: Vec<&str> = value_split[1].split(" gas × ").collect(); + if gas_split.len() != 2 { + return Err(de::Error::custom("invalid format for TxpoolInspectSummary: gas")); + } + let gas_price_split: Vec<&str> = gas_split[1].split(" wei").collect(); + if gas_price_split.len() != 2 { + return Err(de::Error::custom("invalid format for TxpoolInspectSummary: gas_price")); + } + let addr = match addr_split[0] { + "" => None, + "0x" => None, + "contract creation" => None, + addr => { + Some(Address::from_str(addr.trim_start_matches("0x")).map_err(de::Error::custom)?) + } + }; + let value = U256::from_str(value_split[0]).map_err(de::Error::custom)?; + let gas = U256::from_str(gas_split[0]).map_err(de::Error::custom)?; + let gas_price = U256::from_str(gas_price_split[0]).map_err(de::Error::custom)?; + + Ok(TxpoolInspectSummary { to: addr, value, gas, gas_price }) + } + + fn visit_string(self, value: String) -> Result + where + E: de::Error, + { + self.visit_str(&value) + } +} + +/// Implement the `Deserialize` trait for `TxpoolInspectSummary` struct. +impl<'de> Deserialize<'de> for TxpoolInspectSummary { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(TxpoolInspectSummaryVisitor) + } +} + +/// Implement the `Serialize` trait for `TxpoolInspectSummary` struct so that the +/// format matches the one from geth. +impl Serialize for TxpoolInspectSummary { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let formatted_to = if let Some(to) = self.to { + format!("{to:?}") + } else { + "contract creation".to_string() + }; + let formatted = format!( + "{}: {} wei + {} gas × {} wei", + formatted_to, self.value, self.gas, self.gas_price + ); + serializer.serialize_str(&formatted) + } +} + +/// Transaction Pool Content +/// +/// The content inspection property can be queried to list the exact details of all +/// the transactions currently pending for inclusion in the next block(s), as well +/// as the ones that are being scheduled for future execution only. +/// +/// See [here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_content) for more details +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct TxpoolContent { + /// pending tx + pub pending: BTreeMap>, + /// queued tx + pub queued: BTreeMap>, +} + +impl TxpoolContent { + /// Removes the transactions from the given sender + pub fn remove_from(&mut self, sender: &Address) -> TxpoolContentFrom { + TxpoolContentFrom { + pending: self.pending.remove(sender).unwrap_or_default(), + queued: self.queued.remove(sender).unwrap_or_default(), + } + } +} + +/// Transaction Pool Content From +/// +/// Same as [TxpoolContent] but for a specific address. +/// +/// See [here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_contentFrom) for more details +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct TxpoolContentFrom { + /// pending tx + pub pending: BTreeMap, + /// queued tx + pub queued: BTreeMap, +} + +/// Transaction Pool Inspect +/// +/// The inspect inspection property can be queried to list a textual summary +/// of all the transactions currently pending for inclusion in the next block(s), +/// as well as the ones that are being scheduled for future execution only. +/// This is a method specifically tailored to developers to quickly see the +/// transactions in the pool and find any potential issues. +/// +/// See [here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_inspect) for more details +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct TxpoolInspect { + /// pending tx + pub pending: BTreeMap>, + /// queued tx + pub queued: BTreeMap>, +} + +/// Transaction Pool Status +/// +/// The status inspection property can be queried for the number of transactions +/// currently pending for inclusion in the next block(s), as well as the ones that +/// are being scheduled for future execution only. +/// +/// See [here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_status) for more details +#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub struct TxpoolStatus { + /// number of pending tx + pub pending: U64, + /// number of queued tx + pub queued: U64, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serde_txpool_content() { + // Gathered from geth v1.10.23-stable-d901d853/linux-amd64/go1.18.5 + // Addresses used as keys in `pending` and `queued` have been manually lowercased to + // simplify the test. + let txpool_content_json = r#" +{ + "pending": { + "0x00000000863b56a3c1f0f1be8bc4f8b7bd78f57a": { + "29": { + "blockHash": null, + "blockNumber": null, + "from": "0x00000000863b56a3c1f0f1be8bc4f8b7bd78f57a", + "gas": "0x2af9e", + "gasPrice": "0x218711a00", + "maxFeePerGas": "0x218711a00", + "maxPriorityFeePerGas": "0x3b9aca00", + "hash": "0xfbc6fd04ba1c4114f06574263f04099b4fb2da72acc6f9709f0a3d2361308344", + "input": "0x5ae401dc00000000000000000000000000000000000000000000000000000000636c757700000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e404e45aaf000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000863b56a3c1f0f1be8bc4f8b7bd78f57a000000000000000000000000000000000000000000000000000000007781df4000000000000000000000000000000000000000000000006c240454bf9c87cd84000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x1d", + "to": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", + "transactionIndex": null, + "value": "0x0", + "type": "0x2", + "accessList": [], + "chainId": "0x1", + "v": "0x0", + "r": "0xbb809ae71b03319ba2811ebd581c85665169143ffade86e07d2eb4cd03b544dc", + "s": "0x65a2aa7e0e70356f765205a611d580de8e84fa79086f117fd9ab4765f5cf1339" + } + }, + "0x000042429c09de5881f05a0c2a068222f4f5b091": { + "38": { + "blockHash": null, + "blockNumber": null, + "from": "0x000042429c09de5881f05a0c2a068222f4f5b091", + "gas": "0x61a80", + "gasPrice": "0x2540be400", + "hash": "0x054ad1ccf5917139a9b1952f62048f742255a7c11100f593c4f18c1ed49b8dfd", + "input": "0x27dc297e800332e506f28f49a13c1edf087bdd6482d6cb3abdf2a4c455642aef1e98fc240000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002d7b22444149223a313439332e37342c2254555344223a313438392e36362c2255534443223a313439322e34387d00000000000000000000000000000000000000", + "nonce": "0x26", + "to": "0xabd279febe00c93fb0c9e683c6919ec4f107241f", + "transactionIndex": null, + "value": "0x0", + "type": "0x0", + "chainId": "0x1", + "v": "0x26", + "r": "0xaf46b2c0f067f7d1d63ac19daa349c0e1eb83f019ee00542ffa7095e05352e92", + "s": "0x21d6d24d58ec361379ffffe4cc17bec8ce2b9f5f9759a91afc9a54dfdfa519c2" + } + }, + "0x000fab888651fbceb55de230493562159ead0340": { + "12": { + "blockHash": null, + "blockNumber": null, + "from": "0x000fab888651fbceb55de230493562159ead0340", + "gas": "0x12fed", + "gasPrice": "0x1a13b8600", + "maxFeePerGas": "0x1a13b8600", + "maxPriorityFeePerGas": "0x59682f00", + "hash": "0xfae0cffdae6774abe11662a2cdbea019fce48fca87ba9ebf5e9e7c2454c01715", + "input": "0xa9059cbb00000000000000000000000050272a56ef9aff7238e8b40347da62e87c1f69e200000000000000000000000000000000000000000000000000000000428d3dfc", + "nonce": "0xc", + "to": "0x8e8d6ab093905c400d583efd37fbeeb1ee1c0c39", + "transactionIndex": null, + "value": "0x0", + "type": "0x2", + "accessList": [], + "chainId": "0x1", + "v": "0x0", + "r": "0x7b717e689d1bd045ee7afd79b97219f2e36bd22a6a14e07023902194bca96fbf", + "s": "0x7b0ba462c98e7b0f95a53f047cf568ee0443839628dfe4ab294bfab88fa8e251" + } + } + }, + "queued": { + "0x00b846f07f5e7c61569437ca16f88a9dfa00f1bf": { + "143": { + "blockHash": null, + "blockNumber": null, + "from": "0x00b846f07f5e7c61569437ca16f88a9dfa00f1bf", + "gas": "0x33c3b", + "gasPrice": "0x218711a00", + "maxFeePerGas": "0x218711a00", + "maxPriorityFeePerGas": "0x77359400", + "hash": "0x68959706857f7a58d752ede0a5118a5f55f4ae40801f31377e1af201944720b2", + "input": "0x03a9ea6d00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000f2ff840000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000041d0c4694374d7893d63605625687be2f01028a5b49eca00f72901e773ad8ba7906e58d43e114a28353efaf8abd6a2675de83a3a07af579b8b268e6b714376610d1c00000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x8f", + "to": "0xfbddadd80fe7bda00b901fbaf73803f2238ae655", + "transactionIndex": null, + "value": "0x1f58a57c1794eb", + "type": "0x2", + "accessList": [], + "chainId": "0x1", + "v": "0x0", + "r": "0x77d149add2b1b84af9408af55661b05b21e2a436f9bfcaa844584905a0f8f1ac", + "s": "0x358d79063d702f0c3fb46ad0f6ce5db61f5fdb0b20359c8da2e72a11988db283" + } + }, + "0x025276ec2de8ee570cfd4c1010319f14a6d9f0dd": { + "1": { + "blockHash": null, + "blockNumber": null, + "from": "0x025276ec2de8ee570cfd4c1010319f14a6d9f0dd", + "gas": "0x7918", + "gasPrice": "0x12e531724e", + "maxFeePerGas": "0x12e531724e", + "maxPriorityFeePerGas": "0x59682f00", + "hash": "0x35109918ab6129a4d69480514ebec0ea08dc4a4de032fec59003ea66718828c4", + "input": "0x", + "nonce": "0x1", + "to": "0x025276ec2de8ee570cfd4c1010319f14a6d9f0dd", + "transactionIndex": null, + "value": "0x0", + "type": "0x2", + "accessList": [], + "chainId": "0x1", + "v": "0x0", + "r": "0x863ed0413a14f3f1695fd9728f1500a2b46e69d6f4c82408af15354cc5a667d6", + "s": "0x2d503050aa1c9ecbb6df9957459c296f2f6190bc07aa09047d541233100b1c7a" + }, + "4": { + "blockHash": null, + "blockNumber": null, + "from": "0x025276ec2de8ee570cfd4c1010319f14a6d9f0dd", + "gas": "0x7530", + "gasPrice": "0x1919617600", + "maxFeePerGas": "0x1919617600", + "maxPriorityFeePerGas": "0x5c7261c0", + "hash": "0xa58e54464b2ca62a5e2d976604ed9a53b13f8823a170ee4c3ae0cd91cde2a6c5", + "input": "0x", + "nonce": "0x4", + "to": "0x025276ec2de8ee570cfd4c1010319f14a6d9f0dd", + "transactionIndex": null, + "value": "0x0", + "type": "0x2", + "accessList": [], + "chainId": "0x1", + "v": "0x1", + "r": "0xb6a571191c4b5b667876295571c42c9411bbb4569eea1a6ad149572e4efc55a9", + "s": "0x248a72dab9b24568dd9cbe289c205eaba1a6b58b32b5a96c48554945d3fd0d86" + } + }, + "0x02666081cfb787de3562efbbca5f0fe890e927f1": { + "44": { + "blockHash": null, + "blockNumber": null, + "from": "0x02666081cfb787de3562efbbca5f0fe890e927f1", + "gas": "0x16404", + "gasPrice": "0x4bad00695", + "maxFeePerGas": "0x4bad00695", + "maxPriorityFeePerGas": "0xa3e9ab80", + "hash": "0xf627e59d7a59eb650f4c9df222858572601a566263809fdacbb755ac2277a4a7", + "input": "0x095ea7b300000000000000000000000029fbd00940df70cfc5dad3f2370686991e2bbf5cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "nonce": "0x2c", + "to": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "transactionIndex": null, + "value": "0x0", + "type": "0x2", + "accessList": [], + "chainId": "0x1", + "v": "0x1", + "r": "0xcfc88f55fc0779d12705acba58719cd7d0ed5b0c1a7c3c3682b56397ca493dd5", + "s": "0x7e7dc008058c543ebfdae67154c797639447db5e8006f8fc0585352d857c1b6c" + } + } + } +}"#; + let deserialized: TxpoolContent = serde_json::from_str(txpool_content_json).unwrap(); + let serialized: String = serde_json::to_string_pretty(&deserialized).unwrap(); + + let origin: serde_json::Value = serde_json::from_str(txpool_content_json).unwrap(); + let serialized_value = serde_json::to_value(deserialized.clone()).unwrap(); + assert_eq!(origin, serialized_value); + assert_eq!(deserialized, serde_json::from_str::(&serialized).unwrap()); + } + + #[test] + fn serde_txpool_inspect() { + let txpool_inspect_json = r#" +{ + "pending": { + "0x0512261a7486b1e29704ac49a5eb355b6fd86872": { + "124930": "0x000000000000000000000000000000000000007E: 0 wei + 100187 gas × 20000000000 wei" + }, + "0x201354729f8d0f8b64e9a0c353c672c6a66b3857": { + "252350": "0xd10e3Be2bc8f959Bc8C41CF65F60dE721cF89ADF: 0 wei + 65792 gas × 2000000000 wei", + "252351": "0xd10e3Be2bc8f959Bc8C41CF65F60dE721cF89ADF: 0 wei + 65792 gas × 2000000000 wei", + "252352": "0xd10e3Be2bc8f959Bc8C41CF65F60dE721cF89ADF: 0 wei + 65780 gas × 2000000000 wei", + "252353": "0xd10e3Be2bc8f959Bc8C41CF65F60dE721cF89ADF: 0 wei + 65780 gas × 2000000000 wei" + }, + "0x00000000863B56a3C1f0F1be8BC4F8b7BD78F57a": { + "40": "contract creation: 0 wei + 612412 gas × 6000000000 wei" + } + }, + "queued": { + "0x0f87ffcd71859233eb259f42b236c8e9873444e3": { + "7": "0x3479BE69e07E838D9738a301Bb0c89e8EA2Bef4a: 1000000000000000 wei + 21000 gas × 10000000000 wei", + "8": "0x73Aaf691bc33fe38f86260338EF88f9897eCaa4F: 1000000000000000 wei + 21000 gas × 10000000000 wei" + }, + "0x307e8f249bcccfa5b245449256c5d7e6e079943e": { + "3": "0x73Aaf691bc33fe38f86260338EF88f9897eCaa4F: 10000000000000000 wei + 21000 gas × 10000000000 wei" + } + } +}"#; + let deserialized: TxpoolInspect = serde_json::from_str(txpool_inspect_json).unwrap(); + assert_eq!(deserialized, expected_txpool_inspect()); + + let serialized = serde_json::to_string(&deserialized).unwrap(); + let deserialized2: TxpoolInspect = serde_json::from_str(&serialized).unwrap(); + assert_eq!(deserialized2, deserialized); + } + + #[test] + fn serde_txpool_status() { + let txpool_status_json = r#" +{ + "pending": "0x23", + "queued": "0x20" +}"#; + let deserialized: TxpoolStatus = serde_json::from_str(txpool_status_json).unwrap(); + let serialized: String = serde_json::to_string_pretty(&deserialized).unwrap(); + assert_eq!(txpool_status_json.trim(), serialized); + } + + fn expected_txpool_inspect() -> TxpoolInspect { + let mut pending_map = BTreeMap::new(); + let mut pending_map_inner = BTreeMap::new(); + pending_map_inner.insert( + "124930".to_string(), + TxpoolInspectSummary { + to: Some(Address::from_str("000000000000000000000000000000000000007E").unwrap()), + value: U256::from(0u128), + gas: U256::from(100187u128), + gas_price: U256::from(20000000000u128), + }, + ); + pending_map.insert( + Address::from_str("0512261a7486b1e29704ac49a5eb355b6fd86872").unwrap(), + pending_map_inner.clone(), + ); + pending_map_inner.clear(); + pending_map_inner.insert( + "252350".to_string(), + TxpoolInspectSummary { + to: Some(Address::from_str("d10e3Be2bc8f959Bc8C41CF65F60dE721cF89ADF").unwrap()), + value: U256::from(0u128), + gas: U256::from(65792u128), + gas_price: U256::from(2000000000u128), + }, + ); + pending_map_inner.insert( + "252351".to_string(), + TxpoolInspectSummary { + to: Some(Address::from_str("d10e3Be2bc8f959Bc8C41CF65F60dE721cF89ADF").unwrap()), + value: U256::from(0u128), + gas: U256::from(65792u128), + gas_price: U256::from(2000000000u128), + }, + ); + pending_map_inner.insert( + "252352".to_string(), + TxpoolInspectSummary { + to: Some(Address::from_str("d10e3Be2bc8f959Bc8C41CF65F60dE721cF89ADF").unwrap()), + value: U256::from(0u128), + gas: U256::from(65780u128), + gas_price: U256::from(2000000000u128), + }, + ); + pending_map_inner.insert( + "252353".to_string(), + TxpoolInspectSummary { + to: Some(Address::from_str("d10e3Be2bc8f959Bc8C41CF65F60dE721cF89ADF").unwrap()), + value: U256::from(0u128), + gas: U256::from(65780u128), + gas_price: U256::from(2000000000u128), + }, + ); + pending_map.insert( + Address::from_str("201354729f8d0f8b64e9a0c353c672c6a66b3857").unwrap(), + pending_map_inner.clone(), + ); + pending_map_inner.clear(); + pending_map_inner.insert( + "40".to_string(), + TxpoolInspectSummary { + to: None, + value: U256::from(0u128), + gas: U256::from(612412u128), + gas_price: U256::from(6000000000u128), + }, + ); + pending_map.insert( + Address::from_str("00000000863B56a3C1f0F1be8BC4F8b7BD78F57a").unwrap(), + pending_map_inner, + ); + let mut queued_map = BTreeMap::new(); + let mut queued_map_inner = BTreeMap::new(); + queued_map_inner.insert( + "7".to_string(), + TxpoolInspectSummary { + to: Some(Address::from_str("3479BE69e07E838D9738a301Bb0c89e8EA2Bef4a").unwrap()), + value: U256::from(1000000000000000u128), + gas: U256::from(21000u128), + gas_price: U256::from(10000000000u128), + }, + ); + queued_map_inner.insert( + "8".to_string(), + TxpoolInspectSummary { + to: Some(Address::from_str("73Aaf691bc33fe38f86260338EF88f9897eCaa4F").unwrap()), + value: U256::from(1000000000000000u128), + gas: U256::from(21000u128), + gas_price: U256::from(10000000000u128), + }, + ); + queued_map.insert( + Address::from_str("0f87ffcd71859233eb259f42b236c8e9873444e3").unwrap(), + queued_map_inner.clone(), + ); + queued_map_inner.clear(); + queued_map_inner.insert( + "3".to_string(), + TxpoolInspectSummary { + to: Some(Address::from_str("73Aaf691bc33fe38f86260338EF88f9897eCaa4F").unwrap()), + value: U256::from(10000000000000000u128), + gas: U256::from(21000u128), + gas_price: U256::from(10000000000u128), + }, + ); + queued_map.insert( + Address::from_str("307e8f249bcccfa5b245449256c5d7e6e079943e").unwrap(), + queued_map_inner, + ); + + TxpoolInspect { pending: pending_map, queued: queued_map } + } +}