use { crate::{ input_parsers::{keypair_of, keypairs_of, pubkey_of, pubkeys_of}, keypair::{pubkey_from_path, resolve_signer_from_path, signer_from_path}, }, clap::ArgMatches, solana_remote_wallet::remote_wallet::RemoteWalletManager, solana_sdk::{ pubkey::Pubkey, signature::{Keypair, Signature, Signer}, }, std::{error, rc::Rc, str::FromStr}, }; // Sentinel value used to indicate to write to screen instead of file pub const STDOUT_OUTFILE_TOKEN: &str = "-"; // Return the keypair for an argument with filename `name` or `None` if not present wrapped inside `Result`. pub fn try_keypair_of( matches: &ArgMatches, name: &str, ) -> Result, Box> { matches.try_contains_id(name)?; Ok(keypair_of(matches, name)) } pub fn try_keypairs_of( matches: &ArgMatches, name: &str, ) -> Result>, Box> { matches.try_contains_id(name)?; Ok(keypairs_of(matches, name)) } // Return a `Result` wrapped pubkey for an argument that can itself be parsed into a pubkey, // or is a filename that can be read as a keypair pub fn try_pubkey_of( matches: &ArgMatches, name: &str, ) -> Result, Box> { matches.try_contains_id(name)?; Ok(pubkey_of(matches, name)) } pub fn try_pubkeys_of( matches: &ArgMatches, name: &str, ) -> Result>, Box> { matches.try_contains_id(name)?; Ok(pubkeys_of(matches, name)) } // Return pubkey/signature pairs for a string of the form pubkey=signature pub fn pubkeys_sigs_of(matches: &ArgMatches, name: &str) -> Option> { matches.values_of(name).map(|values| { values .map(|pubkey_signer_string| { let mut signer = pubkey_signer_string.split('='); let key = Pubkey::from_str(signer.next().unwrap()).unwrap(); let sig = Signature::from_str(signer.next().unwrap()).unwrap(); (key, sig) }) .collect() }) } // Return pubkey/signature pairs for a string of the form pubkey=signature wrapped inside `Result` #[allow(clippy::type_complexity)] pub fn try_pubkeys_sigs_of( matches: &ArgMatches, name: &str, ) -> Result>, Box> { matches.try_contains_id(name)?; Ok(pubkeys_sigs_of(matches, name)) } // Return a signer from matches at `name` #[allow(clippy::type_complexity)] pub fn signer_of( matches: &ArgMatches, name: &str, wallet_manager: &mut Option>, ) -> Result<(Option>, Option), Box> { if let Some(location) = matches.try_get_one::(name)? { let signer = signer_from_path(matches, location, name, wallet_manager)?; let signer_pubkey = signer.pubkey(); Ok((Some(signer), Some(signer_pubkey))) } else { Ok((None, None)) } } pub fn pubkey_of_signer( matches: &ArgMatches, name: &str, wallet_manager: &mut Option>, ) -> Result, Box> { if let Some(location) = matches.try_get_one::(name)? { Ok(Some(pubkey_from_path( matches, location, name, wallet_manager, )?)) } else { Ok(None) } } pub fn pubkeys_of_multiple_signers( matches: &ArgMatches, name: &str, wallet_manager: &mut Option>, ) -> Result>, Box> { if let Some(pubkey_matches) = matches.try_get_many::(name)? { let mut pubkeys: Vec = vec![]; for signer in pubkey_matches { pubkeys.push(pubkey_from_path(matches, signer, name, wallet_manager)?); } Ok(Some(pubkeys)) } else { Ok(None) } } pub fn resolve_signer( matches: &ArgMatches, name: &str, wallet_manager: &mut Option>, ) -> Result, Box> { resolve_signer_from_path( matches, matches.try_get_one::(name)?.unwrap(), name, wallet_manager, ) } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct PubkeySignature { pubkey: Pubkey, signature: Signature, } impl FromStr for PubkeySignature { type Err = String; fn from_str(s: &str) -> Result { let mut signer = s.split('='); let pubkey = signer .next() .ok_or_else(|| String::from("Malformed signer string"))?; let pubkey = Pubkey::from_str(pubkey).map_err(|err| format!("{err}"))?; let signature = signer .next() .ok_or_else(|| String::from("Malformed signer string"))?; let signature = Signature::from_str(signature).map_err(|err| format!("{err}"))?; Ok(Self { pubkey, signature }) } } #[cfg(test)] mod tests { use { super::*, clap::{Arg, Command}, solana_sdk::signature::write_keypair_file, std::fs, }; fn app<'ab>() -> Command<'ab> { Command::new("test") .arg( Arg::new("multiple") .long("multiple") .takes_value(true) .multiple_occurrences(true) .multiple_values(true), ) .arg(Arg::new("single").takes_value(true).long("single")) .arg(Arg::new("unit").takes_value(true).long("unit")) } fn tmp_file_path(name: &str, pubkey: &Pubkey) -> String { use std::env; let out_dir = env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string()); format!("{out_dir}/tmp/{name}-{pubkey}") } #[test] fn test_keypair_of() { let keypair = Keypair::new(); let outfile = tmp_file_path("test_keypair_of.json", &keypair.pubkey()); let _ = write_keypair_file(&keypair, &outfile).unwrap(); let matches = app().get_matches_from(vec!["test", "--single", &outfile]); assert_eq!( keypair_of(&matches, "single").unwrap().pubkey(), keypair.pubkey() ); assert!(keypair_of(&matches, "multiple").is_none()); let matches = app().get_matches_from(vec!["test", "--single", "random_keypair_file.json"]); assert!(keypair_of(&matches, "single").is_none()); fs::remove_file(&outfile).unwrap(); } #[test] fn test_pubkey_of() { let keypair = Keypair::new(); let outfile = tmp_file_path("test_pubkey_of.json", &keypair.pubkey()); let _ = write_keypair_file(&keypair, &outfile).unwrap(); let matches = app().get_matches_from(vec!["test", "--single", &outfile]); assert_eq!(pubkey_of(&matches, "single"), Some(keypair.pubkey())); assert_eq!(pubkey_of(&matches, "multiple"), None); let matches = app().get_matches_from(vec!["test", "--single", &keypair.pubkey().to_string()]); assert_eq!(pubkey_of(&matches, "single"), Some(keypair.pubkey())); let matches = app().get_matches_from(vec!["test", "--single", "random_keypair_file.json"]); assert_eq!(pubkey_of(&matches, "single"), None); fs::remove_file(&outfile).unwrap(); } #[test] fn test_pubkeys_of() { let keypair = Keypair::new(); let outfile = tmp_file_path("test_pubkeys_of.json", &keypair.pubkey()); let _ = write_keypair_file(&keypair, &outfile).unwrap(); let matches = app().get_matches_from(vec![ "test", "--multiple", &keypair.pubkey().to_string(), "--multiple", &outfile, ]); assert_eq!( pubkeys_of(&matches, "multiple"), Some(vec![keypair.pubkey(), keypair.pubkey()]) ); fs::remove_file(&outfile).unwrap(); } #[test] fn test_pubkeys_sigs_of() { let key1 = solana_sdk::pubkey::new_rand(); let key2 = solana_sdk::pubkey::new_rand(); let sig1 = Keypair::new().sign_message(&[0u8]); let sig2 = Keypair::new().sign_message(&[1u8]); let signer1 = format!("{key1}={sig1}"); let signer2 = format!("{key2}={sig2}"); let matches = app().get_matches_from(vec!["test", "--multiple", &signer1, "--multiple", &signer2]); assert_eq!( pubkeys_sigs_of(&matches, "multiple"), Some(vec![(key1, sig1), (key2, sig2)]) ); } #[test] fn test_parse_pubkey_signature() { let command = Command::new("test").arg( Arg::new("pubkeysig") .long("pubkeysig") .takes_value(true) .value_parser(clap::value_parser!(PubkeySignature)), ); // success case let matches = command .clone() .try_get_matches_from(vec![ "test", "--pubkeysig", "11111111111111111111111111111111=4TpFuec1u4BZfxgHg2VQXwvBHANZuNSJHmgrU34GViLAM5uYZ8t7uuhWMHN4k9r41B2p9mwnHjPGwTmTxyvCZw63" ] ) .unwrap(); let expected = PubkeySignature { pubkey: Pubkey::from_str("11111111111111111111111111111111").unwrap(), signature: Signature::from_str("4TpFuec1u4BZfxgHg2VQXwvBHANZuNSJHmgrU34GViLAM5uYZ8t7uuhWMHN4k9r41B2p9mwnHjPGwTmTxyvCZw63").unwrap(), }; assert_eq!( *matches.get_one::("pubkeysig").unwrap(), expected, ); // validation fails let matches_error = command .clone() .try_get_matches_from(vec!["test", "--pubkeysig", "this_is_an_invalid_arg"]) .unwrap_err(); assert_eq!(matches_error.kind, clap::error::ErrorKind::ValueValidation); } }