1use {
2 crate::keypair::{
3 keypair_from_seed_phrase, pubkey_from_path, resolve_signer_from_path, signer_from_path,
4 ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG,
5 },
6 chrono::DateTime,
7 clap::ArgMatches,
8 solana_bls_signatures::Pubkey as BLSPubkey,
9 solana_clock::UnixTimestamp,
10 solana_cluster_type::ClusterType,
11 solana_commitment_config::CommitmentConfig,
12 solana_keypair::{read_keypair_file, Keypair},
13 solana_native_token::LAMPORTS_PER_SOL,
14 solana_pubkey::Pubkey,
15 solana_remote_wallet::remote_wallet::RemoteWalletManager,
16 solana_signature::Signature,
17 solana_signer::Signer,
18 std::{io, num::ParseIntError, rc::Rc, str::FromStr},
19};
20
21pub const STDOUT_OUTFILE_TOKEN: &str = "-";
23
24pub fn values_of<T>(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<T>>
26where
27 T: std::str::FromStr,
28 <T as std::str::FromStr>::Err: std::fmt::Debug,
29{
30 matches
31 .values_of(name)
32 .map(|xs| xs.map(|x| x.parse::<T>().unwrap()).collect())
33}
34
35pub fn value_of<T>(matches: &ArgMatches<'_>, name: &str) -> Option<T>
37where
38 T: std::str::FromStr,
39 <T as std::str::FromStr>::Err: std::fmt::Debug,
40{
41 if let Some(value) = matches.value_of(name) {
42 value.parse::<T>().ok()
43 } else {
44 None
45 }
46}
47
48pub fn unix_timestamp_from_rfc3339_datetime(
49 matches: &ArgMatches<'_>,
50 name: &str,
51) -> Option<UnixTimestamp> {
52 matches.value_of(name).and_then(|value| {
53 DateTime::parse_from_rfc3339(value)
54 .ok()
55 .map(|date_time| date_time.timestamp())
56 })
57}
58
59pub fn keypair_of(matches: &ArgMatches<'_>, name: &str) -> Option<Keypair> {
61 if let Some(value) = matches.value_of(name) {
62 if value == ASK_KEYWORD {
63 let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
64 keypair_from_seed_phrase(name, skip_validation, true, None, true).ok()
65 } else {
66 read_keypair_file(value).ok()
67 }
68 } else {
69 None
70 }
71}
72
73pub fn keypairs_of(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<Keypair>> {
74 matches.values_of(name).map(|values| {
75 values
76 .filter_map(|value| {
77 if value == ASK_KEYWORD {
78 let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
79 keypair_from_seed_phrase(name, skip_validation, true, None, true).ok()
80 } else {
81 read_keypair_file(value).ok()
82 }
83 })
84 .collect()
85 })
86}
87
88pub fn pubkey_of(matches: &ArgMatches<'_>, name: &str) -> Option<Pubkey> {
91 value_of(matches, name).or_else(|| keypair_of(matches, name).map(|keypair| keypair.pubkey()))
92}
93
94pub fn pubkeys_of(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<Pubkey>> {
95 matches.values_of(name).map(|values| {
96 values
97 .map(|value| {
98 value.parse::<Pubkey>().unwrap_or_else(|_| {
99 read_keypair_file(value)
100 .expect("read_keypair_file failed")
101 .pubkey()
102 })
103 })
104 .collect()
105 })
106}
107
108pub fn bls_pubkeys_of(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<BLSPubkey>> {
109 matches.values_of(name).map(|values| {
110 values
111 .map(|value| {
112 BLSPubkey::from_str(value).unwrap_or_else(|_| {
113 panic!("Failed to parse BLS public key from value: {value}")
114 })
115 })
116 .collect()
117 })
118}
119
120pub fn pubkeys_sigs_of(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<(Pubkey, Signature)>> {
122 matches.values_of(name).map(|values| {
123 values
124 .map(|pubkey_signer_string| {
125 let mut signer = pubkey_signer_string.split('=');
126 let key = Pubkey::from_str(signer.next().unwrap()).unwrap();
127 let sig = Signature::from_str(signer.next().unwrap()).unwrap();
128 (key, sig)
129 })
130 .collect()
131 })
132}
133
134#[allow(clippy::type_complexity)]
136pub fn signer_of(
137 matches: &ArgMatches<'_>,
138 name: &str,
139 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
140) -> Result<(Option<Box<dyn Signer>>, Option<Pubkey>), Box<dyn std::error::Error>> {
141 if let Some(location) = matches.value_of(name) {
142 let signer = signer_from_path(matches, location, name, wallet_manager)?;
143 let signer_pubkey = signer.pubkey();
144 Ok((Some(signer), Some(signer_pubkey)))
145 } else {
146 Ok((None, None))
147 }
148}
149
150pub fn pubkey_of_signer(
151 matches: &ArgMatches<'_>,
152 name: &str,
153 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
154) -> Result<Option<Pubkey>, Box<dyn std::error::Error>> {
155 if let Some(location) = matches.value_of(name) {
156 Ok(Some(pubkey_from_path(
157 matches,
158 location,
159 name,
160 wallet_manager,
161 )?))
162 } else {
163 Ok(None)
164 }
165}
166
167pub fn pubkeys_of_multiple_signers(
168 matches: &ArgMatches<'_>,
169 name: &str,
170 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
171) -> Result<Option<Vec<Pubkey>>, Box<dyn std::error::Error>> {
172 if let Some(pubkey_matches) = matches.values_of(name) {
173 let mut pubkeys: Vec<Pubkey> = vec![];
174 for signer in pubkey_matches {
175 pubkeys.push(pubkey_from_path(matches, signer, name, wallet_manager)?);
176 }
177 Ok(Some(pubkeys))
178 } else {
179 Ok(None)
180 }
181}
182
183pub fn resolve_signer(
184 matches: &ArgMatches<'_>,
185 name: &str,
186 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
187) -> Result<Option<String>, Box<dyn std::error::Error>> {
188 resolve_signer_from_path(
189 matches,
190 matches.value_of(name).unwrap(),
191 name,
192 wallet_manager,
193 )
194}
195
196pub fn lamports_of_sol(matches: &ArgMatches<'_>, name: &str) -> Option<u64> {
201 matches.value_of(name).and_then(|value| {
202 if value == "." {
203 None
204 } else {
205 let (sol, lamports) = value.split_once('.').unwrap_or((value, ""));
206 let sol = if sol.is_empty() {
207 0
208 } else {
209 sol.parse::<u64>().ok()?
210 };
211 let lamports = if lamports.is_empty() {
212 0
213 } else {
214 format!("{lamports:0<9}")[..9].parse().ok()?
215 };
216 Some(
217 LAMPORTS_PER_SOL
218 .saturating_mul(sol)
219 .saturating_add(lamports),
220 )
221 }
222 })
223}
224
225pub fn cluster_type_of(matches: &ArgMatches<'_>, name: &str) -> Option<ClusterType> {
226 value_of(matches, name)
227}
228
229pub fn commitment_of(matches: &ArgMatches<'_>, name: &str) -> Option<CommitmentConfig> {
230 matches
231 .value_of(name)
232 .map(|value| CommitmentConfig::from_str(value).unwrap_or_default())
233}
234
235pub fn parse_cpu_ranges(data: &str) -> Result<Vec<usize>, io::Error> {
240 data.split(',')
241 .map(|range| {
242 let mut iter = range
243 .split('-')
244 .map(|s| s.parse::<usize>().map_err(|ParseIntError { .. }| range));
245 let start = iter.next().unwrap()?; let end = match iter.next() {
247 None => start,
248 Some(end) => {
249 if iter.next().is_some() {
250 return Err(range);
251 }
252 end?
253 }
254 };
255 Ok(start..=end)
256 })
257 .try_fold(Vec::new(), |mut cpus, range| {
258 let range = range.map_err(|range| io::Error::new(io::ErrorKind::InvalidData, range))?;
259 cpus.extend(range);
260 Ok(cpus)
261 })
262}
263
264#[cfg(test)]
265mod tests {
266 use {
267 super::*,
268 clap::{App, Arg},
269 solana_bls_signatures::{keypair::Keypair as BLSKeypair, Pubkey as BLSPubkey},
270 solana_keypair::write_keypair_file,
271 std::fs,
272 };
273
274 fn app<'ab, 'v>() -> App<'ab, 'v> {
275 App::new("test")
276 .arg(
277 Arg::with_name("multiple")
278 .long("multiple")
279 .takes_value(true)
280 .multiple(true),
281 )
282 .arg(Arg::with_name("single").takes_value(true).long("single"))
283 .arg(Arg::with_name("unit").takes_value(true).long("unit"))
284 }
285
286 fn tmp_file_path(name: &str, pubkey: &Pubkey) -> String {
287 use std::env;
288 let out_dir = env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
289
290 format!("{out_dir}/tmp/{name}-{pubkey}")
291 }
292
293 #[test]
294 fn test_values_of() {
295 let matches = app().get_matches_from(vec!["test", "--multiple", "50", "--multiple", "39"]);
296 assert_eq!(values_of(&matches, "multiple"), Some(vec![50, 39]));
297 assert_eq!(values_of::<u64>(&matches, "single"), None);
298
299 let pubkey0 = solana_pubkey::new_rand();
300 let pubkey1 = solana_pubkey::new_rand();
301 let matches = app().get_matches_from(vec![
302 "test",
303 "--multiple",
304 &pubkey0.to_string(),
305 "--multiple",
306 &pubkey1.to_string(),
307 ]);
308 assert_eq!(
309 values_of(&matches, "multiple"),
310 Some(vec![pubkey0, pubkey1])
311 );
312 }
313
314 #[test]
315 fn test_value_of() {
316 let matches = app().get_matches_from(vec!["test", "--single", "50"]);
317 assert_eq!(value_of(&matches, "single"), Some(50));
318 assert_eq!(value_of::<u64>(&matches, "multiple"), None);
319
320 let pubkey = solana_pubkey::new_rand();
321 let matches = app().get_matches_from(vec!["test", "--single", &pubkey.to_string()]);
322 assert_eq!(value_of(&matches, "single"), Some(pubkey));
323 }
324
325 #[test]
326 fn test_keypair_of() {
327 let keypair = Keypair::new();
328 let outfile = tmp_file_path("test_keypair_of.json", &keypair.pubkey());
329 let _ = write_keypair_file(&keypair, &outfile).unwrap();
330
331 let matches = app().get_matches_from(vec!["test", "--single", &outfile]);
332 assert_eq!(
333 keypair_of(&matches, "single").unwrap().pubkey(),
334 keypair.pubkey()
335 );
336 assert!(keypair_of(&matches, "multiple").is_none());
337
338 let matches = app().get_matches_from(vec!["test", "--single", "random_keypair_file.json"]);
339 assert!(keypair_of(&matches, "single").is_none());
340
341 fs::remove_file(&outfile).unwrap();
342 }
343
344 #[test]
345 fn test_pubkey_of() {
346 let keypair = Keypair::new();
347 let outfile = tmp_file_path("test_pubkey_of.json", &keypair.pubkey());
348 let _ = write_keypair_file(&keypair, &outfile).unwrap();
349
350 let matches = app().get_matches_from(vec!["test", "--single", &outfile]);
351 assert_eq!(pubkey_of(&matches, "single"), Some(keypair.pubkey()));
352 assert_eq!(pubkey_of(&matches, "multiple"), None);
353
354 let matches =
355 app().get_matches_from(vec!["test", "--single", &keypair.pubkey().to_string()]);
356 assert_eq!(pubkey_of(&matches, "single"), Some(keypair.pubkey()));
357
358 let matches = app().get_matches_from(vec!["test", "--single", "random_keypair_file.json"]);
359 assert_eq!(pubkey_of(&matches, "single"), None);
360
361 fs::remove_file(&outfile).unwrap();
362 }
363
364 #[test]
365 fn test_pubkeys_of() {
366 let keypair = Keypair::new();
367 let outfile = tmp_file_path("test_pubkeys_of.json", &keypair.pubkey());
368 let _ = write_keypair_file(&keypair, &outfile).unwrap();
369
370 let matches = app().get_matches_from(vec![
371 "test",
372 "--multiple",
373 &keypair.pubkey().to_string(),
374 "--multiple",
375 &outfile,
376 ]);
377 assert_eq!(
378 pubkeys_of(&matches, "multiple"),
379 Some(vec![keypair.pubkey(), keypair.pubkey()])
380 );
381 fs::remove_file(&outfile).unwrap();
382 }
383
384 #[test]
385 fn test_pubkeys_sigs_of() {
386 let key1 = solana_pubkey::new_rand();
387 let key2 = solana_pubkey::new_rand();
388 let sig1 = Keypair::new().sign_message(&[0u8]);
389 let sig2 = Keypair::new().sign_message(&[1u8]);
390 let signer1 = format!("{key1}={sig1}");
391 let signer2 = format!("{key2}={sig2}");
392 let matches =
393 app().get_matches_from(vec!["test", "--multiple", &signer1, "--multiple", &signer2]);
394 assert_eq!(
395 pubkeys_sigs_of(&matches, "multiple"),
396 Some(vec![(key1, sig1), (key2, sig2)])
397 );
398 }
399
400 #[test]
401 #[ignore = "historical reference; shows float behavior fixed in pull #4988"]
402 fn test_lamports_of_sol_origin() {
403 use solana_native_token::sol_str_to_lamports;
404 pub fn lamports_of_sol(matches: &ArgMatches<'_>, name: &str) -> Option<u64> {
405 matches.value_of(name).and_then(sol_str_to_lamports)
406 }
407
408 let matches = app().get_matches_from(vec!["test", "--single", "50"]);
409 assert_eq!(lamports_of_sol(&matches, "single"), Some(50_000_000_000));
410 assert_eq!(lamports_of_sol(&matches, "multiple"), None);
411 let matches = app().get_matches_from(vec!["test", "--single", "1.5"]);
412 assert_eq!(lamports_of_sol(&matches, "single"), Some(1_500_000_000));
413 assert_eq!(lamports_of_sol(&matches, "multiple"), None);
414 let matches = app().get_matches_from(vec!["test", "--single", "0.03"]);
415 assert_eq!(lamports_of_sol(&matches, "single"), Some(30_000_000));
416 let matches = app().get_matches_from(vec!["test", "--single", ".03"]);
417 assert_eq!(lamports_of_sol(&matches, "single"), Some(30_000_000));
418 let matches = app().get_matches_from(vec!["test", "--single", "1."]);
419 assert_eq!(lamports_of_sol(&matches, "single"), Some(1_000_000_000));
420 let matches = app().get_matches_from(vec!["test", "--single", ".0"]);
421 assert_eq!(lamports_of_sol(&matches, "single"), Some(0));
422 let matches = app().get_matches_from(vec!["test", "--single", "."]);
423 assert_eq!(lamports_of_sol(&matches, "single"), None);
424 let matches = app().get_matches_from(vec!["test", "--single", "1.000000015"]);
426 assert_ne!(lamports_of_sol(&matches, "single"), Some(1_000_000_015));
427 let matches = app().get_matches_from(vec!["test", "--single", "0.0157"]);
428 assert_ne!(lamports_of_sol(&matches, "single"), Some(15_700_000));
429 let matches = app().get_matches_from(vec!["test", "--single", "0.5025"]);
430 assert_ne!(lamports_of_sol(&matches, "single"), Some(502_500_000));
431 }
432
433 #[test]
434 fn test_bls_pubkeys_of() {
435 let bls_pubkey1: BLSPubkey = BLSKeypair::new().public;
436 let bls_pubkey2: BLSPubkey = BLSKeypair::new().public;
437 let matches = app().get_matches_from(vec![
438 "test",
439 "--multiple",
440 &bls_pubkey1.to_string(),
441 "--multiple",
442 &bls_pubkey2.to_string(),
443 ]);
444 assert_eq!(
445 bls_pubkeys_of(&matches, "multiple"),
446 Some(vec![bls_pubkey1, bls_pubkey2])
447 );
448 }
449
450 #[test]
451 fn test_lamports_of_sol() {
452 let matches = app().get_matches_from(vec!["test", "--single", "50"]);
453 assert_eq!(lamports_of_sol(&matches, "single"), Some(50_000_000_000));
454 assert_eq!(lamports_of_sol(&matches, "multiple"), None);
455 let matches = app().get_matches_from(vec!["test", "--single", "1.5"]);
456 assert_eq!(lamports_of_sol(&matches, "single"), Some(1_500_000_000));
457 assert_eq!(lamports_of_sol(&matches, "multiple"), None);
458 let matches = app().get_matches_from(vec!["test", "--single", "0.03"]);
459 assert_eq!(lamports_of_sol(&matches, "single"), Some(30_000_000));
460 let matches = app().get_matches_from(vec!["test", "--single", ".03"]);
461 assert_eq!(lamports_of_sol(&matches, "single"), Some(30_000_000));
462 let matches = app().get_matches_from(vec!["test", "--single", "1."]);
463 assert_eq!(lamports_of_sol(&matches, "single"), Some(1_000_000_000));
464 let matches = app().get_matches_from(vec!["test", "--single", ".0"]);
465 assert_eq!(lamports_of_sol(&matches, "single"), Some(0));
466 let matches = app().get_matches_from(vec!["test", "--single", "."]);
467 assert_eq!(lamports_of_sol(&matches, "single"), None);
468 let matches = app().get_matches_from(vec!["test", "--single", "1.000000015"]);
470 assert_eq!(lamports_of_sol(&matches, "single"), Some(1_000_000_015));
471 let matches = app().get_matches_from(vec!["test", "--single", "0.0157"]);
472 assert_eq!(lamports_of_sol(&matches, "single"), Some(15_700_000));
473 let matches = app().get_matches_from(vec!["test", "--single", "0.5025"]);
474 assert_eq!(lamports_of_sol(&matches, "single"), Some(502_500_000));
475 let matches = app().get_matches_from(vec!["test", "--single", "0.1234567891"]);
477 assert_eq!(lamports_of_sol(&matches, "single"), Some(123_456_789));
478 let matches = app().get_matches_from(vec!["test", "--single", "0.1234567899"]);
479 assert_eq!(lamports_of_sol(&matches, "single"), Some(123_456_789));
480 let matches = app().get_matches_from(vec!["test", "--single", "1.000.4567899"]);
481 assert_eq!(lamports_of_sol(&matches, "single"), None);
482 let matches = app().get_matches_from(vec!["test", "--single", "6,998"]);
483 assert_eq!(lamports_of_sol(&matches, "single"), None);
484 let matches = app().get_matches_from(vec!["test", "--single", "6,998.00"]);
485 assert_eq!(lamports_of_sol(&matches, "single"), None);
486 }
487}