#![doc = include_str!("../README.md")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/alloy.jpg",
html_favicon_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/favicon.ico"
)]
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
use alloy_primitives::{BlockHash, ChainId, TxHash, B256, U256};
use serde::{Deserialize, Deserializer, Serialize};
use std::collections::BTreeMap;
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Forking {
pub json_rpc_url: Option<String>,
pub block_number: Option<u64>,
}
impl<'de> serde::Deserialize<'de> for Forking {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct ForkOpts {
json_rpc_url: Option<String>,
#[serde(default, with = "alloy_serde::quantity::opt")]
block_number: Option<u64>,
}
#[derive(serde::Deserialize)]
struct Tagged {
forking: ForkOpts,
}
#[derive(serde::Deserialize)]
#[serde(untagged)]
enum ForkingVariants {
Tagged(Tagged),
Fork(ForkOpts),
}
let f = match ForkingVariants::deserialize(deserializer)? {
ForkingVariants::Fork(ForkOpts { json_rpc_url, block_number }) => {
Self { json_rpc_url, block_number }
}
ForkingVariants::Tagged(f) => {
Self { json_rpc_url: f.forking.json_rpc_url, block_number: f.forking.block_number }
}
};
Ok(f)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NodeInfo {
#[serde(with = "alloy_serde::quantity")]
pub current_block_number: u64,
pub current_block_timestamp: u64,
pub current_block_hash: BlockHash,
pub hard_fork: String,
#[doc(alias = "tx_order")]
pub transaction_order: String,
pub environment: NodeEnvironment,
pub fork_config: NodeForkConfig,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NodeEnvironment {
pub base_fee: U256,
pub chain_id: ChainId,
pub gas_limit: U256,
pub gas_price: U256,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NodeForkConfig {
pub fork_url: Option<String>,
pub fork_block_number: Option<u64>,
pub fork_retry_backoff: Option<u128>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Metadata {
pub client_version: String,
pub chain_id: ChainId,
pub instance_id: B256,
pub latest_block_number: u64,
pub latest_block_hash: BlockHash,
pub forked_network: Option<ForkedNetwork>,
pub snapshots: BTreeMap<U256, (u64, B256)>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ForkedNetwork {
pub chain_id: ChainId,
pub fork_block_number: u64,
pub fork_block_hash: TxHash,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum MineOptions {
Options {
#[serde(with = "alloy_serde::quantity::opt")]
timestamp: Option<u64>,
blocks: Option<u64>,
},
#[serde(with = "alloy_serde::quantity::opt")]
Timestamp(Option<u64>),
}
impl Default for MineOptions {
fn default() -> Self {
Self::Options { timestamp: None, blocks: None }
}
}
#[cfg(test)]
mod tests {
use super::*;
use similar_asserts::assert_eq;
#[test]
fn test_serde_forking_deserialization() {
let json_data = r#"{"forking": {"jsonRpcUrl": "https://ethereumpublicnode.com","blockNumber": "18441649"}}"#;
let forking: Forking = serde_json::from_str(json_data).unwrap();
assert_eq!(
forking,
Forking {
json_rpc_url: Some("https://ethereumpublicnode.com".into()),
block_number: Some(18441649)
}
);
let json_data = r#"{"forking": {"jsonRpcUrl": "https://ethereumpublicnode.com"}}"#;
let forking: Forking = serde_json::from_str(json_data).unwrap();
assert_eq!(
forking,
Forking {
json_rpc_url: Some("https://ethereumpublicnode.com".into()),
block_number: None
}
);
let json_data = r#"{"forking": {"blockNumber": "18441649"}}"#;
let forking: Forking =
serde_json::from_str(json_data).expect("Failed to deserialize forking object");
assert_eq!(forking, Forking { json_rpc_url: None, block_number: Some(18441649) });
}
#[test]
fn test_serde_deserialize_options_with_values() {
let data = r#"{"timestamp": 1620000000, "blocks": 10}"#;
let deserialized: MineOptions = serde_json::from_str(data).expect("Deserialization failed");
assert_eq!(
deserialized,
MineOptions::Options { timestamp: Some(1620000000), blocks: Some(10) }
);
let data = r#"{"timestamp": "0x608f3d00", "blocks": 10}"#;
let deserialized: MineOptions = serde_json::from_str(data).expect("Deserialization failed");
assert_eq!(
deserialized,
MineOptions::Options { timestamp: Some(1620000000), blocks: Some(10) }
);
}
#[test]
fn test_serde_deserialize_options_with_timestamp() {
let data = r#"{"timestamp":"1620000000"}"#;
let deserialized: MineOptions = serde_json::from_str(data).expect("Deserialization failed");
assert_eq!(
deserialized,
MineOptions::Options { timestamp: Some(1620000000), blocks: None }
);
let data = r#"{"timestamp":"0x608f3d00"}"#;
let deserialized: MineOptions = serde_json::from_str(data).expect("Deserialization failed");
assert_eq!(
deserialized,
MineOptions::Options { timestamp: Some(1620000000), blocks: None }
);
}
#[test]
fn test_serde_deserialize_timestamp() {
let data = r#""1620000000""#;
let deserialized: MineOptions = serde_json::from_str(data).expect("Deserialization failed");
assert_eq!(deserialized, MineOptions::Timestamp(Some(1620000000)));
let data = r#""0x608f3d00""#;
let deserialized: MineOptions = serde_json::from_str(data).expect("Deserialization failed");
assert_eq!(deserialized, MineOptions::Timestamp(Some(1620000000)));
}
}