solana/clap-v3-utils/src/input_parsers/signer.rs

305 lines
9.7 KiB
Rust

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<Option<Keypair>, Box<dyn error::Error>> {
matches.try_contains_id(name)?;
Ok(keypair_of(matches, name))
}
pub fn try_keypairs_of(
matches: &ArgMatches,
name: &str,
) -> Result<Option<Vec<Keypair>>, Box<dyn error::Error>> {
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<Option<Pubkey>, Box<dyn error::Error>> {
matches.try_contains_id(name)?;
Ok(pubkey_of(matches, name))
}
pub fn try_pubkeys_of(
matches: &ArgMatches,
name: &str,
) -> Result<Option<Vec<Pubkey>>, Box<dyn error::Error>> {
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<Vec<(Pubkey, Signature)>> {
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<Option<Vec<(Pubkey, Signature)>>, Box<dyn error::Error>> {
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<Rc<RemoteWalletManager>>,
) -> Result<(Option<Box<dyn Signer>>, Option<Pubkey>), Box<dyn std::error::Error>> {
if let Some(location) = matches.try_get_one::<String>(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<Rc<RemoteWalletManager>>,
) -> Result<Option<Pubkey>, Box<dyn std::error::Error>> {
if let Some(location) = matches.try_get_one::<String>(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<Rc<RemoteWalletManager>>,
) -> Result<Option<Vec<Pubkey>>, Box<dyn std::error::Error>> {
if let Some(pubkey_matches) = matches.try_get_many::<String>(name)? {
let mut pubkeys: Vec<Pubkey> = 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<Rc<RemoteWalletManager>>,
) -> Result<Option<String>, Box<dyn std::error::Error>> {
resolve_signer_from_path(
matches,
matches.try_get_one::<String>(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<Self, Self::Err> {
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::<PubkeySignature>("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);
}
}