1use std::{num::NonZeroUsize, sync::OnceLock, time::Duration};
4
5use clap::{builder::Resettable, Args};
6use humantime::{format_duration, parse_duration};
7use reth_engine_local::DEFAULT_FINALITY_DEPTH;
8
9const DEFAULT_MNEMONIC: &str = "test test test test test test test test test test test junk";
10
11static DEV_DEFAULTS: OnceLock<DefaultDevArgs> = OnceLock::new();
13
14#[derive(Debug, Args, PartialEq, Eq, Clone)]
16#[command(next_help_heading = "Dev testnet")]
17pub struct DevArgs {
18 #[arg(long = "dev", alias = "auto-mine", help_heading = "Dev testnet", default_value_t = DefaultDevArgs::get_global().dev, verbatim_doc_comment)]
26 pub dev: bool,
27
28 #[arg(
30 long = "dev.block-max-transactions",
31 help_heading = "Dev testnet",
32 conflicts_with = "block_time",
33 default_value = Resettable::from(DefaultDevArgs::get_global().block_max_transactions.map(|v| v.to_string().into()))
34 )]
35 pub block_max_transactions: Option<usize>,
36
37 #[arg(
42 long = "dev.block-time",
43 help_heading = "Dev testnet",
44 conflicts_with = "block_max_transactions",
45 value_parser = parse_duration,
46 default_value = Resettable::from(DefaultDevArgs::get_global().block_time.map(|v| format_duration(v).to_string().into())),
47 verbatim_doc_comment
48 )]
49 pub block_time: Option<Duration>,
50
51 #[arg(
55 long = "dev.finality-depth",
56 help_heading = "Dev testnet",
57 default_value_t = DefaultDevArgs::get_global().finality_depth,
58 verbatim_doc_comment
59 )]
60 pub finality_depth: NonZeroUsize,
61
62 #[arg(
71 long = "dev.payload-wait-time",
72 help_heading = "Dev testnet",
73 value_parser = parse_duration,
74 default_value = Resettable::from(DefaultDevArgs::get_global().payload_wait_time.map(|v| format_duration(v).to_string().into())),
75 verbatim_doc_comment
76 )]
77 pub payload_wait_time: Option<Duration>,
78
79 #[arg(
81 long = "dev.mnemonic",
82 help_heading = "Dev testnet",
83 value_name = "MNEMONIC",
84 requires = "dev",
85 verbatim_doc_comment,
86 default_value_t = DefaultDevArgs::get_global().dev_mnemonic.clone()
87 )]
88 pub dev_mnemonic: String,
89}
90
91#[derive(Debug, Clone)]
95pub struct DefaultDevArgs {
96 pub dev: bool,
98 pub block_max_transactions: Option<usize>,
100 pub block_time: Option<Duration>,
102 pub finality_depth: NonZeroUsize,
104 pub payload_wait_time: Option<Duration>,
106 pub dev_mnemonic: String,
108}
109
110impl DefaultDevArgs {
111 pub fn try_init(self) -> Result<(), Self> {
113 DEV_DEFAULTS.set(self)
114 }
115
116 pub fn get_global() -> &'static Self {
118 DEV_DEFAULTS.get_or_init(Self::default)
119 }
120
121 pub const fn with_dev(mut self, dev: bool) -> Self {
123 self.dev = dev;
124 self
125 }
126
127 pub const fn with_block_max_transactions(mut self, count: Option<usize>) -> Self {
129 self.block_max_transactions = count;
130 self
131 }
132
133 pub const fn with_block_time(mut self, block_time: Option<Duration>) -> Self {
135 self.block_time = block_time;
136 self
137 }
138
139 pub const fn with_finality_depth(mut self, finality_depth: NonZeroUsize) -> Self {
141 self.finality_depth = finality_depth;
142 self
143 }
144
145 pub const fn with_payload_wait_time(mut self, payload_wait_time: Option<Duration>) -> Self {
147 self.payload_wait_time = payload_wait_time;
148 self
149 }
150
151 pub fn with_dev_mnemonic(mut self, dev_mnemonic: String) -> Self {
153 self.dev_mnemonic = dev_mnemonic;
154 self
155 }
156}
157
158impl Default for DefaultDevArgs {
159 fn default() -> Self {
160 Self {
161 dev: false,
162 block_max_transactions: None,
163 block_time: None,
164 finality_depth: DEFAULT_FINALITY_DEPTH,
165 payload_wait_time: None,
166 dev_mnemonic: DEFAULT_MNEMONIC.to_string(),
167 }
168 }
169}
170
171impl Default for DevArgs {
172 fn default() -> Self {
173 let DefaultDevArgs {
174 dev,
175 block_max_transactions,
176 block_time,
177 finality_depth,
178 payload_wait_time,
179 dev_mnemonic,
180 } = DefaultDevArgs::get_global().clone();
181 Self {
182 dev,
183 block_max_transactions,
184 block_time,
185 finality_depth,
186 payload_wait_time,
187 dev_mnemonic,
188 }
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195 use clap::Parser;
196
197 #[derive(Parser)]
199 struct CommandParser<T: Args> {
200 #[command(flatten)]
201 args: T,
202 }
203
204 #[test]
205 fn test_parse_dev_args() {
206 let args = CommandParser::<DevArgs>::parse_from(["reth"]).args;
207 assert_eq!(
208 args,
209 DevArgs {
210 dev: false,
211 block_max_transactions: None,
212 block_time: None,
213 finality_depth: DEFAULT_FINALITY_DEPTH,
214 payload_wait_time: None,
215 dev_mnemonic: DEFAULT_MNEMONIC.to_string(),
216 }
217 );
218
219 let args = CommandParser::<DevArgs>::parse_from(["reth", "--dev"]).args;
220 assert_eq!(
221 args,
222 DevArgs {
223 dev: true,
224 block_max_transactions: None,
225 block_time: None,
226 finality_depth: DEFAULT_FINALITY_DEPTH,
227 payload_wait_time: None,
228 dev_mnemonic: DEFAULT_MNEMONIC.to_string(),
229 }
230 );
231
232 let args = CommandParser::<DevArgs>::parse_from(["reth", "--auto-mine"]).args;
233 assert_eq!(
234 args,
235 DevArgs {
236 dev: true,
237 block_max_transactions: None,
238 block_time: None,
239 finality_depth: DEFAULT_FINALITY_DEPTH,
240 payload_wait_time: None,
241 dev_mnemonic: DEFAULT_MNEMONIC.to_string(),
242 }
243 );
244
245 let args = CommandParser::<DevArgs>::parse_from([
246 "reth",
247 "--dev",
248 "--dev.block-max-transactions",
249 "2",
250 ])
251 .args;
252 assert_eq!(
253 args,
254 DevArgs {
255 dev: true,
256 block_max_transactions: Some(2),
257 block_time: None,
258 finality_depth: DEFAULT_FINALITY_DEPTH,
259 payload_wait_time: None,
260 dev_mnemonic: DEFAULT_MNEMONIC.to_string(),
261 }
262 );
263
264 let args =
265 CommandParser::<DevArgs>::parse_from(["reth", "--dev", "--dev.block-time", "1s"]).args;
266 assert_eq!(
267 args,
268 DevArgs {
269 dev: true,
270 block_max_transactions: None,
271 block_time: Some(std::time::Duration::from_secs(1)),
272 finality_depth: DEFAULT_FINALITY_DEPTH,
273 payload_wait_time: None,
274 dev_mnemonic: DEFAULT_MNEMONIC.to_string(),
275 }
276 );
277
278 let args =
279 CommandParser::<DevArgs>::parse_from(["reth", "--dev", "--dev.finality-depth", "1"])
280 .args;
281 assert_eq!(args.finality_depth, NonZeroUsize::new(1).unwrap());
282 }
283
284 #[test]
285 fn test_rejects_zero_finality_depth() {
286 assert!(CommandParser::<DevArgs>::try_parse_from([
287 "reth",
288 "--dev",
289 "--dev.finality-depth",
290 "0",
291 ])
292 .is_err());
293 }
294
295 #[test]
296 fn test_parse_dev_args_conflicts() {
297 let args = CommandParser::<DevArgs>::try_parse_from([
298 "reth",
299 "--dev",
300 "--dev.block-max-transactions",
301 "2",
302 "--dev.block-time",
303 "1s",
304 ]);
305 assert!(args.is_err());
306 }
307
308 #[test]
309 fn dev_args_default_sanity_check() {
310 let default_args = DevArgs::default();
311 let args = CommandParser::<DevArgs>::parse_from(["reth"]).args;
312 assert_eq!(args, default_args);
313 }
314}