mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat(rpc): use alloy-rpc-types for ethereum-related types (#5947)
This commit is contained in:
22
Cargo.lock
generated
22
Cargo.lock
generated
@ -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]]
|
||||||
|
|||||||
@ -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 }
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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)),
|
||||||
|
|||||||
@ -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"]
|
||||||
|
|||||||
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 = []
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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};
|
||||||
|
|||||||
@ -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();
|
|
||||||
}
|
|
||||||
@ -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\"");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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;
|
|
||||||
|
|||||||
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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)>,
|
|
||||||
}
|
|
||||||
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
//! Types for tracing
|
|
||||||
|
|
||||||
pub mod common;
|
|
||||||
pub mod filter;
|
|
||||||
pub mod geth;
|
|
||||||
pub mod parity;
|
|
||||||
pub mod tracerequest;
|
|
||||||
@ -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 VM’s state throughout the execution of the transaction,
|
|
||||||
/// including for any subcalls.
|
|
||||||
VmTrace,
|
|
||||||
/// Provides information detailing all altered portions of the Ethereum state made due to the
|
|
||||||
/// execution of the transaction.
|
|
||||||
StateDiff,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The Outcome of a traced transaction with optional settings
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct TraceResults {
|
|
||||||
/// Output of the trace
|
|
||||||
pub output: Bytes,
|
|
||||||
/// Enabled if [TraceType::StateDiff] is provided
|
|
||||||
pub state_diff: Option<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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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>,
|
|
||||||
}
|
|
||||||
@ -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>;
|
|
||||||
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -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>,
|
|
||||||
}
|
|
||||||
@ -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(),
|
||||||
|
|||||||
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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
|
||||||
|
|||||||
@ -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 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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::*;
|
|
||||||
|
|||||||
@ -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};
|
||||||
|
|||||||
@ -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() {
|
||||||
|
|||||||
@ -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>,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user