feat(rpc): use alloy-rpc-types for ethereum-related types (#5947)

This commit is contained in:
evalir
2024-01-04 13:11:49 -04:00
committed by GitHub
parent e3c3ddc14e
commit 09f04a0f6c
49 changed files with 91 additions and 6813 deletions

22
Cargo.lock generated
View File

@ -216,6 +216,25 @@ dependencies = [
"syn 2.0.43", "syn 2.0.43",
] ]
[[package]]
name = "alloy-rpc-types"
version = "0.1.0"
source = "git+https://github.com/alloy-rs/alloy#e64c1c2b9e4f80fe13ac4f65504a4c30fe786454"
dependencies = [
"alloy-primitives",
"alloy-rlp",
"arbitrary",
"ethereum_ssz",
"ethereum_ssz_derive",
"itertools 0.12.0",
"jsonrpsee-types",
"proptest",
"proptest-derive",
"serde",
"serde_json",
"thiserror",
]
[[package]] [[package]]
name = "alloy-sol-macro" name = "alloy-sol-macro"
version = "0.5.4" version = "0.5.4"
@ -6562,6 +6581,7 @@ version = "0.1.0-alpha.13"
dependencies = [ dependencies = [
"alloy-primitives", "alloy-primitives",
"alloy-rlp", "alloy-rlp",
"alloy-rpc-types",
"arbitrary", "arbitrary",
"bytes", "bytes",
"ethereum_ssz", "ethereum_ssz",
@ -6585,8 +6605,10 @@ name = "reth-rpc-types-compat"
version = "0.1.0-alpha.13" version = "0.1.0-alpha.13"
dependencies = [ dependencies = [
"alloy-rlp", "alloy-rlp",
"alloy-rpc-types",
"reth-primitives", "reth-primitives",
"reth-rpc-types", "reth-rpc-types",
"serde_json",
] ]
[[package]] [[package]]

View File

@ -160,6 +160,7 @@ alloy-primitives = "0.5"
alloy-dyn-abi = "0.5" alloy-dyn-abi = "0.5"
alloy-sol-types = "0.5" alloy-sol-types = "0.5"
alloy-rlp = "0.3" alloy-rlp = "0.3"
alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", features = ["jsonrpsee-types"] }
ethers-core = { version = "2.0", default-features = false } ethers-core = { version = "2.0", default-features = false }
ethers-providers = { version = "2.0", default-features = false } ethers-providers = { version = "2.0", default-features = false }
ethers-signers = { version = "2.0", default-features = false } ethers-signers = { version = "2.0", default-features = false }

View File

@ -400,7 +400,7 @@ const _: [(); std::mem::size_of::<BlobTransactionSidecar>()] =
[(); std::mem::size_of::<BlobTransactionSidecarRlp>()]; [(); std::mem::size_of::<BlobTransactionSidecarRlp>()];
const _: [(); std::mem::size_of::<BlobTransactionSidecar>()] = const _: [(); std::mem::size_of::<BlobTransactionSidecar>()] =
[(); std::mem::size_of::<reth_rpc_types::BlobTransactionSidecar>()]; [(); std::mem::size_of::<reth_rpc_types::transaction::BlobTransactionSidecar>()];
impl BlobTransactionSidecarRlp { impl BlobTransactionSidecarRlp {
fn wrap_ref(other: &BlobTransactionSidecar) -> &Self { fn wrap_ref(other: &BlobTransactionSidecar) -> &Self {

View File

@ -85,7 +85,7 @@ where
BlockId::Number(tag) => self.block_by_number(tag, false).await, BlockId::Number(tag) => self.block_by_number(tag, false).await,
}? }?
.ok_or_else(|| RpcError::Custom("block not found".to_string()))?; .ok_or_else(|| RpcError::Custom("block not found".to_string()))?;
let hashes = block.transactions.iter().map(|tx| (tx, opts.clone())).collect::<Vec<_>>(); let hashes = block.transactions.hashes().map(|tx| (*tx, opts.clone())).collect::<Vec<_>>();
let stream = futures::stream::iter(hashes.into_iter().map(move |(tx, opts)| async move { let stream = futures::stream::iter(hashes.into_iter().map(move |(tx, opts)| async move {
match self.debug_trace_transaction_json(tx, opts).await { match self.debug_trace_transaction_json(tx, opts).await {
Ok(result) => Ok((result, tx)), Ok(result) => Ok((result, tx)),

View File

@ -15,6 +15,8 @@ workspace = true
reth-primitives.workspace = true reth-primitives.workspace = true
reth-rpc-types.workspace = true reth-rpc-types.workspace = true
alloy-rlp.workspace = true alloy-rlp.workspace = true
alloy-rpc-types.workspace = true
serde_json.workspace = true
[features] [features]
optimism = ["reth-primitives/optimism", "reth-rpc-types/optimism"] optimism = ["reth-primitives/optimism", "reth-rpc-types/optimism"]

View File

@ -179,6 +179,7 @@ fn from_block_with_transactions(
total_difficulty: Some(total_difficulty), total_difficulty: Some(total_difficulty),
size: Some(U256::from(block_length)), size: Some(U256::from(block_length)),
withdrawals, withdrawals,
other: Default::default(),
} }
} }
@ -196,5 +197,6 @@ pub fn uncle_block_from_header(header: PrimitiveHeader) -> Block {
withdrawals: Some(vec![]), withdrawals: Some(vec![]),
size, size,
total_difficulty: None, total_difficulty: None,
other: Default::default(),
} }
} }

View File

@ -65,11 +65,8 @@ pub fn try_payload_v2_to_block(payload: ExecutionPayloadV2) -> Result<Block, Pay
// this performs the same conversion as the underlying V1 payload, but calculates the // this performs the same conversion as the underlying V1 payload, but calculates the
// withdrawals root and adds withdrawals // withdrawals root and adds withdrawals
let mut base_sealed_block = try_payload_v1_to_block(payload.payload_inner)?; let mut base_sealed_block = try_payload_v1_to_block(payload.payload_inner)?;
let withdrawals: Vec<_> = payload let withdrawals: Vec<_> =
.withdrawals payload.withdrawals.iter().map(|w| convert_standalone_withdraw_to_withdrawal(*w)).collect();
.iter()
.map(|w| convert_standalone_withdraw_to_withdrawal(w.clone()))
.collect();
let withdrawals_root = proofs::calculate_withdrawals_root(&withdrawals); let withdrawals_root = proofs::calculate_withdrawals_root(&withdrawals);
base_sealed_block.withdrawals = Some(withdrawals); base_sealed_block.withdrawals = Some(withdrawals);
base_sealed_block.header.withdrawals_root = Some(withdrawals_root); base_sealed_block.header.withdrawals_root = Some(withdrawals_root);

View File

@ -138,11 +138,14 @@ fn fill(
blob_versioned_hashes, blob_versioned_hashes,
// Optimism fields // Optimism fields
#[cfg(feature = "optimism")] #[cfg(feature = "optimism")]
optimism: reth_rpc_types::OptimismTransactionFields { other: reth_rpc_types::OptimismTransactionFields {
source_hash: signed_tx.source_hash(), source_hash: signed_tx.source_hash(),
mint: signed_tx.mint().map(U128::from), mint: signed_tx.mint().map(U128::from),
is_system_tx: signed_tx.is_deposit().then_some(signed_tx.is_system_transaction()), is_system_tx: signed_tx.is_deposit().then_some(signed_tx.is_system_transaction()),
}, }
.into(),
#[cfg(not(feature = "optimism"))]
other: Default::default(),
} }
} }

View File

@ -15,6 +15,7 @@ workspace = true
# ethereum # ethereum
alloy-rlp = { workspace = true, features = ["arrayvec", "derive"] } alloy-rlp = { workspace = true, features = ["arrayvec", "derive"] }
alloy-primitives = { workspace = true, features = ["rand", "rlp", "serde"] } alloy-primitives = { workspace = true, features = ["rand", "rlp", "serde"] }
alloy-rpc-types.workspace = true
ethereum_ssz_derive = { version = "0.5", optional = true } ethereum_ssz_derive = { version = "0.5", optional = true }
ethereum_ssz = { version = "0.5", optional = true } ethereum_ssz = { version = "0.5", optional = true }
@ -38,8 +39,8 @@ proptest-derive = { workspace = true, optional = true }
[features] [features]
default = ["jsonrpsee-types"] default = ["jsonrpsee-types"]
arbitrary = ["dep:arbitrary", "dep:proptest-derive", "dep:proptest", "alloy-primitives/arbitrary"] arbitrary = ["dep:arbitrary", "dep:proptest-derive", "dep:proptest", "alloy-primitives/arbitrary", "alloy-rpc-types/arbitrary"]
ssz = ["dep:ethereum_ssz" ,"dep:ethereum_ssz_derive", "alloy-primitives/ssz"] ssz = ["dep:ethereum_ssz" ,"dep:ethereum_ssz_derive", "alloy-primitives/ssz", "alloy-rpc-types/ssz"]
optimism = [] optimism = []

View File

@ -12,8 +12,8 @@
use crate::{ use crate::{
beacon::{withdrawals::BeaconWithdrawal, BlsPublicKey}, beacon::{withdrawals::BeaconWithdrawal, BlsPublicKey},
engine::ExecutionPayloadV3, engine::{ExecutionPayload, ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3},
ExecutionPayload, ExecutionPayloadV1, ExecutionPayloadV2, Withdrawal, Withdrawal,
}; };
use alloy_primitives::{Address, Bloom, Bytes, B256, U256}; use alloy_primitives::{Address, Bloom, Bytes, B256, U256};
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};

View File

@ -1,88 +0,0 @@
#![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<Bytes>,
}
/// 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<Bytes>,
pub storage_proof: Vec<EIP1186StorageProof>,
}
/// 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<String>,
}
/// 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::<EIP1186AccountProofResponse>(response).unwrap();
serde_json::to_value(val).unwrap();
}

View File

@ -1,962 +0,0 @@
//! Contains types that represent ethereum types when used in RPC
use crate::{Transaction, Withdrawal};
use alloy_primitives::{Address, BlockHash, BlockNumber, Bloom, Bytes, B256, B64, U256, U64};
use alloy_rlp::{bytes, Decodable, Encodable, Error as RlpError};
use serde::{
de::{MapAccess, Visitor},
ser::{Error, SerializeStruct},
Deserialize, Deserializer, Serialize, Serializer,
};
use std::{
collections::BTreeMap,
fmt::{self, Formatter},
num::ParseIntError,
ops::Deref,
str::FromStr,
};
/// Block Transactions depending on the boolean attribute of `eth_getBlockBy*`,
/// or if used by `eth_getUncle*`
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum BlockTransactions {
/// Only hashes
Hashes(Vec<B256>),
/// Full transactions
Full(Vec<Transaction>),
/// Special case for uncle response.
Uncle,
}
impl BlockTransactions {
/// Check if the enum variant is
/// used for an uncle response.
pub fn is_uncle(&self) -> bool {
matches!(self, Self::Uncle)
}
/// Returns an iterator over the transaction hashes.
pub fn iter(&self) -> BlockTransactionsHashIterator<'_> {
BlockTransactionsHashIterator::new(self)
}
}
/// An Iterator over the transaction hashes of a block.
#[derive(Debug, Clone)]
pub struct BlockTransactionsHashIterator<'a> {
txs: &'a BlockTransactions,
idx: usize,
}
impl<'a> BlockTransactionsHashIterator<'a> {
fn new(txs: &'a BlockTransactions) -> Self {
Self { txs, idx: 0 }
}
}
impl<'a> Iterator for BlockTransactionsHashIterator<'a> {
type Item = B256;
fn next(&mut self) -> Option<Self::Item> {
match self.txs {
BlockTransactions::Full(txs) => {
let tx = txs.get(self.idx);
self.idx += 1;
tx.map(|tx| tx.hash)
}
BlockTransactions::Hashes(txs) => {
let tx = txs.get(self.idx).copied();
self.idx += 1;
tx
}
BlockTransactions::Uncle => None,
}
}
}
/// Determines how the `transactions` field of [Block] should be filled.
///
/// This essentially represents the `full:bool` argument in RPC calls that determine whether the
/// response should include full transaction objects or just the hashes.
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum BlockTransactionsKind {
/// Only include hashes: [BlockTransactions::Hashes]
Hashes,
/// Include full transaction objects: [BlockTransactions::Full]
Full,
}
impl From<bool> for BlockTransactionsKind {
fn from(is_full: bool) -> Self {
if is_full {
BlockTransactionsKind::Full
} else {
BlockTransactionsKind::Hashes
}
}
}
/// Error that can occur when converting other types to blocks
#[derive(Debug, thiserror::Error)]
pub enum BlockError {
/// A transaction failed sender recovery
#[error("transaction failed sender recovery")]
InvalidSignature,
/// A raw block failed to decode
#[error("failed to decode raw block {0}")]
RlpDecodeRawBlock(alloy_rlp::Error),
}
/// Block representation
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Block {
/// Header of the block
#[serde(flatten)]
pub header: Header,
/// Total difficulty, this field is None only if representing
/// an Uncle block.
#[serde(skip_serializing_if = "Option::is_none")]
pub total_difficulty: Option<U256>,
/// Uncles' hashes
pub uncles: Vec<B256>,
/// Transactions
#[serde(skip_serializing_if = "BlockTransactions::is_uncle")]
pub transactions: BlockTransactions,
/// Integer the size of this block in bytes.
pub size: Option<U256>,
/// Withdrawals in the block
#[serde(default, skip_serializing_if = "Option::is_none")]
pub withdrawals: Option<Vec<Withdrawal>>,
}
/// Block header representation.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
#[serde(rename_all = "camelCase")]
pub struct Header {
/// Hash of the block
pub hash: Option<B256>,
/// Hash of the parent
pub parent_hash: B256,
/// Hash of the uncles
#[serde(rename = "sha3Uncles")]
pub uncles_hash: B256,
/// Alias of `author`
pub miner: Address,
/// State root hash
pub state_root: B256,
/// Transactions root hash
pub transactions_root: B256,
/// Transactions receipts root hash
pub receipts_root: B256,
/// Logs bloom
pub logs_bloom: Bloom,
/// Difficulty
pub difficulty: U256,
/// Block number
pub number: Option<U256>,
/// Gas Limit
pub gas_limit: U256,
/// Gas Used
pub gas_used: U256,
/// Timestamp
pub timestamp: U256,
/// Extra data
pub extra_data: Bytes,
/// Mix Hash
///
/// Before the merge this proves, combined with the nonce, that a sufficient amount of
/// computation has been carried out on this block: the Proof-of-Work (PoF).
///
/// After the merge this is `prevRandao`: Randomness value for the generated payload.
///
/// This is an Option because it is not always set by non-ethereum networks.
///
/// See also <https://eips.ethereum.org/EIPS/eip-4399>
/// And <https://github.com/ethereum/execution-apis/issues/328>
#[serde(skip_serializing_if = "Option::is_none")]
pub mix_hash: Option<B256>,
/// Nonce
pub nonce: Option<B64>,
/// Base fee per unit of gas (if past London)
#[serde(skip_serializing_if = "Option::is_none")]
pub base_fee_per_gas: Option<U256>,
/// Withdrawals root hash added by EIP-4895 and is ignored in legacy headers.
#[serde(skip_serializing_if = "Option::is_none")]
pub withdrawals_root: Option<B256>,
/// Blob gas used
#[serde(skip_serializing_if = "Option::is_none")]
pub blob_gas_used: Option<U64>,
/// Excess blob gas
#[serde(skip_serializing_if = "Option::is_none")]
pub excess_blob_gas: Option<U64>,
/// Parent beacon block root
#[serde(skip_serializing_if = "Option::is_none")]
pub parent_beacon_block_root: Option<B256>,
}
/// A block hash which may have
/// a boolean requireCanonical field.
/// If false, an RPC call should raise if a block
/// matching the hash is not found.
/// If true, an RPC call should additionaly raise if
/// the block is not in the canonical chain.
/// <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md#specification>
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize)]
pub struct RpcBlockHash {
/// A block hash
pub block_hash: B256,
/// Whether the block must be a canonical block
pub require_canonical: Option<bool>,
}
impl RpcBlockHash {
/// Returns an [RpcBlockHash] from a [B256].
pub const fn from_hash(block_hash: B256, require_canonical: Option<bool>) -> Self {
RpcBlockHash { block_hash, require_canonical }
}
}
impl From<B256> for RpcBlockHash {
fn from(value: B256) -> Self {
Self::from_hash(value, None)
}
}
impl From<RpcBlockHash> for B256 {
fn from(value: RpcBlockHash) -> Self {
value.block_hash
}
}
impl AsRef<B256> for RpcBlockHash {
fn as_ref(&self) -> &B256 {
&self.block_hash
}
}
/// A block Number (or tag - "latest", "earliest", "pending")
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub enum BlockNumberOrTag {
/// Latest block
#[default]
Latest,
/// Finalized block accepted as canonical
Finalized,
/// Safe head block
Safe,
/// Earliest block (genesis)
Earliest,
/// Pending block (not yet part of the blockchain)
Pending,
/// Block by number from canon chain
Number(u64),
}
impl BlockNumberOrTag {
/// Returns the numeric block number if explicitly set
pub const fn as_number(&self) -> Option<u64> {
match *self {
BlockNumberOrTag::Number(num) => Some(num),
_ => None,
}
}
/// Returns `true` if a numeric block number is set
pub const fn is_number(&self) -> bool {
matches!(self, BlockNumberOrTag::Number(_))
}
/// Returns `true` if it's "latest"
pub const fn is_latest(&self) -> bool {
matches!(self, BlockNumberOrTag::Latest)
}
/// Returns `true` if it's "finalized"
pub const fn is_finalized(&self) -> bool {
matches!(self, BlockNumberOrTag::Finalized)
}
/// Returns `true` if it's "safe"
pub const fn is_safe(&self) -> bool {
matches!(self, BlockNumberOrTag::Safe)
}
/// Returns `true` if it's "pending"
pub const fn is_pending(&self) -> bool {
matches!(self, BlockNumberOrTag::Pending)
}
/// Returns `true` if it's "earliest"
pub const fn is_earliest(&self) -> bool {
matches!(self, BlockNumberOrTag::Earliest)
}
}
impl From<u64> for BlockNumberOrTag {
fn from(num: u64) -> Self {
BlockNumberOrTag::Number(num)
}
}
impl From<U64> for BlockNumberOrTag {
fn from(num: U64) -> Self {
num.to::<u64>().into()
}
}
impl Serialize for BlockNumberOrTag {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match *self {
BlockNumberOrTag::Number(ref x) => serializer.serialize_str(&format!("0x{x:x}")),
BlockNumberOrTag::Latest => serializer.serialize_str("latest"),
BlockNumberOrTag::Finalized => serializer.serialize_str("finalized"),
BlockNumberOrTag::Safe => serializer.serialize_str("safe"),
BlockNumberOrTag::Earliest => serializer.serialize_str("earliest"),
BlockNumberOrTag::Pending => serializer.serialize_str("pending"),
}
}
}
impl<'de> Deserialize<'de> for BlockNumberOrTag {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?.to_lowercase();
s.parse().map_err(serde::de::Error::custom)
}
}
impl FromStr for BlockNumberOrTag {
type Err = ParseBlockNumberError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let block = match s {
"latest" => Self::Latest,
"finalized" => Self::Finalized,
"safe" => Self::Safe,
"earliest" => Self::Earliest,
"pending" => Self::Pending,
_number => {
if let Some(hex_val) = s.strip_prefix("0x") {
let number = u64::from_str_radix(hex_val, 16);
BlockNumberOrTag::Number(number?)
} else {
return Err(HexStringMissingPrefixError::default().into())
}
}
};
Ok(block)
}
}
impl fmt::Display for BlockNumberOrTag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BlockNumberOrTag::Number(ref x) => format!("0x{x:x}").fmt(f),
BlockNumberOrTag::Latest => f.write_str("latest"),
BlockNumberOrTag::Finalized => f.write_str("finalized"),
BlockNumberOrTag::Safe => f.write_str("safe"),
BlockNumberOrTag::Earliest => f.write_str("earliest"),
BlockNumberOrTag::Pending => f.write_str("pending"),
}
}
}
/// Error variants when parsing a [BlockNumberOrTag]
#[derive(Debug, thiserror::Error)]
pub enum ParseBlockNumberError {
/// Failed to parse hex value
#[error(transparent)]
ParseIntErr(#[from] ParseIntError),
/// Block numbers should be 0x-prefixed
#[error(transparent)]
MissingPrefix(#[from] HexStringMissingPrefixError),
}
/// Thrown when a 0x-prefixed hex string was expected
#[derive(Copy, Clone, Debug, Default, thiserror::Error)]
#[non_exhaustive]
#[error("hex string without 0x prefix")]
pub struct HexStringMissingPrefixError;
/// A Block Identifier
/// <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md>
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum BlockId {
/// A block hash and an optional bool that defines if it's canonical
Hash(RpcBlockHash),
/// A block number
Number(BlockNumberOrTag),
}
// === impl BlockId ===
impl BlockId {
/// Returns the block hash if it is [BlockId::Hash]
pub const fn as_block_hash(&self) -> Option<B256> {
match self {
BlockId::Hash(hash) => Some(hash.block_hash),
BlockId::Number(_) => None,
}
}
/// Returns true if this is [BlockNumberOrTag::Latest]
pub const fn is_latest(&self) -> bool {
matches!(self, BlockId::Number(BlockNumberOrTag::Latest))
}
/// Returns true if this is [BlockNumberOrTag::Pending]
pub const fn is_pending(&self) -> bool {
matches!(self, BlockId::Number(BlockNumberOrTag::Pending))
}
}
impl From<u64> for BlockId {
fn from(num: u64) -> Self {
BlockNumberOrTag::Number(num).into()
}
}
impl From<U64> for BlockId {
fn from(value: U64) -> Self {
BlockNumberOrTag::Number(value.to()).into()
}
}
impl From<BlockNumberOrTag> for BlockId {
fn from(num: BlockNumberOrTag) -> Self {
BlockId::Number(num)
}
}
impl From<B256> for BlockId {
fn from(block_hash: B256) -> Self {
BlockId::Hash(RpcBlockHash { block_hash, require_canonical: None })
}
}
impl From<(B256, Option<bool>)> for BlockId {
fn from(hash_can: (B256, Option<bool>)) -> Self {
BlockId::Hash(RpcBlockHash { block_hash: hash_can.0, require_canonical: hash_can.1 })
}
}
impl Serialize for BlockId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match *self {
BlockId::Hash(RpcBlockHash { ref block_hash, ref require_canonical }) => {
let mut s = serializer.serialize_struct("BlockIdEip1898", 1)?;
s.serialize_field("blockHash", block_hash)?;
if let Some(require_canonical) = require_canonical {
s.serialize_field("requireCanonical", require_canonical)?;
}
s.end()
}
BlockId::Number(ref num) => num.serialize(serializer),
}
}
}
impl<'de> Deserialize<'de> for BlockId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct BlockIdVisitor;
impl<'de> Visitor<'de> for BlockIdVisitor {
type Value = BlockId;
fn expecting(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter.write_str("Block identifier following EIP-1898")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
// Since there is no way to clearly distinguish between a DATA parameter and a QUANTITY parameter. A str is therefor deserialized into a Block Number: <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md>
// However, since the hex string should be a QUANTITY, we can safely assume that if the len is 66 bytes, it is in fact a hash, ref <https://github.com/ethereum/go-ethereum/blob/ee530c0d5aa70d2c00ab5691a89ab431b73f8165/rpc/types.go#L184-L184>
if v.len() == 66 {
Ok(BlockId::Hash(v.parse::<B256>().map_err(serde::de::Error::custom)?.into()))
} else {
// quantity hex string or tag
Ok(BlockId::Number(v.parse().map_err(serde::de::Error::custom)?))
}
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut number = None;
let mut block_hash = None;
let mut require_canonical = None;
while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"blockNumber" => {
if number.is_some() || block_hash.is_some() {
return Err(serde::de::Error::duplicate_field("blockNumber"))
}
if require_canonical.is_some() {
return Err(serde::de::Error::custom(
"Non-valid require_canonical field",
))
}
number = Some(map.next_value::<BlockNumberOrTag>()?)
}
"blockHash" => {
if number.is_some() || block_hash.is_some() {
return Err(serde::de::Error::duplicate_field("blockHash"))
}
block_hash = Some(map.next_value::<B256>()?);
}
"requireCanonical" => {
if number.is_some() || require_canonical.is_some() {
return Err(serde::de::Error::duplicate_field("requireCanonical"))
}
require_canonical = Some(map.next_value::<bool>()?)
}
key => {
return Err(serde::de::Error::unknown_field(
key,
&["blockNumber", "blockHash", "requireCanonical"],
))
}
}
}
if let Some(number) = number {
Ok(BlockId::Number(number))
} else if let Some(block_hash) = block_hash {
Ok(BlockId::Hash(RpcBlockHash { block_hash, require_canonical }))
} else {
Err(serde::de::Error::custom(
"Expected `blockNumber` or `blockHash` with `requireCanonical` optionally",
))
}
}
}
deserializer.deserialize_any(BlockIdVisitor)
}
}
/// Block number and hash.
#[derive(Clone, Copy, Hash, Default, PartialEq, Eq)]
pub struct BlockNumHash {
/// Block number
pub number: BlockNumber,
/// Block hash
pub hash: BlockHash,
}
/// Block number and hash of the forked block.
pub type ForkBlock = BlockNumHash;
impl std::fmt::Debug for BlockNumHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("").field(&self.number).field(&self.hash).finish()
}
}
impl BlockNumHash {
/// Creates a new `BlockNumHash` from a block number and hash.
pub fn new(number: BlockNumber, hash: BlockHash) -> Self {
Self { number, hash }
}
/// Consumes `Self` and returns [`BlockNumber`], [`BlockHash`]
pub fn into_components(self) -> (BlockNumber, BlockHash) {
(self.number, self.hash)
}
/// Returns whether or not the block matches the given [BlockHashOrNumber].
pub fn matches_block_or_num(&self, block: &BlockHashOrNumber) -> bool {
match block {
BlockHashOrNumber::Hash(hash) => self.hash == *hash,
BlockHashOrNumber::Number(number) => self.number == *number,
}
}
}
impl From<(BlockNumber, BlockHash)> for BlockNumHash {
fn from(val: (BlockNumber, BlockHash)) -> Self {
BlockNumHash { number: val.0, hash: val.1 }
}
}
impl From<(BlockHash, BlockNumber)> for BlockNumHash {
fn from(val: (BlockHash, BlockNumber)) -> Self {
BlockNumHash { hash: val.0, number: val.1 }
}
}
/// Either a block hash _or_ a block number
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(
any(test, feature = "arbitrary"),
derive(proptest_derive::Arbitrary, arbitrary::Arbitrary)
)]
pub enum BlockHashOrNumber {
/// A block hash
Hash(B256),
/// A block number
Number(u64),
}
// === impl BlockHashOrNumber ===
impl BlockHashOrNumber {
/// Returns the block number if it is a [`BlockHashOrNumber::Number`].
#[inline]
pub fn as_number(self) -> Option<u64> {
match self {
BlockHashOrNumber::Hash(_) => None,
BlockHashOrNumber::Number(num) => Some(num),
}
}
}
impl From<B256> for BlockHashOrNumber {
fn from(value: B256) -> Self {
BlockHashOrNumber::Hash(value)
}
}
impl From<u64> for BlockHashOrNumber {
fn from(value: u64) -> Self {
BlockHashOrNumber::Number(value)
}
}
impl From<U64> for BlockHashOrNumber {
fn from(value: U64) -> Self {
value.to::<u64>().into()
}
}
impl Encodable for BlockHashOrNumber {
/// RLP encodes either the block hash or block number, depending on the variant.
fn encode(&self, out: &mut dyn bytes::BufMut) {
match self {
Self::Hash(block_hash) => block_hash.encode(out),
Self::Number(block_number) => block_number.encode(out),
}
}
fn length(&self) -> usize {
match self {
Self::Hash(block_hash) => block_hash.length(),
Self::Number(block_number) => block_number.length(),
}
}
}
impl Decodable for BlockHashOrNumber {
/// RLP decodes the data into a block hash or number.
/// If the data is exactly 32 bytes and a RLP string, it will be decoded into a block hash.
/// Otherwise, this will try to decode a `u64` from the data as a block number.
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
let header: u8 = *buf.first().ok_or(RlpError::InputTooShort)?;
// if the byte string is exactly 32 bytes, decode it into a Hash
// 0xa0 = 0x80 (start of string) + 0x20 (32, length of string)
if header == 0xa0 {
// strip the first byte, parsing the rest of the string.
// If the rest of the string fails to decode into 32 bytes, we'll bubble up the
// decoding error.
let hash = B256::decode(buf)?;
Ok(Self::Hash(hash))
} else {
// a block number when encoded as bytes ranges from 0 to any number of bytes - we're
// going to accept numbers which fit in less than 64 bytes.
// Any data larger than this which is not caught by the Hash decoding should error and
// is considered an invalid block number.
Ok(Self::Number(u64::decode(buf)?))
}
}
}
/// Error thrown when parsing a [BlockHashOrNumber] from a string.
#[derive(Debug, thiserror::Error)]
#[error("failed to parse {input:?} as a number: {parse_int_error} or hash: {hex_error}")]
pub struct ParseBlockHashOrNumberError {
input: String,
parse_int_error: ParseIntError,
hex_error: alloy_primitives::hex::FromHexError,
}
impl FromStr for BlockHashOrNumber {
type Err = ParseBlockHashOrNumberError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match u64::from_str(s) {
Ok(val) => Ok(val.into()),
Err(pares_int_error) => match B256::from_str(s) {
Ok(val) => Ok(val.into()),
Err(hex_error) => Err(ParseBlockHashOrNumberError {
input: s.to_string(),
parse_int_error: pares_int_error,
hex_error,
}),
},
}
}
}
/// A Block representation that allows to include additional fields
pub type RichBlock = Rich<Block>;
impl From<Block> for RichBlock {
fn from(block: Block) -> Self {
Rich { inner: block, extra_info: Default::default() }
}
}
/// Header representation with additional info.
pub type RichHeader = Rich<Header>;
impl From<Header> for RichHeader {
fn from(header: Header) -> Self {
Rich { inner: header, extra_info: Default::default() }
}
}
/// Value representation with additional info
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct Rich<T> {
/// Standard value.
#[serde(flatten)]
pub inner: T,
/// Additional fields that should be serialized into the `Block` object
#[serde(flatten)]
pub extra_info: BTreeMap<String, serde_json::Value>,
}
impl<T> Deref for Rich<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T: Serialize> Serialize for Rich<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if self.extra_info.is_empty() {
return self.inner.serialize(serializer)
}
let inner = serde_json::to_value(&self.inner);
let extras = serde_json::to_value(&self.extra_info);
if let (Ok(serde_json::Value::Object(mut value)), Ok(serde_json::Value::Object(extras))) =
(inner, extras)
{
value.extend(extras);
value.serialize(serializer)
} else {
Err(S::Error::custom("Unserializable structures: expected objects"))
}
}
}
/// BlockOverrides is a set of header fields to override.
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase", deny_unknown_fields)]
pub struct BlockOverrides {
/// Overrides the block number.
///
/// For `eth_callMany` this will be the block number of the first simulated block. Each
/// following block increments its block number by 1
// Note: geth uses `number`, erigon uses `blockNumber`
#[serde(default, skip_serializing_if = "Option::is_none", alias = "blockNumber")]
pub number: Option<U256>,
/// Overrides the difficulty of the block.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub difficulty: Option<U256>,
/// Overrides the timestamp of the block.
// Note: geth uses `time`, erigon uses `timestamp`
#[serde(default, skip_serializing_if = "Option::is_none", alias = "timestamp")]
pub time: Option<U64>,
/// Overrides the gas limit of the block.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub gas_limit: Option<U64>,
/// Overrides the coinbase address of the block.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub coinbase: Option<Address>,
/// Overrides the prevrandao of the block.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub random: Option<B256>,
/// Overrides the basefee of the block.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub base_fee: Option<U256>,
/// A dictionary that maps blockNumber to a user-defined hash. It could be queried from the
/// solidity opcode BLOCKHASH.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub block_hash: Option<BTreeMap<u64, B256>>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_full_conversion() {
let full = true;
assert_eq!(BlockTransactionsKind::Full, full.into());
let full = false;
assert_eq!(BlockTransactionsKind::Hashes, full.into());
}
#[test]
#[cfg(feature = "jsonrpsee-types")]
fn serde_json_header() {
use jsonrpsee_types::SubscriptionResponse;
let resp = r#"{"jsonrpc":"2.0","method":"eth_subscribe","params":{"subscription":"0x7eef37ff35d471f8825b1c8f67a5d3c0","result":{"hash":"0x7a7ada12e140961a32395059597764416499f4178daf1917193fad7bd2cc6386","parentHash":"0xdedbd831f496e705e7f2ec3c8dcb79051040a360bf1455dbd7eb8ea6ad03b751","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","number":"0x8","gasUsed":"0x0","gasLimit":"0x1c9c380","extraData":"0x","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x642aa48f","difficulty":"0x0","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000"}}}"#;
let _header: SubscriptionResponse<'_, Header> = serde_json::from_str(resp).unwrap();
let resp = r#"{"jsonrpc":"2.0","method":"eth_subscription","params":{"subscription":"0x1a14b6bdcf4542fabf71c4abee244e47","result":{"author":"0x000000568b9b5a365eaa767d42e74ed88915c204","difficulty":"0x1","extraData":"0x4e65746865726d696e6420312e392e32322d302d6463373666616366612d32308639ad8ff3d850a261f3b26bc2a55e0f3a718de0dd040a19a4ce37e7b473f2d7481448a1e1fd8fb69260825377c0478393e6055f471a5cf839467ce919a6ad2700","gasLimit":"0x7a1200","gasUsed":"0x0","hash":"0xa4856602944fdfd18c528ef93cc52a681b38d766a7e39c27a47488c8461adcb0","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","number":"0x434822","parentHash":"0x1a9bdc31fc785f8a95efeeb7ae58f40f6366b8e805f47447a52335c95f4ceb49","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x261","stateRoot":"0xf38c4bf2958e541ec6df148e54ce073dc6b610f8613147ede568cb7b5c2d81ee","totalDifficulty":"0x633ebd","timestamp":"0x604726b0","transactions":[],"transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","uncles":[]}}}"#;
let _header: SubscriptionResponse<'_, Header> = serde_json::from_str(resp).unwrap();
}
#[test]
fn serde_block() {
let block = Block {
header: Header {
hash: Some(B256::with_last_byte(1)),
parent_hash: B256::with_last_byte(2),
uncles_hash: B256::with_last_byte(3),
miner: Address::with_last_byte(4),
state_root: B256::with_last_byte(5),
transactions_root: B256::with_last_byte(6),
receipts_root: B256::with_last_byte(7),
withdrawals_root: Some(B256::with_last_byte(8)),
number: Some(U256::from(9)),
gas_used: U256::from(10),
gas_limit: U256::from(11),
extra_data: Bytes::from(vec![1, 2, 3]),
logs_bloom: Bloom::default(),
timestamp: U256::from(12),
difficulty: U256::from(13),
mix_hash: Some(B256::with_last_byte(14)),
nonce: Some(B64::with_last_byte(15)),
base_fee_per_gas: Some(U256::from(20)),
blob_gas_used: None,
excess_blob_gas: None,
parent_beacon_block_root: None,
},
total_difficulty: Some(U256::from(100000)),
uncles: vec![B256::with_last_byte(17)],
transactions: BlockTransactions::Hashes(vec![B256::with_last_byte(18)]),
size: Some(U256::from(19)),
withdrawals: Some(vec![]),
};
let serialized = serde_json::to_string(&block).unwrap();
assert_eq!(
serialized,
r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000002","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000003","miner":"0x0000000000000000000000000000000000000004","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000005","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000006","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000007","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0xd","number":"0x9","gasLimit":"0xb","gasUsed":"0xa","timestamp":"0xc","extraData":"0x010203","mixHash":"0x000000000000000000000000000000000000000000000000000000000000000e","nonce":"0x000000000000000f","baseFeePerGas":"0x14","withdrawalsRoot":"0x0000000000000000000000000000000000000000000000000000000000000008","totalDifficulty":"0x186a0","uncles":["0x0000000000000000000000000000000000000000000000000000000000000011"],"transactions":["0x0000000000000000000000000000000000000000000000000000000000000012"],"size":"0x13","withdrawals":[]}"#
);
let deserialized: Block = serde_json::from_str(&serialized).unwrap();
assert_eq!(block, deserialized);
}
#[test]
fn serde_block_with_withdrawals_set_as_none() {
let block = Block {
header: Header {
hash: Some(B256::with_last_byte(1)),
parent_hash: B256::with_last_byte(2),
uncles_hash: B256::with_last_byte(3),
miner: Address::with_last_byte(4),
state_root: B256::with_last_byte(5),
transactions_root: B256::with_last_byte(6),
receipts_root: B256::with_last_byte(7),
withdrawals_root: None,
number: Some(U256::from(9)),
gas_used: U256::from(10),
gas_limit: U256::from(11),
extra_data: Bytes::from(vec![1, 2, 3]),
logs_bloom: Bloom::default(),
timestamp: U256::from(12),
difficulty: U256::from(13),
mix_hash: Some(B256::with_last_byte(14)),
nonce: Some(B64::with_last_byte(15)),
base_fee_per_gas: Some(U256::from(20)),
blob_gas_used: None,
excess_blob_gas: None,
parent_beacon_block_root: None,
},
total_difficulty: Some(U256::from(100000)),
uncles: vec![B256::with_last_byte(17)],
transactions: BlockTransactions::Hashes(vec![B256::with_last_byte(18)]),
size: Some(U256::from(19)),
withdrawals: None,
};
let serialized = serde_json::to_string(&block).unwrap();
assert_eq!(
serialized,
r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000002","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000003","miner":"0x0000000000000000000000000000000000000004","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000005","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000006","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000007","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0xd","number":"0x9","gasLimit":"0xb","gasUsed":"0xa","timestamp":"0xc","extraData":"0x010203","mixHash":"0x000000000000000000000000000000000000000000000000000000000000000e","nonce":"0x000000000000000f","baseFeePerGas":"0x14","totalDifficulty":"0x186a0","uncles":["0x0000000000000000000000000000000000000000000000000000000000000011"],"transactions":["0x0000000000000000000000000000000000000000000000000000000000000012"],"size":"0x13"}"#
);
let deserialized: Block = serde_json::from_str(&serialized).unwrap();
assert_eq!(block, deserialized);
}
#[test]
fn block_overrides() {
let s = r#"{"blockNumber": "0xe39dd0"}"#;
let _overrides = serde_json::from_str::<BlockOverrides>(s).unwrap();
}
#[test]
fn serde_rich_block() {
let s = r#"{
"hash": "0xb25d0e54ca0104e3ebfb5a1dcdf9528140854d609886a300946fd6750dcb19f4",
"parentHash": "0x9400ec9ef59689c157ac89eeed906f15ddd768f94e1575e0e27d37c241439a5d",
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"miner": "0x829bd824b016326a401d083b33d092293333a830",
"stateRoot": "0x546e330050c66d02923e7f1f3e925efaf64e4384eeecf2288f40088714a77a84",
"transactionsRoot": "0xd5eb3ad6d7c7a4798cc5fb14a6820073f44a941107c5d79dac60bd16325631fe",
"receiptsRoot": "0xb21c41cbb3439c5af25304e1405524c885e733b16203221900cb7f4b387b62f0",
"logsBloom": "0x1f304e641097eafae088627298685d20202004a4a59e4d8900914724e2402b028c9d596660581f361240816e82d00fa14250c9ca89840887a381efa600288283d170010ab0b2a0694c81842c2482457e0eb77c2c02554614007f42aaf3b4dc15d006a83522c86a240c06d241013258d90540c3008888d576a02c10120808520a2221110f4805200302624d22092b2c0e94e849b1e1aa80bc4cc3206f00b249d0a603ee4310216850e47c8997a20aa81fe95040a49ca5a420464600e008351d161dc00d620970b6a801535c218d0b4116099292000c08001943a225d6485528828110645b8244625a182c1a88a41087e6d039b000a180d04300d0680700a15794",
"difficulty": "0xc40faff9c737d",
"number": "0xa9a230",
"gasLimit": "0xbe5a66",
"gasUsed": "0xbe0fcc",
"timestamp": "0x5f93b749",
"extraData": "0x7070796520e4b883e5bda9e7a59ee4bb99e9b1bc0103",
"mixHash": "0xd5e2b7b71fbe4ddfe552fb2377bf7cddb16bbb7e185806036cee86994c6e97fc",
"nonce": "0x4722f2acd35abe0f",
"totalDifficulty": "0x3dc957fd8167fb2684a",
"uncles": [],
"transactions": [
"0xf435a26acc2a9ef73ac0b73632e32e29bd0e28d5c4f46a7e18ed545c93315916"
],
"size": "0xaeb6"
}"#;
let block = serde_json::from_str::<RichBlock>(s).unwrap();
let serialized = serde_json::to_string(&block).unwrap();
let block2 = serde_json::from_str::<RichBlock>(&serialized).unwrap();
assert_eq!(block, block2);
}
#[test]
fn compact_block_number_serde() {
let num: BlockNumberOrTag = 1u64.into();
let serialized = serde_json::to_string(&num).unwrap();
assert_eq!(serialized, "\"0x1\"");
}
}

View File

@ -1,274 +0,0 @@
use crate::{AccessList, BlockId, BlockOverrides};
use alloy_primitives::{Address, Bytes, B256, U256, U64, U8};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
/// Bundle of transactions
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct Bundle {
/// All transactions to execute
pub transactions: Vec<CallRequest>,
/// Block overrides to apply
pub block_override: Option<BlockOverrides>,
}
/// State context for callMany
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct StateContext {
/// Block Number
pub block_number: Option<BlockId>,
/// Inclusive number of tx to replay in block. -1 means replay all
pub transaction_index: Option<TransactionIndex>,
}
/// CallResponse for eth_callMany
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct EthCallResponse {
/// eth_call output (if no error)
#[serde(skip_serializing_if = "Option::is_none")]
pub value: Option<Bytes>,
/// eth_call output (if error)
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
impl EthCallResponse {
/// Returns the value if present, otherwise returns the error.
pub fn ensure_ok(self) -> Result<Bytes, String> {
match self.value {
Some(output) => Ok(output),
None => Err(self.error.unwrap_or_else(|| "Unknown error".to_string())),
}
}
}
/// Represents a transaction index where -1 means all transactions
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub enum TransactionIndex {
/// -1 means all transactions
#[default]
All,
/// Transaction index
Index(usize),
}
impl TransactionIndex {
/// Returns true if this is the all variant
pub fn is_all(&self) -> bool {
matches!(self, TransactionIndex::All)
}
/// Returns the index if this is the index variant
pub fn index(&self) -> Option<usize> {
match self {
TransactionIndex::All => None,
TransactionIndex::Index(idx) => Some(*idx),
}
}
}
impl Serialize for TransactionIndex {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
TransactionIndex::All => serializer.serialize_i8(-1),
TransactionIndex::Index(idx) => idx.serialize(serializer),
}
}
}
impl<'de> Deserialize<'de> for TransactionIndex {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
match isize::deserialize(deserializer)? {
-1 => Ok(TransactionIndex::All),
idx if idx < -1 => Err(serde::de::Error::custom(format!(
"Invalid transaction index, expected -1 or positive integer, got {}",
idx
))),
idx => Ok(TransactionIndex::Index(idx as usize)),
}
}
}
/// Call request for `eth_call` and adjacent methods.
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct CallRequest {
/// From
pub from: Option<Address>,
/// To
pub to: Option<Address>,
/// Gas Price
pub gas_price: Option<U256>,
/// EIP-1559 Max base fee the caller is willing to pay
pub max_fee_per_gas: Option<U256>,
/// EIP-1559 Priority fee the caller is paying to the block author
pub max_priority_fee_per_gas: Option<U256>,
/// Gas
pub gas: Option<U256>,
/// Value
pub value: Option<U256>,
/// Transaction input data
#[serde(default, flatten)]
pub input: CallInput,
/// Nonce
pub nonce: Option<U64>,
/// chain id
pub chain_id: Option<U64>,
/// AccessList
pub access_list: Option<AccessList>,
/// Max Fee per Blob gas for EIP-4844 transactions
#[serde(skip_serializing_if = "Option::is_none")]
pub max_fee_per_blob_gas: Option<U256>,
/// Blob Versioned Hashes for EIP-4844 transactions
#[serde(skip_serializing_if = "Option::is_none")]
pub blob_versioned_hashes: Option<Vec<B256>>,
/// EIP-2718 type
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub transaction_type: Option<U8>,
}
impl CallRequest {
/// Returns the configured fee cap, if any.
///
/// The returns `gas_price` (legacy) if set or `max_fee_per_gas` (EIP1559)
#[inline]
pub fn fee_cap(&self) -> Option<U256> {
self.gas_price.or(self.max_fee_per_gas)
}
/// Returns true if the request has a `blobVersionedHashes` field but it is empty.
#[inline]
pub fn has_empty_blob_hashes(&self) -> bool {
self.blob_versioned_hashes.as_ref().map(|blobs| blobs.is_empty()).unwrap_or(false)
}
}
/// Helper type that supports both `data` and `input` fields that map to transaction input data.
///
/// This is done for compatibility reasons where older implementations used `data` instead of the
/// newer, recommended `input` field.
///
/// If both fields are set, it is expected that they contain the same value, otherwise an error is
/// returned.
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
pub struct CallInput {
/// Transaction data
#[serde(skip_serializing_if = "Option::is_none")]
pub input: Option<Bytes>,
/// Transaction data
///
/// This is the same as `input` but is used for backwards compatibility: <https://github.com/ethereum/go-ethereum/issues/15628>
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Bytes>,
}
impl CallInput {
/// Creates a new instance with the given input data.
pub fn new(data: Bytes) -> Self {
Self::maybe_input(Some(data))
}
/// Creates a new instance with the given input data.
pub fn maybe_input(input: Option<Bytes>) -> Self {
Self { input, data: None }
}
/// Consumes the type and returns the optional input data.
///
/// Returns an error if both `data` and `input` fields are set and not equal.
pub fn try_into_unique_input(self) -> Result<Option<Bytes>, CallInputError> {
let Self { input, data } = self;
match (input, data) {
(Some(input), Some(data)) if input == data => Ok(Some(input)),
(Some(_), Some(_)) => Err(CallInputError::default()),
(Some(input), None) => Ok(Some(input)),
(None, Some(data)) => Ok(Some(data)),
(None, None) => Ok(None),
}
}
/// Consumes the type and returns the optional input data.
///
/// Returns an error if both `data` and `input` fields are set and not equal.
pub fn unique_input(&self) -> Result<Option<&Bytes>, CallInputError> {
let Self { input, data } = self;
match (input, data) {
(Some(input), Some(data)) if input == data => Ok(Some(input)),
(Some(_), Some(_)) => Err(CallInputError::default()),
(Some(input), None) => Ok(Some(input)),
(None, Some(data)) => Ok(Some(data)),
(None, None) => Ok(None),
}
}
}
impl From<Bytes> for CallInput {
fn from(input: Bytes) -> Self {
Self { input: Some(input), data: None }
}
}
impl From<Option<Bytes>> for CallInput {
fn from(input: Option<Bytes>) -> Self {
Self { input, data: None }
}
}
/// Error thrown when both `data` and `input` fields are set and not equal.
#[derive(Debug, Default, thiserror::Error)]
#[error("both \"data\" and \"input\" are set and not equal. Please use \"input\" to pass transaction call data")]
#[non_exhaustive]
pub struct CallInputError;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn transaction_index() {
let s = "-1";
let idx = serde_json::from_str::<TransactionIndex>(s).unwrap();
assert_eq!(idx, TransactionIndex::All);
let s = "5";
let idx = serde_json::from_str::<TransactionIndex>(s).unwrap();
assert_eq!(idx, TransactionIndex::Index(5));
let s = "-2";
let res = serde_json::from_str::<TransactionIndex>(s);
assert!(res.is_err());
}
#[test]
fn serde_call_request() {
let s = r#"{"accessList":[],"data":"0x0902f1ac","to":"0xa478c2975ab1ea89e8196811f51a7b7ade33eb11","type":"0x02"}"#;
let _req = serde_json::from_str::<CallRequest>(s).unwrap();
}
#[test]
fn serde_unique_call_input() {
let s = r#"{"accessList":[],"data":"0x0902f1ac", "input":"0x0902f1ac","to":"0xa478c2975ab1ea89e8196811f51a7b7ade33eb11","type":"0x02"}"#;
let req = serde_json::from_str::<CallRequest>(s).unwrap();
assert!(req.input.try_into_unique_input().unwrap().is_some());
let s = r#"{"accessList":[],"data":"0x0902f1ac","to":"0xa478c2975ab1ea89e8196811f51a7b7ade33eb11","type":"0x02"}"#;
let req = serde_json::from_str::<CallRequest>(s).unwrap();
assert!(req.input.try_into_unique_input().unwrap().is_some());
let s = r#"{"accessList":[],"input":"0x0902f1ac","to":"0xa478c2975ab1ea89e8196811f51a7b7ade33eb11","type":"0x02"}"#;
let req = serde_json::from_str::<CallRequest>(s).unwrap();
assert!(req.input.try_into_unique_input().unwrap().is_some());
let s = r#"{"accessList":[],"data":"0x0902f1ac", "input":"0x0902f1","to":"0xa478c2975ab1ea89e8196811f51a7b7ade33eb11","type":"0x02"}"#;
let req = serde_json::from_str::<CallRequest>(s).unwrap();
assert!(req.input.try_into_unique_input().is_err());
}
}

View File

@ -1,32 +0,0 @@
//! Commonly used errors for the `eth_` namespace.
/// List of JSON-RPC error codes
#[derive(Debug, Copy, PartialEq, Eq, Clone)]
pub enum EthRpcErrorCode {
/// Failed to send transaction, See also <https://github.com/MetaMask/eth-rpc-errors/blob/main/src/error-constants.ts>
TransactionRejected,
/// Custom geth error code, <https://github.com/vapory-legacy/wiki/blob/master/JSON-RPC-Error-Codes-Improvement-Proposal.md>
ExecutionError,
/// <https://eips.ethereum.org/EIPS/eip-1898>
InvalidInput,
/// Thrown when a block wasn't found <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md>
/// > If the block is not found, the callee SHOULD raise a JSON-RPC error (the recommended
/// > error code is -32001: Resource not found).
ResourceNotFound,
/// Thrown when querying for `finalized` or `safe` block before the merge transition is
/// finalized, <https://github.com/ethereum/execution-apis/blob/6d17705a875e52c26826124c2a8a15ed542aeca2/src/schemas/block.yaml#L109>
UnknownBlock,
}
impl EthRpcErrorCode {
/// Returns the error code as `i32`
pub const fn code(&self) -> i32 {
match *self {
EthRpcErrorCode::TransactionRejected => -32003,
EthRpcErrorCode::ExecutionError => 3,
EthRpcErrorCode::InvalidInput => -32000,
EthRpcErrorCode::ResourceNotFound => -32001,
EthRpcErrorCode::UnknownBlock => -39001,
}
}
}

View File

@ -1,55 +0,0 @@
use alloy_primitives::U256;
use serde::{Deserialize, Serialize};
/// Internal struct to calculate reward percentiles
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TxGasAndReward {
/// Gas used by the transaction
pub gas_used: u64,
/// The effective gas tip by the transaction
pub reward: u128,
}
impl PartialOrd for TxGasAndReward {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for TxGasAndReward {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
// compare only the reward
// see:
// <https://github.com/ethereum/go-ethereum/blob/ee8e83fa5f6cb261dad2ed0a7bbcde4930c41e6c/eth/gasprice/feehistory.go#L85>
self.reward.cmp(&other.reward)
}
}
/// Response type for `eth_feeHistory`
#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FeeHistory {
/// An array of block base fees per gas.
/// This includes the next block after the newest of the returned range,
/// because this value can be derived from the newest block. Zeroes are
/// returned for pre-EIP-1559 blocks.
///
/// # Note
///
/// Empty list is skipped only for compatibility with Erigon and Geth.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub base_fee_per_gas: Vec<U256>,
/// An array of block gas used ratios. These are calculated as the ratio
/// of `gasUsed` and `gasLimit`.
///
/// # Note
///
/// The `Option` is only for compatability with Erigon and Geth.
pub gas_used_ratio: Vec<f64>,
/// Lowest number block of the returned range.
pub oldest_block: U256,
/// An (optional) array of effective priority fee per gas data points from a single
/// block. All zeroes are returned if the block is empty.
#[serde(skip_serializing_if = "Option::is_none")]
pub reward: Option<Vec<Vec<U256>>>,
}

File diff suppressed because it is too large Load Diff

View File

@ -1,104 +0,0 @@
use alloy_primitives::U256;
use serde::{
de::{Error, Visitor},
Deserialize, Deserializer, Serialize, Serializer,
};
use std::fmt;
/// A hex encoded or decimal index that's intended to be used as a rust index, hence it's
/// deserialized into a `usize`.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Default)]
pub struct Index(usize);
impl From<Index> for usize {
fn from(idx: Index) -> Self {
idx.0
}
}
impl From<Index> for U256 {
fn from(idx: Index) -> Self {
U256::from(idx.0)
}
}
impl From<usize> for Index {
fn from(idx: usize) -> Self {
Index(idx)
}
}
impl Serialize for Index {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&format!("0x{:x}", self.0))
}
}
impl<'a> Deserialize<'a> for Index {
fn deserialize<D>(deserializer: D) -> Result<Index, D::Error>
where
D: Deserializer<'a>,
{
struct IndexVisitor;
impl<'a> Visitor<'a> for IndexVisitor {
type Value = Index;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "hex-encoded or decimal index")
}
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: Error,
{
Ok(Index(value as usize))
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: Error,
{
if let Some(val) = value.strip_prefix("0x") {
usize::from_str_radix(val, 16).map(Index).map_err(|e| {
Error::custom(format!("Failed to parse hex encoded index value: {e}"))
})
} else {
value
.parse::<usize>()
.map(Index)
.map_err(|e| Error::custom(format!("Failed to parse numeric index: {e}")))
}
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
where
E: Error,
{
self.visit_str(value.as_ref())
}
}
deserializer.deserialize_any(IndexVisitor)
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::{thread_rng, Rng};
#[test]
fn test_serde_index_rand() {
let mut rng = thread_rng();
for _ in 0..100 {
let index = Index(rng.gen());
let val = serde_json::to_string(&index).unwrap();
let de: Index = serde_json::from_str(&val).unwrap();
assert_eq!(index, de);
}
}
}

View File

@ -1,55 +0,0 @@
use alloy_primitives::{Address, Bytes, B256, U256};
use serde::{Deserialize, Serialize};
/// Ethereum Log emitted by a transaction
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Log {
/// Address
pub address: Address,
/// All topics of the log
pub topics: Vec<B256>,
/// Additional data fields of the log
pub data: Bytes,
/// Hash of the block the transaction that emitted this log was mined in
pub block_hash: Option<B256>,
/// Number of the block the transaction that emitted this log was mined in
pub block_number: Option<U256>,
/// Transaction Hash
pub transaction_hash: Option<B256>,
/// Index of the Transaction in the block
pub transaction_index: Option<U256>,
/// Log Index in Block
pub log_index: Option<U256>,
/// Geth Compatibility Field: whether this log was removed
#[serde(default)]
pub removed: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serde_log() {
let log = Log {
address: Address::with_last_byte(0x69),
topics: vec![B256::with_last_byte(0x69)],
data: Bytes::from_static(&[0x69]),
block_hash: Some(B256::with_last_byte(0x69)),
block_number: Some(U256::from(0x69)),
transaction_hash: Some(B256::with_last_byte(0x69)),
transaction_index: Some(U256::from(0x69)),
log_index: Some(U256::from(0x69)),
removed: false,
};
let serialized = serde_json::to_string(&log).unwrap();
assert_eq!(
serialized,
r#"{"address":"0x0000000000000000000000000000000000000069","topics":["0x0000000000000000000000000000000000000000000000000000000000000069"],"data":"0x69","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000069","blockNumber":"0x69","transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000069","transactionIndex":"0x69","logIndex":"0x69","removed":false}"#
);
let deserialized: Log = serde_json::from_str(&serialized).unwrap();
assert_eq!(log, deserialized);
}
}

View File

@ -1,34 +1,4 @@
//! Ethereum related types //! Ethereum related types
mod account;
mod block;
mod call;
pub mod engine; pub mod engine;
pub mod error;
mod fee;
mod filter;
mod index;
mod log;
pub mod pubsub;
pub mod raw_log;
pub mod state;
mod syncing;
pub mod trace;
pub mod transaction; pub mod transaction;
pub mod txpool;
pub mod withdrawal;
mod work;
pub use account::*;
pub use block::*;
pub use call::{Bundle, CallInput, CallInputError, CallRequest, EthCallResponse, StateContext};
pub use engine::{ExecutionPayload, ExecutionPayloadV1, ExecutionPayloadV2, PayloadError};
pub use fee::{FeeHistory, TxGasAndReward};
pub use filter::*;
pub use index::Index;
pub use log::Log;
pub use raw_log::{logs_bloom, Log as RawLog};
pub use syncing::*;
pub use transaction::*;
pub use withdrawal::Withdrawal;
pub use work::Work;

View File

@ -1,169 +0,0 @@
//! Ethereum types for pub-sub
use crate::{
eth::{Filter, Transaction},
Log, RichHeader,
};
use alloy_primitives::B256;
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
/// Subscription result.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(untagged)]
pub enum SubscriptionResult {
/// New block header.
Header(Box<RichHeader>),
/// Log
Log(Box<Log>),
/// Transaction hash
TransactionHash(B256),
/// Full Transaction
FullTransaction(Box<Transaction>),
/// SyncStatus
SyncState(PubSubSyncStatus),
}
/// Response type for a SyncStatus subscription
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum PubSubSyncStatus {
/// If not currently syncing, this should always be `false`
Simple(bool),
/// Current Stats about syncing
Detailed(SyncStatusMetadata),
}
/// Sync status infos
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[allow(missing_docs)]
pub struct SyncStatusMetadata {
pub syncing: bool,
pub starting_block: u64,
pub current_block: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub highest_block: Option<u64>,
}
impl Serialize for SubscriptionResult {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match *self {
SubscriptionResult::Header(ref header) => header.serialize(serializer),
SubscriptionResult::Log(ref log) => log.serialize(serializer),
SubscriptionResult::TransactionHash(ref hash) => hash.serialize(serializer),
SubscriptionResult::FullTransaction(ref tx) => tx.serialize(serializer),
SubscriptionResult::SyncState(ref sync) => sync.serialize(serializer),
}
}
}
/// Subscription kind.
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
pub enum SubscriptionKind {
/// New block headers subscription.
///
/// Fires a notification each time a new header is appended to the chain, including chain
/// reorganizations. In case of a chain reorganization the subscription will emit all new
/// headers for the new chain. Therefore the subscription can emit multiple headers on the same
/// height.
NewHeads,
/// Logs subscription.
///
/// Returns logs that are included in new imported blocks and match the given filter criteria.
/// In case of a chain reorganization previous sent logs that are on the old chain will be
/// resent with the removed property set to true. Logs from transactions that ended up in the
/// new chain are emitted. Therefore, a subscription can emit logs for the same transaction
/// multiple times.
Logs,
/// New Pending Transactions subscription.
///
/// Returns the hash or full tx for all transactions that are added to the pending state and
/// are signed with a key that is available in the node. When a transaction that was
/// previously part of the canonical chain isn't part of the new canonical chain after a
/// reorganization its again emitted.
NewPendingTransactions,
/// Node syncing status subscription.
///
/// Indicates when the node starts or stops synchronizing. The result can either be a boolean
/// indicating that the synchronization has started (true), finished (false) or an object with
/// various progress indicators.
Syncing,
}
/// Any additional parameters for a subscription.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum Params {
/// No parameters passed.
#[default]
None,
/// Log parameters.
Logs(Box<Filter>),
/// Boolean parameter for new pending transactions.
Bool(bool),
}
impl Params {
/// Returns true if it's a bool parameter.
#[inline]
pub fn is_bool(&self) -> bool {
matches!(self, Params::Bool(_))
}
/// Returns true if it's a log parameter.
#[inline]
pub fn is_logs(&self) -> bool {
matches!(self, Params::Logs(_))
}
}
impl Serialize for Params {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Params::None => (&[] as &[serde_json::Value]).serialize(serializer),
Params::Logs(logs) => logs.serialize(serializer),
Params::Bool(full) => full.serialize(serializer),
}
}
}
impl<'a> Deserialize<'a> for Params {
fn deserialize<D>(deserializer: D) -> Result<Params, D::Error>
where
D: Deserializer<'a>,
{
let v = serde_json::Value::deserialize(deserializer)?;
if v.is_null() {
return Ok(Params::None)
}
if let Some(val) = v.as_bool() {
return Ok(Params::Bool(val))
}
serde_json::from_value(v)
.map(|f| Params::Logs(Box::new(f)))
.map_err(|e| D::Error::custom(format!("Invalid Pub-Sub parameters: {e}")))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn params_serde() {
let s: Params = serde_json::from_str("true").unwrap();
assert_eq!(s, Params::Bool(true));
let s: Params = serde_json::from_str("null").unwrap();
assert_eq!(s, Params::None);
}
}

View File

@ -1,30 +0,0 @@
//! Ethereum log object.
use alloy_primitives::{Address, Bloom, Bytes, B256};
use alloy_rlp::{RlpDecodable, RlpEncodable};
/// Ethereum Log
#[derive(Clone, Debug, PartialEq, Eq, RlpDecodable, RlpEncodable, Default)]
pub struct Log {
/// Contract that emitted this log.
pub address: Address,
/// Topics of the log. The number of logs depend on what `LOG` opcode is used.
pub topics: Vec<B256>,
/// Arbitrary length data.
pub data: Bytes,
}
/// Calculate receipt logs bloom.
pub fn logs_bloom<'a, It>(logs: It) -> Bloom
where
It: IntoIterator<Item = &'a Log>,
{
let mut bloom = Bloom::ZERO;
for log in logs {
bloom.m3_2048(log.address.as_slice());
for topic in &log.topics {
bloom.m3_2048(topic.as_slice());
}
}
bloom
}

View File

@ -1,69 +0,0 @@
//! bindings for state overrides in eth_call
use alloy_primitives::{Address, Bytes, B256, U256, U64};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// A set of account overrides
pub type StateOverride = HashMap<Address, AccountOverride>;
/// Custom account override used in call
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase", deny_unknown_fields)]
#[allow(missing_docs)]
pub struct AccountOverride {
/// Fake balance to set for the account before executing the call.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub balance: Option<U256>,
/// Fake nonce to set for the account before executing the call.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub nonce: Option<U64>,
/// Fake EVM bytecode to inject into the account before executing the call.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub code: Option<Bytes>,
/// Fake key-value mapping to override all slots in the account storage before executing the
/// call.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub state: Option<HashMap<B256, U256>>,
/// Fake key-value mapping to override individual slots in the account storage before executing
/// the call.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub state_diff: Option<HashMap<B256, U256>>,
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::address;
#[test]
fn test_state_override() {
let s = r#"{
"0x0000000000000000000000000000000000000124": {
"code": "0x6080604052348015600e575f80fd5b50600436106026575f3560e01c80632096525514602a575b5f80fd5b60306044565b604051901515815260200160405180910390f35b5f604e600242605e565b5f0360595750600190565b505f90565b5f82607757634e487b7160e01b5f52601260045260245ffd5b50069056fea2646970667358221220287f77a4262e88659e3fb402138d2ee6a7ff9ba86bae487a95aa28156367d09c64736f6c63430008140033"
}
}"#;
let state_override: StateOverride = serde_json::from_str(s).unwrap();
let acc =
state_override.get(&address!("0000000000000000000000000000000000000124")).unwrap();
assert!(acc.code.is_some());
}
#[test]
fn test_state_override_state_diff() {
let s = r#"{
"0x1b5212AF6b76113afD94cD2B5a78a73B7d7A8222": {
"balance": "0x39726378b58c400000",
"stateDiff": {}
},
"0xdAC17F958D2ee523a2206206994597C13D831ec7": {
"stateDiff": {
"0xede27e4e7f3676edbf125879f17a896d6507958df3d57bda6219f1880cae8a41": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
}
}
}"#;
let state_override: StateOverride = serde_json::from_str(s).unwrap();
let acc =
state_override.get(&address!("1b5212AF6b76113afD94cD2B5a78a73B7d7A8222")).unwrap();
assert!(acc.state_diff.is_some());
}
}

View File

@ -1,161 +0,0 @@
use alloy_primitives::{B512, U256, U64};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::BTreeMap;
/// Syncing info
#[derive(Debug, Clone, Default, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SyncInfo {
/// Starting block
pub starting_block: U256,
/// Current block
pub current_block: U256,
/// Highest block seen so far
pub highest_block: U256,
/// Warp sync snapshot chunks total.
pub warp_chunks_amount: Option<U256>,
/// Warp sync snapshot chunks processed.
pub warp_chunks_processed: Option<U256>,
}
/// Peers info
#[derive(Debug, Clone, Default, Serialize)]
pub struct Peers {
/// Number of active peers
pub active: usize,
/// Number of connected peers
pub connected: usize,
/// Max number of peers
pub max: u32,
/// Detailed information on peers
pub peers: Vec<PeerInfo>,
}
/// Number of peers connected to.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum PeerCount {
/// Peer count as integer
Number(u32),
/// Peer count as hex
Hex(U64),
}
/// Peer connection information
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PeerInfo {
/// Public node id
pub id: Option<String>,
/// Node client ID
pub name: String,
/// Capabilities
pub caps: Vec<String>,
/// Network information
pub network: PeerNetworkInfo,
/// Protocols information
pub protocols: PeerProtocolsInfo,
}
/// Peer network information
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PeerNetworkInfo {
/// Remote endpoint address
pub remote_address: String,
/// Local endpoint address
pub local_address: String,
}
/// Peer protocols information
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PeerProtocolsInfo {
/// Ethereum protocol information
pub eth: Option<PeerEthProtocolInfo>,
/// PIP protocol information.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pip: Option<PipProtocolInfo>,
}
/// Peer Ethereum protocol information
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PeerEthProtocolInfo {
/// Negotiated ethereum protocol version
pub version: u32,
/// Peer total difficulty if known
pub difficulty: Option<U256>,
/// SHA3 of peer best block hash
pub head: String,
}
/// Peer PIP protocol information
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PipProtocolInfo {
/// Negotiated PIP protocol version
pub version: u32,
/// Peer total difficulty
pub difficulty: U256,
/// SHA3 of peer best block hash
pub head: String,
}
/// Sync status
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum SyncStatus {
/// Info when syncing
Info(SyncInfo),
/// Not syncing
None,
}
impl<'de> Deserialize<'de> for SyncStatus {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum Syncing {
/// When client is synced to the highest block, eth_syncing with return "false"
None(bool),
IsSyncing(SyncInfo),
}
match Syncing::deserialize(deserializer)? {
Syncing::None(false) => Ok(SyncStatus::None),
Syncing::None(true) => Err(serde::de::Error::custom(
"eth_syncing returned `true` that is undefined value.",
)),
Syncing::IsSyncing(sync) => Ok(SyncStatus::Info(sync)),
}
}
}
impl Serialize for SyncStatus {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
SyncStatus::Info(info) => info.serialize(serializer),
SyncStatus::None => serializer.serialize_bool(false),
}
}
}
/// Propagation statistics for pending transaction.
#[derive(Debug, Clone, Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionStats {
/// Block no this transaction was first seen.
pub first_seen: u64,
/// Peers this transaction was propagated to with count.
pub propagated_to: BTreeMap<B512, usize>,
}
/// Chain status.
#[derive(Debug, Clone, Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ChainStatus {
/// Describes the gap in the blockchain, if there is one: (first, last)
pub block_gap: Option<(U256, U256)>,
}

View File

@ -1,36 +0,0 @@
//! Types used by tracing backends
use alloy_primitives::TxHash;
use serde::{Deserialize, Serialize};
/// The result of a single transaction trace.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(untagged, rename_all = "camelCase")]
pub enum TraceResult<Ok, Err> {
/// Untagged success variant
Success {
/// Trace results produced by the tracer
result: Ok,
/// transaction hash
#[serde(skip_serializing_if = "Option::is_none")]
tx_hash: Option<TxHash>,
},
/// Untagged error variant
Error {
/// Trace failure produced by the tracer
error: Err,
/// transaction hash
#[serde(skip_serializing_if = "Option::is_none")]
tx_hash: Option<TxHash>,
},
}
impl<Ok, Err> TraceResult<Ok, Err> {
/// Returns the hash of the transaction that was traced.
pub fn tx_hash(&self) -> Option<TxHash> {
*match self {
TraceResult::Success { tx_hash, .. } => tx_hash,
TraceResult::Error { tx_hash, .. } => tx_hash,
}
}
}

View File

@ -1,184 +0,0 @@
//! `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<u64>,
/// To block
#[serde(with = "u64_hex_or_decimal_opt")]
pub to_block: Option<u64>,
/// From address
#[serde(default)]
pub from_address: Vec<Address>,
/// To address
#[serde(default)]
pub to_address: Vec<Address>,
/// How to apply `from_address` and `to_address` filters.
#[serde(default)]
pub mode: TraceFilterMode,
/// Output offset
pub after: Option<u64>,
/// Output amount
pub count: Option<u64>,
}
// === 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<Address>,
to_addresses: HashSet<Address>,
}
impl TraceFilterMatcher {
/// Returns `true` if the given `from` and `to` addresses match this filter.
pub fn matches(&self, from: Address, to: Option<Address>) -> 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)));
}
}

View File

@ -1,118 +0,0 @@
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"`
///
/// <https://github.com/ethereum/go-ethereum/blob/91cb6f863a965481e51d5d9c0e5ccd54796fd967/eth/tracers/native/call.go#L44>
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct CallFrame {
/// The address of that initiated the call.
pub from: Address,
/// How much gas was left before the call
#[serde(default, deserialize_with = "from_int_or_hex")]
pub gas: U256,
/// How much gas was used by the call
#[serde(default, deserialize_with = "from_int_or_hex", rename = "gasUsed")]
pub gas_used: U256,
/// The address of the contract that was called.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub to: Option<Address>,
/// Calldata input
pub input: Bytes,
/// Output of the call, if any.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub output: Option<Bytes>,
/// Error message, if any.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
/// Why this call reverted, if it reverted.
#[serde(default, rename = "revertReason", skip_serializing_if = "Option::is_none")]
pub revert_reason: Option<String>,
/// Recorded child calls.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub calls: Vec<CallFrame>,
/// Logs emitted by this call
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub logs: Vec<CallLogFrame>,
/// Value transferred
#[serde(default, skip_serializing_if = "Option::is_none")]
pub value: Option<U256>,
/// The type of the call
#[serde(rename = "type")]
pub typ: String,
}
/// Represents a recorded call
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct CallLogFrame {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub address: Option<Address>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub topics: Option<Vec<B256>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub data: Option<Bytes>,
}
#[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<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub with_log: Option<bool>,
}
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 <https://github.com/ethereum/go-ethereum/tree/master/eth/tracers/internal/tracetest/testdata>
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();
}
}

View File

@ -1,34 +0,0 @@
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
/// <https://github.com/ethereum/go-ethereum/blob/91cb6f863a965481e51d5d9c0e5ccd54796fd967/eth/tracers/native/4byte.go#L48>
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct FourByteFrame(pub BTreeMap<String, u64>);
#[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();
}
}

View File

@ -1,579 +0,0 @@
//! 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,
},
};
mod call;
mod four_byte;
mod noop;
mod pre_state;
/// Result type for geth style transaction trace
pub type TraceResult = crate::trace::common::TraceResult<GethTrace, String>;
/// blockTraceResult represents the results of tracing a single block when an entire chain is being
/// traced. ref <https://github.com/ethereum/go-ethereum/blob/ee530c0d5aa70d2c00ab5691a89ab431b73f8165/eth/tracers/api.go#L218-L222>
#[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<TraceResult>,
}
/// Geth Default struct log trace frame
///
/// <https://github.com/ethereum/go-ethereum/blob/a9ef135e2dd53682d106c6a2aede9187026cc1de/eth/tracers/logger/logger.go#L406-L411>
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DefaultFrame {
/// Whether the transaction failed
pub failed: bool,
/// How much gas was used.
pub gas: u64,
/// Output of the transaction
#[serde(serialize_with = "crate::serde_helpers::serialize_hex_string_no_prefix")]
pub return_value: Bytes,
/// Recorded traces of the transaction
pub struct_logs: Vec<StructLog>,
}
/// Represents a struct log entry in a trace
///
/// <https://github.com/ethereum/go-ethereum/blob/366d2169fbc0e0f803b68c042b77b6b480836dbc/eth/tracers/logger/logger.go#L413-L426>
#[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 <https://github.com/ethereum/go-ethereum/blob/366d2169fbc0e0f803b68c042b77b6b480836dbc/eth/tracers/logger/logger.go#L450-L452>
#[serde(default, skip_serializing_if = "Option::is_none")]
pub memory: Option<Vec<String>>,
/// Size of memory.
#[serde(default, rename = "memSize", skip_serializing_if = "Option::is_none")]
pub memory_size: Option<u64>,
/// EVM stack
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stack: Option<Vec<U256>>,
/// Last call's return data. Enabled via enableReturnData
#[serde(default, rename = "returnData", skip_serializing_if = "Option::is_none")]
pub return_data: Option<Bytes>,
/// 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<BTreeMap<B256, B256>>,
/// Current call depth
pub depth: u64,
/// Refund counter
#[serde(default, rename = "refund", skip_serializing_if = "Option::is_none")]
pub refund_counter: Option<u64>,
/// Error message if any
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
/// 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<DefaultFrame> for GethTrace {
fn from(value: DefaultFrame) -> Self {
GethTrace::Default(value)
}
}
impl From<FourByteFrame> for GethTrace {
fn from(value: FourByteFrame) -> Self {
GethTrace::FourByteTracer(value)
}
}
impl From<CallFrame> for GethTrace {
fn from(value: CallFrame) -> Self {
GethTrace::CallTracer(value)
}
}
impl From<PreStateFrame> for GethTrace {
fn from(value: PreStateFrame) -> Self {
GethTrace::PreStateTracer(value)
}
}
impl From<NoopFrame> for GethTrace {
fn from(value: NoopFrame) -> Self {
GethTrace::NoopTracer(value)
}
}
/// Available built-in tracers
///
/// See <https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers>
#[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 <https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers> and <https://geth.ethereum.org/docs/developers/evm-tracing/custom-tracer>
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[serde(untagged)]
pub enum GethDebugTracerType {
/// built-in tracer
BuiltInTracer(GethDebugBuiltInTracerType),
/// custom JS tracer
JsTracer(String),
}
impl From<GethDebugBuiltInTracerType> 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<T: DeserializeOwned>(self) -> Result<T, serde_json::Error> {
serde_json::from_value(self.0)
}
/// Returns the [CallConfig] if it is a call config.
pub fn into_call_config(self) -> Result<CallConfig, serde_json::Error> {
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<PreStateConfig, serde_json::Error> {
if self.0.is_null() {
return Ok(Default::default())
}
self.from_value()
}
}
impl From<serde_json::Value> for GethDebugTracerConfig {
fn from(value: serde_json::Value) -> Self {
GethDebugTracerConfig(value)
}
}
/// Bindings for additional `debug_traceTransaction` options
///
/// See <https://geth.ethereum.org/docs/rpc/ns-debug#debug_tracetransaction>
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct GethDebugTracingOptions {
/// The common tracing options
#[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<GethDebugTracerType>,
/// 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 <https://github.com/ethereum/go-ethereum/issues/26513>
///
/// 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<String>,
}
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<bool>,
/// 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 <https://github.com/ethereum/go-ethereum/pull/23558> 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 <https://github.com/paradigmxyz/reth/issues/3033>
#[serde(default, skip_serializing_if = "Option::is_none")]
pub disable_memory: Option<bool>,
/// disable stack capture
#[serde(default, skip_serializing_if = "Option::is_none")]
pub disable_stack: Option<bool>,
/// Disable storage capture
#[serde(default, skip_serializing_if = "Option::is_none")]
pub disable_storage: Option<bool>,
/// Enable return data capture
#[serde(default, skip_serializing_if = "Option::is_none")]
pub enable_return_data: Option<bool>,
/// 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<bool>,
/// print output during capture end
#[serde(default, skip_serializing_if = "Option::is_none")]
pub debug: Option<bool>,
/// maximum length of output, but zero means unlimited
#[serde(default, skip_serializing_if = "Option::is_none")]
pub limit: Option<u64>,
}
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 <https://geth.ethereum.org/docs/rpc/ns-debug#debug_tracecall>
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct GethDebugTracingCallOptions {
/// All the options
#[serde(flatten)]
pub tracing_options: GethDebugTracingOptions,
/// The state overrides to apply
#[serde(default, skip_serializing_if = "Option::is_none")]
pub state_overrides: Option<StateOverride>,
/// The block overrides to apply
#[serde(default, skip_serializing_if = "Option::is_none")]
pub block_overrides: Option<BlockOverrides>,
}
/// Serializes a storage map as a list of key-value pairs _without_ 0x-prefix
fn serialize_string_storage_map_opt<S: Serializer>(
storage: &Option<BTreeMap<B256, B256>>,
s: S,
) -> Result<S::Ok, S::Error> {
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::<GethDebugTracingOptions>(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());
}
// <https://etherscan.io/tx/0xd01212e8ab48d2fd2ea9c4f33f8670fd1cf0cfb09d2e3c6ceddfaf54152386e5>
#[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::<serde_json::Value>(s).unwrap();
similar_asserts::assert_eq!(input, val);
}
#[test]
fn test_trace_result_serde() {
let s = r#" {
"result": {
"from": "0xccc5499e15fedaaeaba68aeb79b95b20f725bc56",
"gas": "0x186a0",
"gasUsed": "0xdb91",
"to": "0xdac17f958d2ee523a2206206994597c13d831ec7",
"input": "0xa9059cbb000000000000000000000000e3f85a274c1edbea2f2498cf5978f41961cf8b5b0000000000000000000000000000000000000000000000000000000068c8f380",
"value": "0x0",
"type": "CALL"
}
}"#;
let _result: TraceResult = serde_json::from_str(s).unwrap();
}
}

View File

@ -1,32 +0,0 @@
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
/// An empty frame response that's only an empty json object `{}`
/// <https://github.com/ethereum/go-ethereum/blob/91cb6f863a965481e51d5d9c0e5ccd54796fd967/eth/tracers/native/noop.go#L34>
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct NoopFrame(BTreeMap<Null, Null>);
#[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();
}
}

View File

@ -1,348 +0,0 @@
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
///
/// <https://github.com/ethereum/go-ethereum/blob/91cb6f863a965481e51d5d9c0e5ccd54796fd967/eth/tracers/native/prestate.go#L38>
#[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<Address, AccountState>);
/// 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<Address, AccountState>,
/// The account states before the transaction is executed.
pub pre: BTreeMap<Address, AccountState>,
}
// === 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<U256>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub code: Option<Bytes>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub nonce: Option<u64>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub storage: BTreeMap<B256, B256>,
}
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<Bytes>) -> 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<bool>,
}
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 <https://github.com/ethereum/go-ethereum/tree/master/eth/tracers/internal/tracetest/testdata>
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());
}
}

View File

@ -1,7 +0,0 @@
//! Types for tracing
pub mod common;
pub mod filter;
pub mod geth;
pub mod parity;
pub mod tracerequest;

View File

@ -1,759 +0,0 @@
//! Types for trace module.
//!
//! See <https://openethereum.github.io/JSONRPC-trace-module>
use alloy_primitives::{Address, Bytes, B256, U256, U64};
use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer};
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 VMs 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<StateDiff>,
/// Enabled if [TraceType::Trace] is provided, otherwise an empty vec
#[serde(default)]
pub trace: Vec<TransactionTrace>,
/// Enabled if [TraceType::VmTrace] is provided
pub vm_trace: Option<VmTrace>,
}
// === 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 {
/// The recorded trace.
#[serde(flatten)]
pub full_trace: TraceResults,
/// Hash of the traced transaction.
pub transaction_hash: B256,
}
/// A changed value
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct ChangedType<T> {
/// Original value
pub from: T,
/// New value
pub to: T,
}
/// Represents how a value changed.
///
/// This is used for statediff.
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
pub enum Delta<T> {
/// Existing value didn't change.
#[default]
#[serde(rename = "=")]
Unchanged,
/// New storage value added.
#[serde(rename = "+")]
Added(T),
/// Existing storage value removed.
#[serde(rename = "-")]
Removed(T),
/// Existing storage value changed.
#[serde(rename = "*")]
Changed(ChangedType<T>),
}
// === impl Delta ===
impl<T> Delta<T> {
/// 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(_))
}
}
/// The diff of an account after a transaction
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AccountDiff {
/// How the balance changed, if at all
pub balance: Delta<U256>,
/// How the code changed, if at all
pub code: Delta<Bytes>,
/// How the nonce changed, if at all
pub nonce: Delta<U64>,
/// All touched/changed storage values
pub storage: BTreeMap<B256, Delta<B256>>,
}
/// New-type for list of account diffs
#[derive(Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize)]
#[serde(transparent)]
pub struct StateDiff(pub BTreeMap<Address, AccountDiff>);
impl Deref for StateDiff {
type Target = BTreeMap<Address, AccountDiff>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for StateDiff {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
/// Represents the various types of actions recorded during tracing
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "type", content = "action")]
pub enum Action {
/// Regular call
Call(CallAction),
/// A CREATE call
Create(CreateAction),
/// Parity style traces never renamed suicide to selfdestruct: <https://eips.ethereum.org/EIPS/eip-6>
///
/// For compatibility reasons, this is serialized as `suicide`: <https://github.com/paradigmxyz/reth/issues/3721>
#[serde(rename = "suicide", alias = "selfdestruct")]
Selfdestruct(SelfdestructAction),
/// Rewards if any (pre POS)
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(_))
}
/// Returns what kind of action this is
pub fn kind(&self) -> ActionType {
match self {
Action::Call(_) => ActionType::Call,
Action::Create(_) => ActionType::Create,
Action::Selfdestruct(_) => ActionType::Selfdestruct,
Action::Reward(_) => ActionType::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 gas available for the creation init code.
pub gas: U64,
/// The init code.
pub init: Bytes,
/// The value with which the new account is endowed.
pub value: U256,
}
/// What kind of reward.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum RewardType {
/// Block rewards
Block,
/// Reward for uncle block
Uncle,
}
/// Recorded reward of a block.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RewardAction {
/// Author's address.
pub author: Address,
/// Reward type.
pub reward_type: RewardType,
/// Reward amount.
pub value: U256,
}
/// 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,
/// Balance of the contract just before it was destroyed.
pub balance: U256,
/// destroyed contract heir.
pub refund_address: Address,
}
/// Outcome of a CALL.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CallOutput {
/// Gas used by the call.
pub gas_used: U64,
/// The output data of the call.
pub output: Bytes,
}
/// Outcome of a CREATE.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateOutput {
/// Address of the created contract.
pub address: Address,
/// Contract code.
pub code: Bytes,
/// Gas used by the call.
pub gas_used: U64,
}
/// 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 {
/// Represents what kind of trace this is
#[serde(flatten)]
pub action: Action,
/// The error message if the transaction failed.
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
/// Output of the trace, can be CALL or CREATE
pub result: Option<TraceOutput>,
/// How many subtraces this trace has.
pub subtraces: usize,
/// The identifier of this transaction trace in the set.
///
/// This gives the exact location in the call trace
/// [index in root CALL, index in first CALL, index in second CALL, …].
pub trace_address: Vec<usize>,
}
/// A wrapper for [TransactionTrace] that includes additional information about the transaction.
#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LocalizedTransactionTrace {
/// Trace of the transaction and its result.
#[serde(flatten)]
pub trace: TransactionTrace,
/// Hash of the block, if not pending.
///
/// Note: this deviates from <https://openethereum.github.io/JSONRPC-trace-module#trace_transaction> which always returns a block number
pub block_hash: Option<B256>,
/// Block number the transaction is included in, None if pending.
///
/// Note: this deviates from <https://openethereum.github.io/JSONRPC-trace-module#trace_transaction> which always returns a block number
pub block_number: Option<u64>,
/// Hash of the transaction
pub transaction_hash: Option<B256>,
/// Transaction index within the block, None if pending.
pub transaction_position: Option<u64>,
}
// Implement Serialize manually to ensure consistent ordering of fields to match other client's
// format
impl Serialize for LocalizedTransactionTrace {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut s = serializer.serialize_struct("LocalizedTransactionTrace", 9)?;
let TransactionTrace { action, error, result, subtraces, trace_address } = &self.trace;
match action {
Action::Call(call_action) => {
s.serialize_field("action", call_action)?;
}
Action::Create(create_action) => {
s.serialize_field("action", create_action)?;
}
Action::Selfdestruct(selfdestruct_action) => {
s.serialize_field("action", selfdestruct_action)?;
}
Action::Reward(reward_action) => {
s.serialize_field("action", reward_action)?;
}
}
if let Some(block_hash) = self.block_hash {
s.serialize_field("blockHash", &block_hash)?;
}
if let Some(block_number) = self.block_number {
s.serialize_field("blockNumber", &block_number)?;
}
if let Some(error) = error {
s.serialize_field("error", error)?;
}
match result {
Some(TraceOutput::Call(call)) => {
s.serialize_field("result", call)?;
}
Some(TraceOutput::Create(create)) => {
s.serialize_field("result", create)?;
}
None => {}
}
s.serialize_field("subtraces", &subtraces)?;
s.serialize_field("traceAddress", &trace_address)?;
if let Some(transaction_hash) = &self.transaction_hash {
s.serialize_field("transactionHash", transaction_hash)?;
}
if let Some(transaction_position) = &self.transaction_position {
s.serialize_field("transactionPosition", transaction_position)?;
}
s.serialize_field("type", &action.kind())?;
s.end()
}
}
/// 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<VmInstruction>,
}
/// A record of a single VM instruction, opcode level.
#[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<VmExecutedOperation>,
/// The program counter.
pub pc: usize,
/// Subordinate trace of the CALL/CREATE if applicable.
pub sub: Option<VmTrace>,
/// Stringified opcode.
#[serde(skip_serializing_if = "Option::is_none")]
pub op: Option<String>,
/// Index of the instruction in the set.
#[serde(skip_serializing_if = "Option::is_none")]
pub idx: Option<String>,
}
/// 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<U256>,
/// If altered, the memory delta.
pub mem: Option<MemoryDelta>,
/// The altered storage value, if any.
pub store: Option<StorageDelta>,
}
/// 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 {
/// Storage key.
pub key: U256,
/// Storage value belonging to the key.
pub val: U256,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::{json, Value};
use std::str::FromStr;
#[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::<TransactionTrace>(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::<TransactionTrace>(input).unwrap();
assert!(val.action.is_selfdestruct());
let json = serde_json::to_value(val.clone()).unwrap();
let expect = serde_json::from_str::<serde_json::Value>(input).unwrap();
similar_asserts::assert_eq!(json, expect);
let s = serde_json::to_string(&val).unwrap();
let json = serde_json::from_str::<serde_json::Value>(&s).unwrap();
similar_asserts::assert_eq!(json, expect);
let input = input.replace("suicide", "selfdestruct");
let val = serde_json::from_str::<TransactionTrace>(&input).unwrap();
assert!(val.action.is_selfdestruct());
}
#[derive(Debug)]
struct TraceTestCase {
trace: LocalizedTransactionTrace,
expected_json: Value,
}
#[test]
fn test_serialization_order() {
let test_cases = vec![
TraceTestCase {
trace: LocalizedTransactionTrace {
trace: TransactionTrace {
action: Action::Call(CallAction {
from: "0x4f4495243837681061c4743b74b3eedf548d56a5".parse::<Address>().unwrap(),
call_type: CallType::DelegateCall,
gas: U64::from(3148955),
input: Bytes::from_str("0x585a9fd40000000000000000000000000000000000000000000000000000000000000040a47c5ad9a4af285720eae6cc174a9c75c5bbaf973b00f1a0c191327445b6581000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000ce16f69375520ab01377ce7b88f5ba8c48f8d666f61490331372e432315cd97447e3bc452d6c73a6e0536260a88ddab46f85c88d00000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000aab8cf0fbfb038751339cb61161fa11789b41a78f1b7b0e12cf8e467d403590b7a5f26f0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000646616e746f6d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a3078636531364636393337353532306162303133373763653742383866354241384334384638443636360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045553444300000000000000000000000000000000000000000000000000000000").unwrap(),
to: "0x99b5fa03a5ea4315725c43346e55a6a6fbd94098".parse::<Address>().unwrap(),
value: U256::from(0),
}),
error: None,
result: Some(TraceOutput::Call(CallOutput { gas_used: U64::from(32364), output: Bytes::new() })),
subtraces: 0,
trace_address: vec![0, 10, 0],
},
block_hash: Some(B256::ZERO),
block_number: Some(18557272),
transaction_hash: Some(B256::from_str("0x54160ddcdbfaf98a43a43c328ebd44aa99faa765e0daa93e61145b06815a4071").unwrap()),
transaction_position: Some(102),
},
expected_json: json!({
"action": {
"from": "0x4f4495243837681061c4743b74b3eedf548d56a5",
"callType": "delegatecall",
"gas": "0x300c9b",
"input": "0x585a9fd40000000000000000000000000000000000000000000000000000000000000040a47c5ad9a4af285720eae6cc174a9c75c5bbaf973b00f1a0c191327445b6581000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000ce16f69375520ab01377ce7b88f5ba8c48f8d666f61490331372e432315cd97447e3bc452d6c73a6e0536260a88ddab46f85c88d00000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000aab8cf0fbfb038751339cb61161fa11789b41a78f1b7b0e12cf8e467d403590b7a5f26f0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000646616e746f6d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a3078636531364636393337353532306162303133373763653742383866354241384334384638443636360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045553444300000000000000000000000000000000000000000000000000000000",
"to": "0x99b5fa03a5ea4315725c43346e55a6a6fbd94098",
"value": "0x0"
},
"blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"blockNumber": 18557272,
"result": {
"gasUsed": "0x7e6c",
"output": "0x"
},
"subtraces": 0,
"traceAddress": [
0,
10,
0
],
"transactionHash": "0x54160ddcdbfaf98a43a43c328ebd44aa99faa765e0daa93e61145b06815a4071",
"transactionPosition": 102,
"type": "call"
}),
},
TraceTestCase {
trace: LocalizedTransactionTrace {
trace: TransactionTrace {
action: Action::Create(CreateAction{
from: "0x4f4495243837681061c4743b74b3eedf548d56a5".parse::<Address>().unwrap(),
gas: U64::from(3438907),
init: Bytes::from_str("0x6080604052600160005534801561001557600080fd5b50610324806100256000396000f3fe608060405234801561001057600080fd5b50600436106100355760003560e01c8062f55d9d1461003a5780631cff79cd1461004f575b600080fd5b61004d6100483660046101da565b610079565b005b61006261005d3660046101fc565b6100bb565b60405161007092919061027f565b60405180910390f35b6002600054141561009d5760405163caa30f5560e01b815260040160405180910390fd5b600260005573ffffffffffffffffffffffffffffffffffffffff8116ff5b60006060600260005414156100e35760405163caa30f5560e01b815260040160405180910390fd5b600260005573ffffffffffffffffffffffffffffffffffffffff85163b610136576040517f6f7c43f100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8473ffffffffffffffffffffffffffffffffffffffff16848460405161015d9291906102de565b6000604051808303816000865af19150503d806000811461019a576040519150601f19603f3d011682016040523d82523d6000602084013e61019f565b606091505b50600160005590969095509350505050565b803573ffffffffffffffffffffffffffffffffffffffff811681146101d557600080fd5b919050565b6000602082840312156101ec57600080fd5b6101f5826101b1565b9392505050565b60008060006040848603121561021157600080fd5b61021a846101b1565b9250602084013567ffffffffffffffff8082111561023757600080fd5b818601915086601f83011261024b57600080fd5b81358181111561025a57600080fd5b87602082850101111561026c57600080fd5b6020830194508093505050509250925092565b821515815260006020604081840152835180604085015260005b818110156102b557858101830151858201606001528201610299565b818111156102c7576000606083870101525b50601f01601f191692909201606001949350505050565b818382376000910190815291905056fea264697066735822122032cb5e746816b7fac95205c068b30da37bd40119a57265be331c162cae74712464736f6c63430008090033").unwrap(),
value: U256::from(0),
}),
error: None,
result: Some(TraceOutput::Create(CreateOutput { gas_used: U64::from(183114), address: "0x7eb6c6c1db08c0b9459a68cfdcedab64f319c138".parse::<Address>().unwrap(), code: Bytes::from_str("0x608060405234801561001057600080fd5b50600436106100355760003560e01c8062f55d9d1461003a5780631cff79cd1461004f575b600080fd5b61004d6100483660046101da565b610079565b005b61006261005d3660046101fc565b6100bb565b60405161007092919061027f565b60405180910390f35b6002600054141561009d5760405163caa30f5560e01b815260040160405180910390fd5b600260005573ffffffffffffffffffffffffffffffffffffffff8116ff5b60006060600260005414156100e35760405163caa30f5560e01b815260040160405180910390fd5b600260005573ffffffffffffffffffffffffffffffffffffffff85163b610136576040517f6f7c43f100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8473ffffffffffffffffffffffffffffffffffffffff16848460405161015d9291906102de565b6000604051808303816000865af19150503d806000811461019a576040519150601f19603f3d011682016040523d82523d6000602084013e61019f565b606091505b50600160005590969095509350505050565b803573ffffffffffffffffffffffffffffffffffffffff811681146101d557600080fd5b919050565b6000602082840312156101ec57600080fd5b6101f5826101b1565b9392505050565b60008060006040848603121561021157600080fd5b61021a846101b1565b9250602084013567ffffffffffffffff8082111561023757600080fd5b818601915086601f83011261024b57600080fd5b81358181111561025a57600080fd5b87602082850101111561026c57600080fd5b6020830194508093505050509250925092565b821515815260006020604081840152835180604085015260005b818110156102b557858101830151858201606001528201610299565b818111156102c7576000606083870101525b50601f01601f191692909201606001949350505050565b818382376000910190815291905056fea264697066735822122032cb5e746816b7fac95205c068b30da37bd40119a57265be331c162cae74712464736f6c63430008090033").unwrap() })),
subtraces: 0,
trace_address: vec![0, 7, 0, 0],
},
block_hash: Some(B256::from_str("0xd5ac5043011d4f16dba7841fa760c4659644b78f663b901af4673b679605ed0d").unwrap()),
block_number: Some(18557272),
transaction_hash: Some(B256::from_str("0x54160ddcdbfaf98a43a43c328ebd44aa99faa765e0daa93e61145b06815a4071").unwrap()),
transaction_position: Some(102),
},
expected_json: json!({
"action": {
"from": "0x4f4495243837681061c4743b74b3eedf548d56a5",
"gas": "0x34793b",
"init": "0x6080604052600160005534801561001557600080fd5b50610324806100256000396000f3fe608060405234801561001057600080fd5b50600436106100355760003560e01c8062f55d9d1461003a5780631cff79cd1461004f575b600080fd5b61004d6100483660046101da565b610079565b005b61006261005d3660046101fc565b6100bb565b60405161007092919061027f565b60405180910390f35b6002600054141561009d5760405163caa30f5560e01b815260040160405180910390fd5b600260005573ffffffffffffffffffffffffffffffffffffffff8116ff5b60006060600260005414156100e35760405163caa30f5560e01b815260040160405180910390fd5b600260005573ffffffffffffffffffffffffffffffffffffffff85163b610136576040517f6f7c43f100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8473ffffffffffffffffffffffffffffffffffffffff16848460405161015d9291906102de565b6000604051808303816000865af19150503d806000811461019a576040519150601f19603f3d011682016040523d82523d6000602084013e61019f565b606091505b50600160005590969095509350505050565b803573ffffffffffffffffffffffffffffffffffffffff811681146101d557600080fd5b919050565b6000602082840312156101ec57600080fd5b6101f5826101b1565b9392505050565b60008060006040848603121561021157600080fd5b61021a846101b1565b9250602084013567ffffffffffffffff8082111561023757600080fd5b818601915086601f83011261024b57600080fd5b81358181111561025a57600080fd5b87602082850101111561026c57600080fd5b6020830194508093505050509250925092565b821515815260006020604081840152835180604085015260005b818110156102b557858101830151858201606001528201610299565b818111156102c7576000606083870101525b50601f01601f191692909201606001949350505050565b818382376000910190815291905056fea264697066735822122032cb5e746816b7fac95205c068b30da37bd40119a57265be331c162cae74712464736f6c63430008090033",
"value": "0x0"
},
"blockHash": "0xd5ac5043011d4f16dba7841fa760c4659644b78f663b901af4673b679605ed0d",
"blockNumber": 18557272,
"result": {
"address": "0x7eb6c6c1db08c0b9459a68cfdcedab64f319c138",
"code": "0x608060405234801561001057600080fd5b50600436106100355760003560e01c8062f55d9d1461003a5780631cff79cd1461004f575b600080fd5b61004d6100483660046101da565b610079565b005b61006261005d3660046101fc565b6100bb565b60405161007092919061027f565b60405180910390f35b6002600054141561009d5760405163caa30f5560e01b815260040160405180910390fd5b600260005573ffffffffffffffffffffffffffffffffffffffff8116ff5b60006060600260005414156100e35760405163caa30f5560e01b815260040160405180910390fd5b600260005573ffffffffffffffffffffffffffffffffffffffff85163b610136576040517f6f7c43f100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8473ffffffffffffffffffffffffffffffffffffffff16848460405161015d9291906102de565b6000604051808303816000865af19150503d806000811461019a576040519150601f19603f3d011682016040523d82523d6000602084013e61019f565b606091505b50600160005590969095509350505050565b803573ffffffffffffffffffffffffffffffffffffffff811681146101d557600080fd5b919050565b6000602082840312156101ec57600080fd5b6101f5826101b1565b9392505050565b60008060006040848603121561021157600080fd5b61021a846101b1565b9250602084013567ffffffffffffffff8082111561023757600080fd5b818601915086601f83011261024b57600080fd5b81358181111561025a57600080fd5b87602082850101111561026c57600080fd5b6020830194508093505050509250925092565b821515815260006020604081840152835180604085015260005b818110156102b557858101830151858201606001528201610299565b818111156102c7576000606083870101525b50601f01601f191692909201606001949350505050565b818382376000910190815291905056fea264697066735822122032cb5e746816b7fac95205c068b30da37bd40119a57265be331c162cae74712464736f6c63430008090033",
"gasUsed": "0x2cb4a"
},
"subtraces": 0,
"traceAddress": [
0,
7,
0,
0
],
"transactionHash": "0x54160ddcdbfaf98a43a43c328ebd44aa99faa765e0daa93e61145b06815a4071",
"transactionPosition": 102,
"type": "create"
}),
}
];
for (i, test_case) in test_cases.iter().enumerate() {
let serialized = serde_json::to_string(&test_case.trace).unwrap();
let actual_json: Value = serde_json::from_str(&serialized).unwrap();
assert_eq!(
actual_json, test_case.expected_json,
"Test case {} failed; Trace: {:?}",
i, test_case.trace
);
}
}
#[test]
fn test_deserialize_serialize() {
let reference_data = r#"{
"action": {
"from": "0xc77820eef59629fc8d88154977bc8de8a1b2f4ae",
"callType": "call",
"gas": "0x4a0d00",
"input": "0x12",
"to": "0x4f4495243837681061c4743b74b3eedf548d56a5",
"value": "0x0"
},
"blockHash": "0xd5ac5043011d4f16dba7841fa760c4659644b78f663b901af4673b679605ed0d",
"blockNumber": 18557272,
"result": {
"gasUsed": "0x17d337",
"output": "0x"
},
"subtraces": 1,
"traceAddress": [],
"transactionHash": "0x54160ddcdbfaf98a43a43c328ebd44aa99faa765e0daa93e61145b06815a4071",
"transactionPosition": 102,
"type": "call"
}"#;
let trace: LocalizedTransactionTrace = serde_json::from_str(reference_data).unwrap();
assert!(trace.trace.action.is_call());
let serialized = serde_json::to_string_pretty(&trace).unwrap();
similar_asserts::assert_eq!(serialized, reference_data);
}
#[test]
fn test_deserialize_serialize_selfdestruct() {
let reference_data = r#"{
"action": {
"address": "0xc77820eef59629fc8d88154977bc8de8a1b2f4ae",
"balance": "0x0",
"refundAddress": "0x4f4495243837681061c4743b74b3eedf548d56a5"
},
"blockHash": "0xd5ac5043011d4f16dba7841fa760c4659644b78f663b901af4673b679605ed0d",
"blockNumber": 18557272,
"result": {
"gasUsed": "0x17d337",
"output": "0x"
},
"subtraces": 1,
"traceAddress": [],
"transactionHash": "0x54160ddcdbfaf98a43a43c328ebd44aa99faa765e0daa93e61145b06815a4071",
"transactionPosition": 102,
"type": "suicide"
}"#;
let trace: LocalizedTransactionTrace = serde_json::from_str(reference_data).unwrap();
assert!(trace.trace.action.is_selfdestruct());
let serialized = serde_json::to_string_pretty(&trace).unwrap();
similar_asserts::assert_eq!(serialized, reference_data);
}
}

View File

@ -1,84 +0,0 @@
//! 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, Default)]
pub struct TraceCallRequest {
/// call request object
pub call: CallRequest,
/// trace types
pub trace_types: HashSet<TraceType>,
/// Optional: blockId
pub block_id: Option<BlockId>,
/// Optional: StateOverride
pub state_overrides: Option<StateOverride>,
/// Optional: BlockOverrides
pub block_overrides: Option<Box<BlockOverrides>>,
}
impl TraceCallRequest {
/// Returns a new [`TraceCallRequest`] given a [`CallRequest`] and [`HashSet<TraceType>`]
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<BlockOverrides>) -> 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<I: IntoIterator<Item = TraceType>>(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)
}
}

View File

@ -1,89 +0,0 @@
use alloy_primitives::{Address, B256, U256};
use serde::{Deserialize, Serialize};
/// A list of addresses and storage keys that the transaction plans to access.
/// Accesses outside the list are possible, but become more expensive.
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, Hash, Default)]
#[serde(rename_all = "camelCase")]
pub struct AccessListItem {
/// Account addresses that would be loaded at the start of execution
pub address: Address,
/// Keys of storage that would be loaded at the start of execution
pub storage_keys: Vec<B256>,
}
/// AccessList as defined in EIP-2930
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct AccessList(pub Vec<AccessListItem>);
impl AccessList {
/// Converts the list into a vec, expected by revm
pub fn flattened(&self) -> Vec<(Address, Vec<U256>)> {
self.flatten().collect()
}
/// Consumes the type and converts the list into a vec, expected by revm
pub fn into_flattened(self) -> Vec<(Address, Vec<U256>)> {
self.into_flatten().collect()
}
/// Consumes the type and returns an iterator over the list's addresses and storage keys.
pub fn into_flatten(self) -> impl Iterator<Item = (Address, Vec<U256>)> {
self.0.into_iter().map(|item| {
(
item.address,
item.storage_keys.into_iter().map(|slot| U256::from_be_bytes(slot.0)).collect(),
)
})
}
/// Returns an iterator over the list's addresses and storage keys.
pub fn flatten(&self) -> impl Iterator<Item = (Address, Vec<U256>)> + '_ {
self.0.iter().map(|item| {
(
item.address,
item.storage_keys.iter().map(|slot| U256::from_be_bytes(slot.0)).collect(),
)
})
}
}
/// Access list with gas used appended.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct AccessListWithGasUsed {
/// List with accounts accessed during transaction.
pub access_list: AccessList,
/// Estimated gas used with access list.
pub gas_used: U256,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn access_list_serde() {
let list = AccessList(vec![
AccessListItem { address: Address::ZERO, storage_keys: vec![B256::ZERO] },
AccessListItem { address: Address::ZERO, storage_keys: vec![B256::ZERO] },
]);
let json = serde_json::to_string(&list).unwrap();
let list2 = serde_json::from_str::<AccessList>(&json).unwrap();
assert_eq!(list, list2);
}
#[test]
fn access_list_with_gas_used() {
let list = AccessListWithGasUsed {
access_list: AccessList(vec![
AccessListItem { address: Address::ZERO, storage_keys: vec![B256::ZERO] },
AccessListItem { address: Address::ZERO, storage_keys: vec![B256::ZERO] },
]),
gas_used: U256::from(100),
};
let json = serde_json::to_string(&list).unwrap();
let list2 = serde_json::from_str::<AccessListWithGasUsed>(&json).unwrap();
assert_eq!(list, list2);
}
}

View File

@ -1,19 +0,0 @@
//! Commonly used additional types that are not part of the JSON RPC spec but are often required
//! when working with RPC types, such as [Transaction](crate::Transaction)
use alloy_primitives::{TxHash, B256};
/// Additional fields in the context of a block that contains this transaction.
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
pub struct TransactionInfo {
/// Hash of the transaction.
pub hash: Option<TxHash>,
/// Index of the transaction in the block
pub index: Option<u64>,
/// Hash of the block.
pub block_hash: Option<B256>,
/// Number of the block.
pub block_number: Option<u64>,
/// Base fee of the block.
pub base_fee: Option<u64>,
}

View File

@ -1,12 +0,0 @@
//! KZG type bindings
use alloy_primitives::FixedBytes;
/// How many bytes are in a blob
pub const BYTES_PER_BLOB: usize = 131072;
/// A Blob serialized as 0x-prefixed hex string
pub type Blob = FixedBytes<BYTES_PER_BLOB>;
/// A commitment/proof serialized as 0x-prefixed hex string
pub type Bytes48 = FixedBytes<48>;

View File

@ -1,182 +1,5 @@
//! RPC types for transactions //! RPC types for transactions
pub use access_list::{AccessList, AccessListItem, AccessListWithGasUsed};
use alloy_primitives::{Address, Bytes, B256, U128, U256, U64};
pub use common::TransactionInfo;
pub use receipt::TransactionReceipt;
pub use request::TransactionRequest;
use serde::{Deserialize, Serialize};
pub use signature::{Parity, Signature};
pub use typed::*;
mod access_list;
mod common;
pub mod kzg;
mod receipt;
mod request; mod request;
mod signature;
mod typed; mod typed;
pub use request::TransactionRequest;
/// Transaction object used in RPC pub use typed::*;
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Transaction {
/// Hash
pub hash: B256,
/// Nonce
pub nonce: U64,
/// Block hash
pub block_hash: Option<B256>,
/// Block number
pub block_number: Option<U256>,
/// Transaction Index
pub transaction_index: Option<U256>,
/// Sender
pub from: Address,
/// Recipient
pub to: Option<Address>,
/// Transferred value
pub value: U256,
/// Gas Price
#[serde(skip_serializing_if = "Option::is_none")]
pub gas_price: Option<U128>,
/// Gas amount
pub gas: U256,
/// Max BaseFeePerGas the user is willing to pay.
#[serde(skip_serializing_if = "Option::is_none")]
pub max_fee_per_gas: Option<U128>,
/// The miner's tip.
#[serde(skip_serializing_if = "Option::is_none")]
pub max_priority_fee_per_gas: Option<U128>,
/// Configured max fee per blob gas for eip-4844 transactions
#[serde(skip_serializing_if = "Option::is_none")]
pub max_fee_per_blob_gas: Option<U128>,
/// Data
pub input: Bytes,
/// All _flattened_ fields of the transaction signature.
///
/// Note: this is an option so special transaction types without a signature (e.g. <https://github.com/ethereum-optimism/optimism/blob/0bf643c4147b43cd6f25a759d331ef3a2a61a2a3/specs/deposits.md#the-deposited-transaction-type>) can be supported.
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub signature: Option<Signature>,
/// The chain id of the transaction, if any.
pub chain_id: Option<U64>,
/// Contains the blob hashes for eip-4844 transactions.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub blob_versioned_hashes: Vec<B256>,
/// EIP2930
///
/// Pre-pay to warm storage access.
#[serde(skip_serializing_if = "Option::is_none")]
pub access_list: Option<Vec<AccessListItem>>,
/// EIP2718
///
/// Transaction type, Some(2) for EIP-1559 transaction,
/// Some(1) for AccessList transaction, None for Legacy
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub transaction_type: Option<U64>,
/// Optimism specific transaction fields
#[cfg(feature = "optimism")]
#[serde(flatten)]
pub optimism: OptimismTransactionFields,
}
/// Optimism specific transaction fields
#[cfg(feature = "optimism")]
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OptimismTransactionFields {
/// Hash that uniquely identifies the source of the deposit.
#[serde(rename = "sourceHash", skip_serializing_if = "Option::is_none")]
pub source_hash: Option<B256>,
/// The ETH value to mint on L2
#[serde(rename = "mint", skip_serializing_if = "Option::is_none")]
pub mint: Option<U128>,
/// Field indicating whether the transaction is a system transaction, and therefore
/// exempt from the L2 gas limit.
#[serde(rename = "isSystemTx", skip_serializing_if = "Option::is_none")]
pub is_system_tx: Option<bool>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::eth::transaction::signature::Parity;
#[test]
fn serde_transaction() {
let transaction = Transaction {
hash: B256::with_last_byte(1),
nonce: U64::from(2),
block_hash: Some(B256::with_last_byte(3)),
block_number: Some(U256::from(4)),
transaction_index: Some(U256::from(5)),
from: Address::with_last_byte(6),
to: Some(Address::with_last_byte(7)),
value: U256::from(8),
gas_price: Some(U128::from(9)),
gas: U256::from(10),
input: Bytes::from(vec![11, 12, 13]),
signature: Some(Signature {
v: U256::from(14),
r: U256::from(14),
s: U256::from(14),
y_parity: None,
}),
chain_id: Some(U64::from(17)),
blob_versioned_hashes: vec![],
access_list: None,
transaction_type: Some(U64::from(20)),
max_fee_per_gas: Some(U128::from(21)),
max_priority_fee_per_gas: Some(U128::from(22)),
max_fee_per_blob_gas: None,
#[cfg(feature = "optimism")]
optimism: Default::default(),
};
let serialized = serde_json::to_string(&transaction).unwrap();
assert_eq!(
serialized,
r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","nonce":"0x2","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000003","blockNumber":"0x4","transactionIndex":"0x5","from":"0x0000000000000000000000000000000000000006","to":"0x0000000000000000000000000000000000000007","value":"0x8","gasPrice":"0x9","gas":"0xa","maxFeePerGas":"0x15","maxPriorityFeePerGas":"0x16","input":"0x0b0c0d","r":"0xe","s":"0xe","v":"0xe","chainId":"0x11","type":"0x14"}"#
);
let deserialized: Transaction = serde_json::from_str(&serialized).unwrap();
assert_eq!(transaction, deserialized);
}
#[test]
fn serde_transaction_with_parity_bit() {
let transaction = Transaction {
hash: B256::with_last_byte(1),
nonce: U64::from(2),
block_hash: Some(B256::with_last_byte(3)),
block_number: Some(U256::from(4)),
transaction_index: Some(U256::from(5)),
from: Address::with_last_byte(6),
to: Some(Address::with_last_byte(7)),
value: U256::from(8),
gas_price: Some(U128::from(9)),
gas: U256::from(10),
input: Bytes::from(vec![11, 12, 13]),
signature: Some(Signature {
v: U256::from(14),
r: U256::from(14),
s: U256::from(14),
y_parity: Some(Parity(true)),
}),
chain_id: Some(U64::from(17)),
blob_versioned_hashes: vec![],
access_list: None,
transaction_type: Some(U64::from(20)),
max_fee_per_gas: Some(U128::from(21)),
max_priority_fee_per_gas: Some(U128::from(22)),
max_fee_per_blob_gas: None,
#[cfg(feature = "optimism")]
optimism: Default::default(),
};
let serialized = serde_json::to_string(&transaction).unwrap();
assert_eq!(
serialized,
r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","nonce":"0x2","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000003","blockNumber":"0x4","transactionIndex":"0x5","from":"0x0000000000000000000000000000000000000006","to":"0x0000000000000000000000000000000000000007","value":"0x8","gasPrice":"0x9","gas":"0xa","maxFeePerGas":"0x15","maxPriorityFeePerGas":"0x16","input":"0x0b0c0d","r":"0xe","s":"0xe","v":"0xe","yParity":"0x1","chainId":"0x11","type":"0x14"}"#
);
let deserialized: Transaction = serde_json::from_str(&serialized).unwrap();
assert_eq!(transaction, deserialized);
}
}

View File

@ -1,74 +0,0 @@
use crate::Log;
use alloy_primitives::{Address, Bloom, B256, U128, U256, U64, U8};
use serde::{Deserialize, Serialize};
/// Transaction receipt
#[derive(Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionReceipt {
/// Transaction Hash.
pub transaction_hash: Option<B256>,
/// Index within the block.
pub transaction_index: U64,
/// Hash of the block this transaction was included within.
pub block_hash: Option<B256>,
/// Number of the block this transaction was included within.
pub block_number: Option<U256>,
/// Cumulative gas used within the block after this was executed.
pub cumulative_gas_used: U256,
/// Gas used by this transaction alone.
pub gas_used: Option<U256>,
/// The price paid post-execution by the transaction (i.e. base fee + priority fee). Both
/// fields in 1559-style transactions are maximums (max fee + max priority fee), the amount
/// that's actually paid by users can only be determined post-execution
pub effective_gas_price: U128,
/// Blob gas used by the eip-4844 transaction
///
/// This is None for non eip-4844 transactions
#[serde(skip_serializing_if = "Option::is_none")]
pub blob_gas_used: Option<U128>,
/// The price paid by the eip-4844 transaction per blob gas.
#[serde(skip_serializing_if = "Option::is_none")]
pub blob_gas_price: Option<U128>,
/// Address of the sender
pub from: Address,
/// Address of the receiver. null when its a contract creation transaction.
pub to: Option<Address>,
/// Contract address created, or None if not a deployment.
pub contract_address: Option<Address>,
/// Logs emitted by this transaction.
pub logs: Vec<Log>,
/// Logs bloom
pub logs_bloom: Bloom,
/// The post-transaction stateroot (pre Byzantium)
///
/// EIP98 makes this optional field, if it's missing then skip serializing it
#[serde(skip_serializing_if = "Option::is_none", rename = "root")]
pub state_root: Option<B256>,
/// Status: either 1 (success) or 0 (failure). Only present after activation of EIP-658
#[serde(skip_serializing_if = "Option::is_none", rename = "status")]
pub status_code: Option<U64>,
/// EIP-2718 Transaction type, Some(1) for AccessList transaction, None for Legacy
#[serde(rename = "type")]
pub transaction_type: U8,
/// Deposit nonce for deposit transactions post-regolith
#[cfg(feature = "optimism")]
#[serde(skip_serializing_if = "Option::is_none")]
pub deposit_nonce: Option<U64>,
/// L1 fee for the transaction
#[cfg(feature = "optimism")]
#[serde(skip_serializing_if = "Option::is_none")]
pub l1_fee: Option<U256>,
/// L1 fee scalar for the transaction
#[cfg(feature = "optimism")]
#[serde(skip_serializing_if = "Option::is_none")]
pub l1_fee_scalar: Option<U256>,
/// L1 gas price for the transaction
#[cfg(feature = "optimism")]
#[serde(skip_serializing_if = "Option::is_none")]
pub l1_gas_price: Option<U256>,
/// L1 gas used for the transaction
#[cfg(feature = "optimism")]
#[serde(skip_serializing_if = "Option::is_none")]
pub l1_gas_used: Option<U256>,
}

View File

@ -1,7 +1,8 @@
use crate::eth::transaction::{ use crate::{
typed::{ eth::transaction::typed::{
BlobTransactionSidecar, EIP1559TransactionRequest, EIP2930TransactionRequest, BlobTransactionSidecar, EIP1559TransactionRequest, EIP2930TransactionRequest,
LegacyTransactionRequest, TransactionKind, TypedTransactionRequest, EIP4844TransactionRequest, LegacyTransactionRequest, TransactionKind,
TypedTransactionRequest,
}, },
AccessList, AccessList,
}; };
@ -141,7 +142,7 @@ impl TransactionRequest {
Some(sidecar), Some(sidecar),
) => { ) => {
// As per the EIP, we follow the same semantics as EIP-1559. // As per the EIP, we follow the same semantics as EIP-1559.
Some(TypedTransactionRequest::EIP4844(crate::EIP4844TransactionRequest { Some(TypedTransactionRequest::EIP4844(EIP4844TransactionRequest {
chain_id: 0, chain_id: 0,
nonce: nonce.unwrap_or_default(), nonce: nonce.unwrap_or_default(),
max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(), max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(),

View File

@ -1,190 +0,0 @@
//! Signature related RPC values
use alloy_primitives::U256;
use serde::{Deserialize, Serialize};
/// Container type for all signature fields in RPC
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
pub struct Signature {
/// The R field of the signature; the point on the curve.
pub r: U256,
/// The S field of the signature; the point on the curve.
pub s: U256,
// TODO: change these fields to an untagged enum for `v` XOR `y_parity` if/when CLs support it.
// See <https://github.com/ethereum/go-ethereum/issues/27727> for more information
/// For EIP-155, EIP-2930 and Blob transactions this is set to the parity (0 for even, 1 for
/// odd) of the y-value of the secp256k1 signature.
///
/// For legacy transactions, this is the recovery id
///
/// See also <https://ethereum.github.io/execution-apis/api-documentation/> and <https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionbyhash>
pub v: U256,
/// The y parity of the signature. This is only used for typed (non-legacy) transactions.
#[serde(default, rename = "yParity", skip_serializing_if = "Option::is_none")]
pub y_parity: Option<Parity>,
}
/// Type that represents the signature parity byte, meant for use in RPC.
///
/// This will be serialized as "0x0" if false, and "0x1" if true.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Parity(
#[serde(serialize_with = "serialize_parity", deserialize_with = "deserialize_parity")] pub bool,
);
fn serialize_parity<S>(parity: &bool, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(if *parity { "0x1" } else { "0x0" })
}
/// This implementation disallows serialization of the y parity bit that are not `"0x0"` or `"0x1"`.
fn deserialize_parity<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
match s.as_str() {
"0x0" => Ok(false),
"0x1" => Ok(true),
_ => Err(serde::de::Error::custom(format!(
"invalid parity value, parity should be either \"0x0\" or \"0x1\": {}",
s
))),
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::*;
#[test]
fn deserialize_without_parity() {
let raw_signature_without_y_parity = r#"{
"r":"0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0",
"s":"0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05",
"v":"0x1"
}"#;
let signature: Signature = serde_json::from_str(raw_signature_without_y_parity).unwrap();
let expected = Signature {
r: U256::from_str("0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0")
.unwrap(),
s: U256::from_str("0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05")
.unwrap(),
v: U256::from_str("1").unwrap(),
y_parity: None,
};
assert_eq!(signature, expected);
}
#[test]
fn deserialize_with_parity() {
let raw_signature_with_y_parity = r#"{
"r":"0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0",
"s":"0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05",
"v":"0x1",
"yParity": "0x1"
}"#;
let signature: Signature = serde_json::from_str(raw_signature_with_y_parity).unwrap();
let expected = Signature {
r: U256::from_str("0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0")
.unwrap(),
s: U256::from_str("0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05")
.unwrap(),
v: U256::from_str("1").unwrap(),
y_parity: Some(Parity(true)),
};
assert_eq!(signature, expected);
}
#[test]
fn serialize_both_parity() {
// this test should be removed if the struct moves to an enum based on tx type
let signature = Signature {
r: U256::from_str("0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0")
.unwrap(),
s: U256::from_str("0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05")
.unwrap(),
v: U256::from_str("1").unwrap(),
y_parity: Some(Parity(true)),
};
let serialized = serde_json::to_string(&signature).unwrap();
assert_eq!(
serialized,
r#"{"r":"0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0","s":"0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05","v":"0x1","yParity":"0x1"}"#
);
}
#[test]
fn serialize_v_only() {
// this test should be removed if the struct moves to an enum based on tx type
let signature = Signature {
r: U256::from_str("0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0")
.unwrap(),
s: U256::from_str("0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05")
.unwrap(),
v: U256::from_str("1").unwrap(),
y_parity: None,
};
let expected = r#"{"r":"0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0","s":"0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05","v":"0x1"}"#;
let serialized = serde_json::to_string(&signature).unwrap();
assert_eq!(serialized, expected);
}
#[test]
fn serialize_parity() {
let parity = Parity(true);
let serialized = serde_json::to_string(&parity).unwrap();
assert_eq!(serialized, r#""0x1""#);
let parity = Parity(false);
let serialized = serde_json::to_string(&parity).unwrap();
assert_eq!(serialized, r#""0x0""#);
}
#[test]
fn deserialize_parity() {
let raw_parity = r#""0x1""#;
let parity: Parity = serde_json::from_str(raw_parity).unwrap();
assert_eq!(parity, Parity(true));
let raw_parity = r#""0x0""#;
let parity: Parity = serde_json::from_str(raw_parity).unwrap();
assert_eq!(parity, Parity(false));
}
#[test]
fn deserialize_parity_invalid() {
let raw_parity = r#""0x2""#;
let parity: Result<Parity, _> = serde_json::from_str(raw_parity);
assert!(parity.is_err());
let raw_parity = r#""0x""#;
let parity: Result<Parity, _> = serde_json::from_str(raw_parity);
assert!(parity.is_err());
// In the spec this is defined as a uint, which requires 0x
// yParity:
// <https://github.com/ethereum/execution-apis/blob/8fcafbbc86257f6e61fddd9734148e38872a71c9/src/schemas/transaction.yaml#L157>
//
// uint:
// <https://github.com/ethereum/execution-apis/blob/8fcafbbc86257f6e61fddd9734148e38872a71c9/src/schemas/base-types.yaml#L47>
let raw_parity = r#""1""#;
let parity: Result<Parity, _> = serde_json::from_str(raw_parity);
assert!(parity.is_err());
let raw_parity = r#""0""#;
let parity: Result<Parity, _> = serde_json::from_str(raw_parity);
assert!(parity.is_err());
}
}

View File

@ -4,12 +4,12 @@
#![allow(missing_docs)] #![allow(missing_docs)]
use crate::{
eth::transaction::AccessList,
kzg::{Blob, Bytes48},
};
use alloy_primitives::{Address, Bytes, B256, U128, U256, U64}; use alloy_primitives::{Address, Bytes, B256, U128, U256, U64};
use alloy_rlp::{BufMut, Decodable, Encodable, Error as RlpError}; use alloy_rlp::{BufMut, Decodable, Encodable, Error as RlpError};
use alloy_rpc_types::{
kzg::{Blob, Bytes48},
AccessList,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Container type for various Ethereum transaction requests /// Container type for various Ethereum transaction requests

View File

@ -1,520 +0,0 @@
//! Types for the `txpool` namespace: <https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-txpool>
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<Address>,
/// 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<E>(self, value: &str) -> Result<Self::Value, E>
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<E>(self, value: String) -> Result<Self::Value, E>
where
E: de::Error,
{
self.visit_str(&value)
}
}
/// Implement the `Deserialize` trait for `TxpoolInspectSummary` struct.
impl<'de> Deserialize<'de> for TxpoolInspectSummary {
fn deserialize<D>(deserializer: D) -> Result<TxpoolInspectSummary, D::Error>
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<Address, BTreeMap<String, Transaction>>,
/// queued tx
pub queued: BTreeMap<Address, BTreeMap<String, Transaction>>,
}
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<String, Transaction>,
/// queued tx
pub queued: BTreeMap<String, Transaction>,
}
/// 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<Address, BTreeMap<String, TxpoolInspectSummary>>,
/// queued tx
pub queued: BTreeMap<Address, BTreeMap<String, TxpoolInspectSummary>>,
}
/// 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::<TxpoolContent>(&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 }
}
}

View File

@ -1,57 +0,0 @@
//! Withdrawal type and serde helpers.
use crate::serde_helpers::u64_hex;
use alloy_primitives::{Address, U256};
use alloy_rlp::{RlpDecodable, RlpEncodable};
use serde::{Deserialize, Serialize};
use std::mem;
/// Multiplier for converting gwei to wei.
pub const GWEI_TO_WEI: u64 = 1_000_000_000;
/// Withdrawal represents a validator withdrawal from the consensus layer.
#[derive(
Debug, Clone, PartialEq, Eq, Default, Hash, RlpEncodable, RlpDecodable, Serialize, Deserialize,
)]
#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
pub struct Withdrawal {
/// Monotonically increasing identifier issued by consensus layer.
#[serde(with = "u64_hex")]
pub index: u64,
/// Index of validator associated with withdrawal.
#[serde(with = "u64_hex", rename = "validatorIndex")]
pub validator_index: u64,
/// Target address for withdrawn ether.
pub address: Address,
/// Value of the withdrawal in gwei.
#[serde(with = "u64_hex")]
pub amount: u64,
}
impl Withdrawal {
/// Return the withdrawal amount in wei.
pub fn amount_wei(&self) -> U256 {
U256::from(self.amount) * U256::from(GWEI_TO_WEI)
}
/// Calculate a heuristic for the in-memory size of the [Withdrawal].
#[inline]
pub fn size(&self) -> usize {
mem::size_of::<Self>()
}
}
#[cfg(test)]
mod tests {
use super::*;
// <https://github.com/paradigmxyz/reth/issues/1614>
#[test]
fn test_withdrawal_serde_roundtrip() {
let input = r#"[{"index":"0x0","validatorIndex":"0x0","address":"0x0000000000000000000000000000000000001000","amount":"0x1"},{"index":"0x1","validatorIndex":"0x1","address":"0x0000000000000000000000000000000000001001","amount":"0x1"},{"index":"0x2","validatorIndex":"0x2","address":"0x0000000000000000000000000000000000001002","amount":"0x1"},{"index":"0x3","validatorIndex":"0x3","address":"0x0000000000000000000000000000000000001003","amount":"0x1"},{"index":"0x4","validatorIndex":"0x4","address":"0x0000000000000000000000000000000000001004","amount":"0x1"},{"index":"0x5","validatorIndex":"0x5","address":"0x0000000000000000000000000000000000001005","amount":"0x1"},{"index":"0x6","validatorIndex":"0x6","address":"0x0000000000000000000000000000000000001006","amount":"0x1"},{"index":"0x7","validatorIndex":"0x7","address":"0x0000000000000000000000000000000000001007","amount":"0x1"},{"index":"0x8","validatorIndex":"0x8","address":"0x0000000000000000000000000000000000001008","amount":"0x1"},{"index":"0x9","validatorIndex":"0x9","address":"0x0000000000000000000000000000000000001009","amount":"0x1"},{"index":"0xa","validatorIndex":"0xa","address":"0x000000000000000000000000000000000000100a","amount":"0x1"},{"index":"0xb","validatorIndex":"0xb","address":"0x000000000000000000000000000000000000100b","amount":"0x1"},{"index":"0xc","validatorIndex":"0xc","address":"0x000000000000000000000000000000000000100c","amount":"0x1"},{"index":"0xd","validatorIndex":"0xd","address":"0x000000000000000000000000000000000000100d","amount":"0x1"},{"index":"0xe","validatorIndex":"0xe","address":"0x000000000000000000000000000000000000100e","amount":"0x1"},{"index":"0xf","validatorIndex":"0xf","address":"0x000000000000000000000000000000000000100f","amount":"0x1"}]"#;
let withdrawals: Vec<Withdrawal> = serde_json::from_str(input).unwrap();
let s = serde_json::to_string(&withdrawals).unwrap();
assert_eq!(input, s);
}
}

View File

@ -1,69 +0,0 @@
use alloy_primitives::{B256, U256};
use serde::{
de::{Error, SeqAccess, Visitor},
Deserialize, Deserializer, Serialize, Serializer,
};
use std::fmt;
/// The result of an `eth_getWork` request
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct Work {
/// The proof-of-work hash.
pub pow_hash: B256,
/// The seed hash.
pub seed_hash: B256,
/// The target.
pub target: B256,
/// The block number: this isn't always stored.
pub number: Option<u64>,
}
impl Serialize for Work {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self.number.as_ref() {
Some(num) => {
(&self.pow_hash, &self.seed_hash, &self.target, U256::from(*num)).serialize(s)
}
None => (&self.pow_hash, &self.seed_hash, &self.target).serialize(s),
}
}
}
impl<'a> Deserialize<'a> for Work {
fn deserialize<D>(deserializer: D) -> Result<Work, D::Error>
where
D: Deserializer<'a>,
{
struct WorkVisitor;
impl<'a> Visitor<'a> for WorkVisitor {
type Value = Work;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "Work object")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'a>,
{
let pow_hash = seq
.next_element::<B256>()?
.ok_or_else(|| A::Error::custom("missing pow hash"))?;
let seed_hash = seq
.next_element::<B256>()?
.ok_or_else(|| A::Error::custom("missing seed hash"))?;
let target = seq
.next_element::<B256>()?
.ok_or_else(|| A::Error::custom("missing target"))?;
let number = seq.next_element::<u64>()?;
Ok(Work { pow_hash, seed_hash, target, number })
}
}
deserializer.deserialize_any(WorkVisitor)
}
}

View File

@ -20,11 +20,22 @@ pub mod relay;
mod rpc; mod rpc;
pub mod serde_helpers; pub mod serde_helpers;
// Ethereum specific rpc types coming from alloy.
pub use alloy_rpc_types::*;
// Ethereum specific rpc types related to typed transaction requests and the engine API.
pub use eth::{
engine,
engine::{
ExecutionPayload, ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, PayloadError,
},
transaction::{
self, BlobTransactionSidecar, TransactionKind, TransactionRequest, TypedTransactionRequest,
},
};
pub use admin::*; pub use admin::*;
pub use eth::*;
pub use mev::*; pub use mev::*;
pub use net::*; pub use net::*;
pub use otterscan::*; pub use otterscan::*;
pub use peer::*; pub use peer::*;
pub use rpc::*; pub use rpc::*;
pub use serde_helpers::*;

View File

@ -4,8 +4,9 @@
use crate::{ use crate::{
beacon::{BlsPublicKey, BlsSignature}, beacon::{BlsPublicKey, BlsSignature},
engine::{BlobsBundleV1, ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3}, engine::{
ExecutionPayload, BlobsBundleV1, ExecutionPayload, ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3,
},
}; };
use alloy_primitives::{Address, B256, U256}; use alloy_primitives::{Address, B256, U256};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View File

@ -49,6 +49,8 @@ use crate::eth::error::OptimismEthApiError;
#[cfg(feature = "optimism")] #[cfg(feature = "optimism")]
use reth_revm::optimism::RethL1BlockInfo; use reth_revm::optimism::RethL1BlockInfo;
#[cfg(feature = "optimism")] #[cfg(feature = "optimism")]
use reth_rpc_types::OptimismTransactionReceiptFields;
#[cfg(feature = "optimism")]
use revm::L1BlockInfo; use revm::L1BlockInfo;
#[cfg(feature = "optimism")] #[cfg(feature = "optimism")]
use std::ops::Div; use std::ops::Div;
@ -1201,22 +1203,28 @@ pub(crate) fn build_transaction_receipt_with_block_receipts(
// EIP-4844 fields // EIP-4844 fields
blob_gas_price: meta.excess_blob_gas.map(calc_blob_gasprice).map(U128::from), blob_gas_price: meta.excess_blob_gas.map(calc_blob_gasprice).map(U128::from),
blob_gas_used: transaction.transaction.blob_gas_used().map(U128::from), blob_gas_used: transaction.transaction.blob_gas_used().map(U128::from),
// Optimism fields
#[cfg(feature = "optimism")]
deposit_nonce: receipt.deposit_nonce.map(U64::from),
..Default::default() ..Default::default()
}; };
#[cfg(feature = "optimism")] #[cfg(feature = "optimism")]
if let Some(l1_block_info) = optimism_tx_meta.l1_block_info { {
if !transaction.is_deposit() { let mut op_fields = OptimismTransactionReceiptFields {
res_receipt.l1_fee = optimism_tx_meta.l1_fee; deposit_nonce: receipt.deposit_nonce.map(U64::from),
res_receipt.l1_gas_used = ..Default::default()
optimism_tx_meta.l1_data_gas.map(|dg| dg + l1_block_info.l1_fee_overhead); };
res_receipt.l1_fee_scalar =
Some(l1_block_info.l1_fee_scalar.div(U256::from(1_000_000))); if let Some(l1_block_info) = optimism_tx_meta.l1_block_info {
res_receipt.l1_gas_price = Some(l1_block_info.l1_base_fee); if !transaction.is_deposit() {
op_fields.l1_fee = optimism_tx_meta.l1_fee;
op_fields.l1_gas_used =
optimism_tx_meta.l1_data_gas.map(|dg| dg + l1_block_info.l1_fee_overhead);
op_fields.l1_fee_scalar =
Some(l1_block_info.l1_fee_scalar.div(U256::from(1_000_000)));
op_fields.l1_gas_price = Some(l1_block_info.l1_base_fee);
}
} }
res_receipt.other = op_fields.into();
} }
match transaction.transaction.kind() { match transaction.transaction.kind() {

View File

@ -124,7 +124,7 @@ mod implementations {
/// This implementation appends the transactions and uses [Vec::sort_by] function for sorting. /// This implementation appends the transactions and uses [Vec::sort_by] function for sorting.
#[derive(Default)] #[derive(Default)]
pub struct VecTxPoolSortStable { pub(crate) struct VecTxPoolSortStable {
inner: Vec<MockTransaction>, inner: Vec<MockTransaction>,
} }
@ -145,7 +145,7 @@ mod implementations {
/// This implementation appends the transactions and uses [Vec::sort_unstable_by] function for /// This implementation appends the transactions and uses [Vec::sort_unstable_by] function for
/// sorting. /// sorting.
#[derive(Default)] #[derive(Default)]
pub struct VecTxPoolSortUnstable { pub(crate) struct VecTxPoolSortUnstable {
inner: Vec<MockTransaction>, inner: Vec<MockTransaction>,
} }
@ -190,7 +190,7 @@ mod implementations {
/// This implementation uses BinaryHeap which is drained and reconstructed on each reordering. /// This implementation uses BinaryHeap which is drained and reconstructed on each reordering.
#[derive(Default)] #[derive(Default)]
pub struct BinaryHeapTxPool { pub(crate) struct BinaryHeapTxPool {
inner: BinaryHeap<MockTransactionWithPriority>, inner: BinaryHeap<MockTransactionWithPriority>,
base_fee: Option<u64>, base_fee: Option<u64>,
} }

View File

@ -96,7 +96,7 @@ unknown-git = "deny"
allow-git = [ allow-git = [
# TODO: remove, see ./Cargo.toml # TODO: remove, see ./Cargo.toml
"https://github.com/bluealloy/revm", "https://github.com/bluealloy/revm",
"https://github.com/alloy-rs/alloy",
"https://github.com/ethereum/c-kzg-4844", "https://github.com/ethereum/c-kzg-4844",
"https://github.com/sigp/discv5", "https://github.com/sigp/discv5",
"https://github.com/stevefan1999-personal/rust-igd", "https://github.com/stevefan1999-personal/rust-igd",