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