1use alloy_eips::BlockHashOrNumber;
2use alloy_primitives::B256;
3use reth_fs_util::FsPathError;
4use std::{
5 net::{IpAddr, Ipv4Addr, SocketAddr, ToSocketAddrs},
6path::Path,
7str::FromStr,
8time::Duration,
9};
1011/// Helper to parse a [Duration] from seconds
12pub fn parse_duration_from_secs(arg: &str) -> eyre::Result<Duration, std::num::ParseIntError> {
13let seconds = arg.parse()?;
14Ok(Duration::from_secs(seconds))
15}
1617/// Helper to parse a [Duration] from seconds if it's a number or milliseconds if the input contains
18/// a `ms` suffix:
19/// * `5ms` -> 5 milliseconds
20/// * `5` -> 5 seconds
21/// * `5s` -> 5 seconds
22pub fn parse_duration_from_secs_or_ms(
23 arg: &str,
24) -> eyre::Result<Duration, std::num::ParseIntError> {
25if arg.ends_with("ms") {
26arg.trim_end_matches("ms").parse().map(Duration::from_millis)
27 } else if arg.ends_with('s') {
28arg.trim_end_matches('s').parse().map(Duration::from_secs)
29 } else {
30arg.parse().map(Duration::from_secs)
31 }
32}
3334/// Parse [`BlockHashOrNumber`]
35pub fn hash_or_num_value_parser(value: &str) -> eyre::Result<BlockHashOrNumber, eyre::Error> {
36match B256::from_str(value) {
37Ok(hash) => Ok(BlockHashOrNumber::Hash(hash)),
38Err(_) => Ok(BlockHashOrNumber::Number(value.parse()?)),
39 }
40}
4142/// Error thrown while parsing a socket address.
43#[derive(thiserror::Error, Debug)]
44pub enum SocketAddressParsingError {
45/// Failed to convert the string into a socket addr
46#[error("could not parse socket address: {0}")]
47Io(#[from] std::io::Error),
48/// Input must not be empty
49#[error("cannot parse socket address from empty string")]
50Empty,
51/// Failed to parse the address
52#[error("could not parse socket address from {0}")]
53Parse(String),
54/// Failed to parse port
55#[error("could not parse port: {0}")]
56Port(#[from] std::num::ParseIntError),
57}
5859/// Parse a [`SocketAddr`] from a `str`.
60///
61/// The following formats are checked:
62///
63/// - If the value can be parsed as a `u16` or starts with `:` it is considered a port, and the
64/// hostname is set to `localhost`.
65/// - If the value contains `:` it is assumed to be the format `<host>:<port>`
66/// - Otherwise it is assumed to be a hostname
67///
68/// An error is returned if the value is empty.
69pub fn parse_socket_address(value: &str) -> eyre::Result<SocketAddr, SocketAddressParsingError> {
70if value.is_empty() {
71return Err(SocketAddressParsingError::Empty)
72 }
7374if let Some(port) = value.strip_prefix(':').or_else(|| value.strip_prefix("localhost:")) {
75let port: u16 = port.parse()?;
76return Ok(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port))
77 }
78if let Ok(port) = value.parse() {
79return Ok(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port))
80 }
81value82 .to_socket_addrs()?
83.next()
84 .ok_or_else(|| SocketAddressParsingError::Parse(value.to_string()))
85}
8687/// Wrapper around [`reth_fs_util::read_json_file`] which can be used as a clap value parser.
88pub fn read_json_from_file<T: serde::de::DeserializeOwned>(path: &str) -> Result<T, FsPathError> {
89 reth_fs_util::read_json_file(Path::new(path))
90}
9192/// Parses an ether value from a string.
93///
94/// The amount in eth like "1.05" will be interpreted in wei (1.05 * 1e18).
95/// Supports both decimal and integer inputs.
96///
97/// # Examples
98/// - "1.05" -> 1.05 ETH = 1.05 * 10^18 wei
99/// - "2" -> 2 ETH = 2 * 10^18 wei
100pub fn parse_ether_value(value: &str) -> eyre::Result<u128> {
101let eth = value.parse::<f64>()?;
102if eth.is_sign_negative() {
103return Err(eyre::eyre!("Ether value cannot be negative"))
104 }
105let wei = eth * 1e18;
106Ok(weias u128)
107}
108109#[cfg(test)]
110mod tests {
111use super::*;
112use rand::Rng;
113114#[test]
115fn parse_socket_addresses() {
116for value in ["localhost:9000", ":9000", "9000"] {
117let socket_addr = parse_socket_address(value)
118 .unwrap_or_else(|_| panic!("could not parse socket address: {value}"));
119120assert!(socket_addr.ip().is_loopback());
121assert_eq!(socket_addr.port(), 9000);
122 }
123 }
124125#[test]
126fn parse_socket_address_random() {
127let port: u16 = rand::rng().random();
128129for value in [format!("localhost:{port}"), format!(":{port}"), port.to_string()] {
130let socket_addr = parse_socket_address(&value)
131 .unwrap_or_else(|_| panic!("could not parse socket address: {value}"));
132133assert!(socket_addr.ip().is_loopback());
134assert_eq!(socket_addr.port(), port);
135 }
136 }
137138#[test]
139fn parse_ms_or_seconds() {
140let ms = parse_duration_from_secs_or_ms("5ms").unwrap();
141assert_eq!(ms, Duration::from_millis(5));
142143let seconds = parse_duration_from_secs_or_ms("5").unwrap();
144assert_eq!(seconds, Duration::from_secs(5));
145146let seconds = parse_duration_from_secs_or_ms("5s").unwrap();
147assert_eq!(seconds, Duration::from_secs(5));
148149assert!(parse_duration_from_secs_or_ms("5ns").is_err());
150 }
151152#[test]
153fn parse_ether_values() {
154// Test basic decimal value
155let wei = parse_ether_value("1.05").unwrap();
156assert_eq!(wei, 1_050_000_000_000_000_000u128);
157158// Test integer value
159let wei = parse_ether_value("2").unwrap();
160assert_eq!(wei, 2_000_000_000_000_000_000u128);
161162// Test zero
163let wei = parse_ether_value("0").unwrap();
164assert_eq!(wei, 0);
165166// Test negative value fails
167assert!(parse_ether_value("-1").is_err());
168169// Test invalid input fails
170assert!(parse_ether_value("abc").is_err());
171 }
172}