[clap-v3-utils] Deprecate input validators and add parsers to replace them (#33276)
* add tests for validating `Pubkey` and `Hash` * add pubkey signature parser * add parsers for straightforward validators * add parser for token amounts * add parser for derivation and seeds * resolve warnings from deprecations in clap-v3-utils * remove some deprecated functions from `solana_keygen` * refactor signer related input parsers into a submodule * fix deprecation notice for utl * refactor parsers in `input_validators` to `input_parsers` * cargo fmt * Apply suggestions from code review Co-authored-by: Trent Nelson <trent.a.b.nelson@gmail.com> * Update clap-v3-utils/src/input_parsers/mod.rs Co-authored-by: Trent Nelson <trent.a.b.nelson@gmail.com> * mionr fixes to build * add deprecation notice for old `input_parsers::signer` functions * update `UiTokenAmount` to `Amount` * refactor to-be-deprecated functions back to `input_parsers/mod.rs * fmt --------- Co-authored-by: Trent Nelson <trent.a.b.nelson@gmail.com>
This commit is contained in:
parent
25c27d452c
commit
ec36369e47
|
@ -0,0 +1,548 @@
|
|||
use {
|
||||
crate::{
|
||||
input_validators::normalize_to_url_if_moniker,
|
||||
keypair::{keypair_from_seed_phrase, ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG},
|
||||
},
|
||||
chrono::DateTime,
|
||||
clap::ArgMatches,
|
||||
solana_sdk::{
|
||||
clock::UnixTimestamp,
|
||||
commitment_config::CommitmentConfig,
|
||||
genesis_config::ClusterType,
|
||||
native_token::sol_to_lamports,
|
||||
pubkey::{Pubkey, MAX_SEED_LEN},
|
||||
signature::{read_keypair_file, Keypair, Signer},
|
||||
},
|
||||
std::str::FromStr,
|
||||
};
|
||||
|
||||
pub mod signer;
|
||||
#[deprecated(
|
||||
since = "1.17.0",
|
||||
note = "Please use the functions in `solana_clap_v3_utils::input_parsers::signer` directly instead"
|
||||
)]
|
||||
pub use signer::{
|
||||
pubkey_of_signer, pubkeys_of_multiple_signers, pubkeys_sigs_of, resolve_signer, signer_of,
|
||||
STDOUT_OUTFILE_TOKEN,
|
||||
};
|
||||
|
||||
// Return parsed values from matches at `name`
|
||||
pub fn values_of<T>(matches: &ArgMatches, name: &str) -> Option<Vec<T>>
|
||||
where
|
||||
T: std::str::FromStr,
|
||||
<T as std::str::FromStr>::Err: std::fmt::Debug,
|
||||
{
|
||||
matches
|
||||
.values_of(name)
|
||||
.map(|xs| xs.map(|x| x.parse::<T>().unwrap()).collect())
|
||||
}
|
||||
|
||||
// Return a parsed value from matches at `name`
|
||||
pub fn value_of<T>(matches: &ArgMatches, name: &str) -> Option<T>
|
||||
where
|
||||
T: std::str::FromStr,
|
||||
<T as std::str::FromStr>::Err: std::fmt::Debug,
|
||||
{
|
||||
matches
|
||||
.value_of(name)
|
||||
.and_then(|value| value.parse::<T>().ok())
|
||||
}
|
||||
|
||||
pub fn unix_timestamp_from_rfc3339_datetime(
|
||||
matches: &ArgMatches,
|
||||
name: &str,
|
||||
) -> Option<UnixTimestamp> {
|
||||
matches.value_of(name).and_then(|value| {
|
||||
DateTime::parse_from_rfc3339(value)
|
||||
.ok()
|
||||
.map(|date_time| date_time.timestamp())
|
||||
})
|
||||
}
|
||||
|
||||
#[deprecated(
|
||||
since = "1.17.0",
|
||||
note = "please use `Amount::parse_decimal` and `Amount::sol_to_lamport` instead"
|
||||
)]
|
||||
pub fn lamports_of_sol(matches: &ArgMatches, name: &str) -> Option<u64> {
|
||||
value_of(matches, name).map(sol_to_lamports)
|
||||
}
|
||||
|
||||
pub fn cluster_type_of(matches: &ArgMatches, name: &str) -> Option<ClusterType> {
|
||||
value_of(matches, name)
|
||||
}
|
||||
|
||||
pub fn commitment_of(matches: &ArgMatches, name: &str) -> Option<CommitmentConfig> {
|
||||
matches
|
||||
.value_of(name)
|
||||
.map(|value| CommitmentConfig::from_str(value).unwrap_or_default())
|
||||
}
|
||||
|
||||
pub fn parse_url(arg: &str) -> Result<String, String> {
|
||||
url::Url::parse(arg)
|
||||
.map_err(|err| err.to_string())
|
||||
.and_then(|url| {
|
||||
url.has_host()
|
||||
.then_some(arg.to_string())
|
||||
.ok_or("no host provided".to_string())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_url_or_moniker(arg: &str) -> Result<String, String> {
|
||||
parse_url(&normalize_to_url_if_moniker(arg))
|
||||
}
|
||||
|
||||
pub fn parse_pow2(arg: &str) -> Result<usize, String> {
|
||||
arg.parse::<usize>()
|
||||
.map_err(|e| format!("Unable to parse, provided: {arg}, err: {e}"))
|
||||
.and_then(|v| {
|
||||
v.is_power_of_two()
|
||||
.then_some(v)
|
||||
.ok_or(format!("Must be a power of 2: {v}"))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_percentage(arg: &str) -> Result<u8, String> {
|
||||
arg.parse::<u8>()
|
||||
.map_err(|e| format!("Unable to parse input percentage, provided: {arg}, err: {e}"))
|
||||
.and_then(|v| {
|
||||
(v <= 100).then_some(v).ok_or(format!(
|
||||
"Percentage must be in range of 0 to 100, provided: {v}"
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum Amount {
|
||||
Decimal(f64),
|
||||
Raw(u64),
|
||||
All,
|
||||
}
|
||||
impl Amount {
|
||||
pub fn parse(arg: &str) -> Result<Amount, String> {
|
||||
if arg == "ALL" {
|
||||
Ok(Amount::All)
|
||||
} else {
|
||||
Self::parse_decimal(arg).or(Self::parse_raw(arg)
|
||||
.map_err(|_| format!("Unable to parse input amount, provided: {arg}")))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_decimal(arg: &str) -> Result<Amount, String> {
|
||||
arg.parse::<f64>()
|
||||
.map(Amount::Decimal)
|
||||
.map_err(|_| format!("Unable to parse input amount, provided: {arg}"))
|
||||
}
|
||||
|
||||
pub fn parse_raw(arg: &str) -> Result<Amount, String> {
|
||||
arg.parse::<u64>()
|
||||
.map(Amount::Raw)
|
||||
.map_err(|_| format!("Unable to parse input amount, provided: {arg}"))
|
||||
}
|
||||
|
||||
pub fn parse_decimal_or_all(arg: &str) -> Result<Amount, String> {
|
||||
if arg == "ALL" {
|
||||
Ok(Amount::All)
|
||||
} else {
|
||||
Self::parse_decimal(arg).map_err(|_| {
|
||||
format!("Unable to parse input amount as float or 'ALL' keyword, provided: {arg}")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_raw_amount(&self, decimals: u8) -> Self {
|
||||
match self {
|
||||
Amount::Decimal(amount) => {
|
||||
Amount::Raw((amount * 10_usize.pow(decimals as u32) as f64) as u64)
|
||||
}
|
||||
Amount::Raw(amount) => Amount::Raw(*amount),
|
||||
Amount::All => Amount::All,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sol_to_lamport(&self) -> Amount {
|
||||
const NATIVE_SOL_DECIMALS: u8 = 9;
|
||||
self.to_raw_amount(NATIVE_SOL_DECIMALS)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum RawTokenAmount {
|
||||
Amount(u64),
|
||||
All,
|
||||
}
|
||||
|
||||
pub fn parse_rfc3339_datetime(arg: &str) -> Result<String, String> {
|
||||
DateTime::parse_from_rfc3339(arg)
|
||||
.map(|_| arg.to_string())
|
||||
.map_err(|e| format!("{e}"))
|
||||
}
|
||||
|
||||
pub fn parse_derivation(arg: &str) -> Result<String, String> {
|
||||
let value = arg.replace('\'', "");
|
||||
let mut parts = value.split('/');
|
||||
let account = parts.next().unwrap();
|
||||
account
|
||||
.parse::<u32>()
|
||||
.map_err(|e| format!("Unable to parse derivation, provided: {account}, err: {e}"))
|
||||
.and_then(|_| {
|
||||
if let Some(change) = parts.next() {
|
||||
change.parse::<u32>().map_err(|e| {
|
||||
format!("Unable to parse derivation, provided: {change}, err: {e}")
|
||||
})
|
||||
} else {
|
||||
Ok(0)
|
||||
}
|
||||
})?;
|
||||
Ok(arg.to_string())
|
||||
}
|
||||
|
||||
pub fn parse_structured_seed(arg: &str) -> Result<String, String> {
|
||||
let (prefix, value) = arg
|
||||
.split_once(':')
|
||||
.ok_or("Seed must contain ':' as delimiter")
|
||||
.unwrap();
|
||||
if prefix.is_empty() || value.is_empty() {
|
||||
Err(String::from("Seed prefix or value is empty"))
|
||||
} else {
|
||||
match prefix {
|
||||
"string" | "pubkey" | "hex" | "u8" => Ok(arg.to_string()),
|
||||
_ => {
|
||||
let len = prefix.len();
|
||||
if len != 5 && len != 6 {
|
||||
Err(format!("Wrong prefix length {len} {prefix}:{value}"))
|
||||
} else {
|
||||
let sign = &prefix[0..1];
|
||||
let type_size = &prefix[1..len.saturating_sub(2)];
|
||||
let byte_order = &prefix[len.saturating_sub(2)..len];
|
||||
if sign != "u" && sign != "i" {
|
||||
Err(format!("Wrong prefix sign {sign} {prefix}:{value}"))
|
||||
} else if type_size != "16"
|
||||
&& type_size != "32"
|
||||
&& type_size != "64"
|
||||
&& type_size != "128"
|
||||
{
|
||||
Err(format!(
|
||||
"Wrong prefix type size {type_size} {prefix}:{value}"
|
||||
))
|
||||
} else if byte_order != "le" && byte_order != "be" {
|
||||
Err(format!(
|
||||
"Wrong prefix byte order {byte_order} {prefix}:{value}"
|
||||
))
|
||||
} else {
|
||||
Ok(arg.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_derived_address_seed(arg: &str) -> Result<String, String> {
|
||||
(arg.len() <= MAX_SEED_LEN)
|
||||
.then_some(arg.to_string())
|
||||
.ok_or(format!(
|
||||
"Address seed must not be longer than {MAX_SEED_LEN} bytes"
|
||||
))
|
||||
}
|
||||
|
||||
// Return the keypair for an argument with filename `name` or None if not present.
|
||||
pub fn keypair_of(matches: &ArgMatches, name: &str) -> Option<Keypair> {
|
||||
if let Some(value) = matches.value_of(name) {
|
||||
if value == ASK_KEYWORD {
|
||||
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
|
||||
keypair_from_seed_phrase(name, skip_validation, true, None, true).ok()
|
||||
} else {
|
||||
read_keypair_file(value).ok()
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keypairs_of(matches: &ArgMatches, name: &str) -> Option<Vec<Keypair>> {
|
||||
matches.values_of(name).map(|values| {
|
||||
values
|
||||
.filter_map(|value| {
|
||||
if value == ASK_KEYWORD {
|
||||
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
|
||||
keypair_from_seed_phrase(name, skip_validation, true, None, true).ok()
|
||||
} else {
|
||||
read_keypair_file(value).ok()
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
// Return a 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 pubkey_of(matches: &ArgMatches, name: &str) -> Option<Pubkey> {
|
||||
value_of(matches, name).or_else(|| keypair_of(matches, name).map(|keypair| keypair.pubkey()))
|
||||
}
|
||||
|
||||
pub fn pubkeys_of(matches: &ArgMatches, name: &str) -> Option<Vec<Pubkey>> {
|
||||
matches.values_of(name).map(|values| {
|
||||
values
|
||||
.map(|value| {
|
||||
value.parse::<Pubkey>().unwrap_or_else(|_| {
|
||||
read_keypair_file(value)
|
||||
.expect("read_keypair_file failed")
|
||||
.pubkey()
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use {
|
||||
super::*,
|
||||
clap::{Arg, Command},
|
||||
solana_sdk::{hash::Hash, pubkey::Pubkey},
|
||||
};
|
||||
|
||||
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"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_values_of() {
|
||||
let matches = app().get_matches_from(vec!["test", "--multiple", "50", "--multiple", "39"]);
|
||||
assert_eq!(values_of(&matches, "multiple"), Some(vec![50, 39]));
|
||||
assert_eq!(values_of::<u64>(&matches, "single"), None);
|
||||
|
||||
let pubkey0 = solana_sdk::pubkey::new_rand();
|
||||
let pubkey1 = solana_sdk::pubkey::new_rand();
|
||||
let matches = app().get_matches_from(vec![
|
||||
"test",
|
||||
"--multiple",
|
||||
&pubkey0.to_string(),
|
||||
"--multiple",
|
||||
&pubkey1.to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
values_of(&matches, "multiple"),
|
||||
Some(vec![pubkey0, pubkey1])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_value_of() {
|
||||
let matches = app().get_matches_from(vec!["test", "--single", "50"]);
|
||||
assert_eq!(value_of(&matches, "single"), Some(50));
|
||||
assert_eq!(value_of::<u64>(&matches, "multiple"), None);
|
||||
|
||||
let pubkey = solana_sdk::pubkey::new_rand();
|
||||
let matches = app().get_matches_from(vec!["test", "--single", &pubkey.to_string()]);
|
||||
assert_eq!(value_of(&matches, "single"), Some(pubkey));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_pubkey() {
|
||||
let command = Command::new("test").arg(
|
||||
Arg::new("pubkey")
|
||||
.long("pubkey")
|
||||
.takes_value(true)
|
||||
.value_parser(clap::value_parser!(Pubkey)),
|
||||
);
|
||||
|
||||
// success case
|
||||
let matches = command
|
||||
.clone()
|
||||
.try_get_matches_from(vec!["test", "--pubkey", "11111111111111111111111111111111"])
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
*matches.get_one::<Pubkey>("pubkey").unwrap(),
|
||||
Pubkey::from_str("11111111111111111111111111111111").unwrap(),
|
||||
);
|
||||
|
||||
// validation fails
|
||||
let matches_error = command
|
||||
.clone()
|
||||
.try_get_matches_from(vec!["test", "--pubkey", "this_is_an_invalid_arg"])
|
||||
.unwrap_err();
|
||||
assert_eq!(matches_error.kind, clap::error::ErrorKind::ValueValidation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_hash() {
|
||||
let command = Command::new("test").arg(
|
||||
Arg::new("hash")
|
||||
.long("hash")
|
||||
.takes_value(true)
|
||||
.value_parser(clap::value_parser!(Hash)),
|
||||
);
|
||||
|
||||
// success case
|
||||
let matches = command
|
||||
.clone()
|
||||
.try_get_matches_from(vec!["test", "--hash", "11111111111111111111111111111111"])
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
*matches.get_one::<Hash>("hash").unwrap(),
|
||||
Hash::from_str("11111111111111111111111111111111").unwrap(),
|
||||
);
|
||||
|
||||
// validation fails
|
||||
let matches_error = command
|
||||
.clone()
|
||||
.try_get_matches_from(vec!["test", "--hash", "this_is_an_invalid_arg"])
|
||||
.unwrap_err();
|
||||
assert_eq!(matches_error.kind, clap::error::ErrorKind::ValueValidation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_token_decimal() {
|
||||
let command = Command::new("test").arg(
|
||||
Arg::new("amount")
|
||||
.long("amount")
|
||||
.takes_value(true)
|
||||
.value_parser(Amount::parse_decimal),
|
||||
);
|
||||
|
||||
// success cases
|
||||
let matches = command
|
||||
.clone()
|
||||
.try_get_matches_from(vec!["test", "--amount", "11223344"])
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
*matches.get_one::<Amount>("amount").unwrap(),
|
||||
Amount::Decimal(11223344_f64),
|
||||
);
|
||||
|
||||
let matches = command
|
||||
.clone()
|
||||
.try_get_matches_from(vec!["test", "--amount", "0.11223344"])
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
*matches.get_one::<Amount>("amount").unwrap(),
|
||||
Amount::Decimal(0.11223344),
|
||||
);
|
||||
|
||||
// validation fail cases
|
||||
let matches_error = command
|
||||
.clone()
|
||||
.try_get_matches_from(vec!["test", "--amount", "this_is_an_invalid_arg"])
|
||||
.unwrap_err();
|
||||
assert_eq!(matches_error.kind, clap::error::ErrorKind::ValueValidation);
|
||||
|
||||
let matches_error = command
|
||||
.clone()
|
||||
.try_get_matches_from(vec!["test", "--amount", "all"])
|
||||
.unwrap_err();
|
||||
assert_eq!(matches_error.kind, clap::error::ErrorKind::ValueValidation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_token_decimal_or_all() {
|
||||
let command = Command::new("test").arg(
|
||||
Arg::new("amount")
|
||||
.long("amount")
|
||||
.takes_value(true)
|
||||
.value_parser(Amount::parse_decimal_or_all),
|
||||
);
|
||||
|
||||
// success cases
|
||||
let matches = command
|
||||
.clone()
|
||||
.try_get_matches_from(vec!["test", "--amount", "11223344"])
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
*matches.get_one::<Amount>("amount").unwrap(),
|
||||
Amount::Decimal(11223344_f64),
|
||||
);
|
||||
|
||||
let matches = command
|
||||
.clone()
|
||||
.try_get_matches_from(vec!["test", "--amount", "0.11223344"])
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
*matches.get_one::<Amount>("amount").unwrap(),
|
||||
Amount::Decimal(0.11223344),
|
||||
);
|
||||
|
||||
let matches = command
|
||||
.clone()
|
||||
.try_get_matches_from(vec!["test", "--amount", "ALL"])
|
||||
.unwrap();
|
||||
assert_eq!(*matches.get_one::<Amount>("amount").unwrap(), Amount::All,);
|
||||
|
||||
// validation fail cases
|
||||
let matches_error = command
|
||||
.clone()
|
||||
.try_get_matches_from(vec!["test", "--amount", "this_is_an_invalid_arg"])
|
||||
.unwrap_err();
|
||||
assert_eq!(matches_error.kind, clap::error::ErrorKind::ValueValidation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sol_to_lamports() {
|
||||
let command = Command::new("test").arg(
|
||||
Arg::new("amount")
|
||||
.long("amount")
|
||||
.takes_value(true)
|
||||
.value_parser(Amount::parse_decimal_or_all),
|
||||
);
|
||||
|
||||
let test_cases = vec![
|
||||
("50", 50_000_000_000),
|
||||
("1.5", 1_500_000_000),
|
||||
("0.03", 30_000_000),
|
||||
];
|
||||
|
||||
for (arg, expected_lamport) in test_cases {
|
||||
let matches = command
|
||||
.clone()
|
||||
.try_get_matches_from(vec!["test", "--amount", arg])
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
matches
|
||||
.get_one::<Amount>("amount")
|
||||
.unwrap()
|
||||
.sol_to_lamport(),
|
||||
Amount::Raw(expected_lamport),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_derivation() {
|
||||
let command = Command::new("test").arg(
|
||||
Arg::new("derivation")
|
||||
.long("derivation")
|
||||
.takes_value(true)
|
||||
.value_parser(parse_derivation),
|
||||
);
|
||||
|
||||
let test_arguments = vec![
|
||||
("2", true),
|
||||
("0", true),
|
||||
("65537", true),
|
||||
("0/2", true),
|
||||
("a", false),
|
||||
("4294967296", false),
|
||||
("a/b", false),
|
||||
("0/4294967296", false),
|
||||
];
|
||||
|
||||
for (arg, should_accept) in test_arguments {
|
||||
if should_accept {
|
||||
let matches = command
|
||||
.clone()
|
||||
.try_get_matches_from(vec!["test", "--derivation", arg])
|
||||
.unwrap();
|
||||
assert_eq!(matches.get_one::<String>("derivation").unwrap(), arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +1,13 @@
|
|||
use {
|
||||
crate::keypair::{
|
||||
keypair_from_seed_phrase, pubkey_from_path, resolve_signer_from_path, signer_from_path,
|
||||
ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG,
|
||||
crate::{
|
||||
input_parsers::{keypair_of, keypairs_of, pubkey_of, pubkeys_of},
|
||||
keypair::{pubkey_from_path, resolve_signer_from_path, signer_from_path},
|
||||
},
|
||||
chrono::DateTime,
|
||||
clap::ArgMatches,
|
||||
solana_remote_wallet::remote_wallet::RemoteWalletManager,
|
||||
solana_sdk::{
|
||||
clock::UnixTimestamp,
|
||||
commitment_config::CommitmentConfig,
|
||||
genesis_config::ClusterType,
|
||||
native_token::sol_to_lamports,
|
||||
pubkey::Pubkey,
|
||||
signature::{read_keypair_file, Keypair, Signature, Signer},
|
||||
signature::{Keypair, Signature, Signer},
|
||||
},
|
||||
std::{error, rc::Rc, str::FromStr},
|
||||
};
|
||||
|
@ -20,55 +15,6 @@ use {
|
|||
// Sentinel value used to indicate to write to screen instead of file
|
||||
pub const STDOUT_OUTFILE_TOKEN: &str = "-";
|
||||
|
||||
// Return parsed values from matches at `name`
|
||||
pub fn values_of<T>(matches: &ArgMatches, name: &str) -> Option<Vec<T>>
|
||||
where
|
||||
T: std::str::FromStr,
|
||||
<T as std::str::FromStr>::Err: std::fmt::Debug,
|
||||
{
|
||||
matches
|
||||
.values_of(name)
|
||||
.map(|xs| xs.map(|x| x.parse::<T>().unwrap()).collect())
|
||||
}
|
||||
|
||||
// Return a parsed value from matches at `name`
|
||||
pub fn value_of<T>(matches: &ArgMatches, name: &str) -> Option<T>
|
||||
where
|
||||
T: std::str::FromStr,
|
||||
<T as std::str::FromStr>::Err: std::fmt::Debug,
|
||||
{
|
||||
if let Some(value) = matches.value_of(name) {
|
||||
value.parse::<T>().ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unix_timestamp_from_rfc3339_datetime(
|
||||
matches: &ArgMatches,
|
||||
name: &str,
|
||||
) -> Option<UnixTimestamp> {
|
||||
matches.value_of(name).and_then(|value| {
|
||||
DateTime::parse_from_rfc3339(value)
|
||||
.ok()
|
||||
.map(|date_time| date_time.timestamp())
|
||||
})
|
||||
}
|
||||
|
||||
// Return the keypair for an argument with filename `name` or None if not present.
|
||||
pub fn keypair_of(matches: &ArgMatches, name: &str) -> Option<Keypair> {
|
||||
if let Some(value) = matches.value_of(name) {
|
||||
if value == ASK_KEYWORD {
|
||||
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
|
||||
keypair_from_seed_phrase(name, skip_validation, true, None, true).ok()
|
||||
} else {
|
||||
read_keypair_file(value).ok()
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Return the keypair for an argument with filename `name` or `None` if not present wrapped inside `Result`.
|
||||
pub fn try_keypair_of(
|
||||
matches: &ArgMatches,
|
||||
|
@ -78,21 +24,6 @@ pub fn try_keypair_of(
|
|||
Ok(keypair_of(matches, name))
|
||||
}
|
||||
|
||||
pub fn keypairs_of(matches: &ArgMatches, name: &str) -> Option<Vec<Keypair>> {
|
||||
matches.values_of(name).map(|values| {
|
||||
values
|
||||
.filter_map(|value| {
|
||||
if value == ASK_KEYWORD {
|
||||
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
|
||||
keypair_from_seed_phrase(name, skip_validation, true, None, true).ok()
|
||||
} else {
|
||||
read_keypair_file(value).ok()
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn try_keypairs_of(
|
||||
matches: &ArgMatches,
|
||||
name: &str,
|
||||
|
@ -101,12 +32,6 @@ pub fn try_keypairs_of(
|
|||
Ok(keypairs_of(matches, name))
|
||||
}
|
||||
|
||||
// Return a 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 pubkey_of(matches: &ArgMatches, name: &str) -> Option<Pubkey> {
|
||||
value_of(matches, name).or_else(|| keypair_of(matches, name).map(|keypair| keypair.pubkey()))
|
||||
}
|
||||
|
||||
// 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(
|
||||
|
@ -117,20 +42,6 @@ pub fn try_pubkey_of(
|
|||
Ok(pubkey_of(matches, name))
|
||||
}
|
||||
|
||||
pub fn pubkeys_of(matches: &ArgMatches, name: &str) -> Option<Vec<Pubkey>> {
|
||||
matches.values_of(name).map(|values| {
|
||||
values
|
||||
.map(|value| {
|
||||
value.parse::<Pubkey>().unwrap_or_else(|_| {
|
||||
read_keypair_file(value)
|
||||
.expect("read_keypair_file failed")
|
||||
.pubkey()
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn try_pubkeys_of(
|
||||
matches: &ArgMatches,
|
||||
name: &str,
|
||||
|
@ -225,18 +136,28 @@ pub fn resolve_signer(
|
|||
)
|
||||
}
|
||||
|
||||
pub fn lamports_of_sol(matches: &ArgMatches, name: &str) -> Option<u64> {
|
||||
value_of(matches, name).map(sol_to_lamports)
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct PubkeySignature {
|
||||
pubkey: Pubkey,
|
||||
signature: Signature,
|
||||
}
|
||||
impl FromStr for PubkeySignature {
|
||||
type Err = String;
|
||||
|
||||
pub fn cluster_type_of(matches: &ArgMatches, name: &str) -> Option<ClusterType> {
|
||||
value_of(matches, name)
|
||||
}
|
||||
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}"))?;
|
||||
|
||||
pub fn commitment_of(matches: &ArgMatches, name: &str) -> Option<CommitmentConfig> {
|
||||
matches
|
||||
.value_of(name)
|
||||
.map(|value| CommitmentConfig::from_str(value).unwrap_or_default())
|
||||
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)]
|
||||
|
@ -268,38 +189,6 @@ mod tests {
|
|||
format!("{out_dir}/tmp/{name}-{pubkey}")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_values_of() {
|
||||
let matches = app().get_matches_from(vec!["test", "--multiple", "50", "--multiple", "39"]);
|
||||
assert_eq!(values_of(&matches, "multiple"), Some(vec![50, 39]));
|
||||
assert_eq!(values_of::<u64>(&matches, "single"), None);
|
||||
|
||||
let pubkey0 = solana_sdk::pubkey::new_rand();
|
||||
let pubkey1 = solana_sdk::pubkey::new_rand();
|
||||
let matches = app().get_matches_from(vec![
|
||||
"test",
|
||||
"--multiple",
|
||||
&pubkey0.to_string(),
|
||||
"--multiple",
|
||||
&pubkey1.to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
values_of(&matches, "multiple"),
|
||||
Some(vec![pubkey0, pubkey1])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_value_of() {
|
||||
let matches = app().get_matches_from(vec!["test", "--single", "50"]);
|
||||
assert_eq!(value_of(&matches, "single"), Some(50));
|
||||
assert_eq!(value_of::<u64>(&matches, "multiple"), None);
|
||||
|
||||
let pubkey = solana_sdk::pubkey::new_rand();
|
||||
let matches = app().get_matches_from(vec!["test", "--single", &pubkey.to_string()]);
|
||||
assert_eq!(value_of(&matches, "single"), Some(pubkey));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keypair_of() {
|
||||
let keypair = Keypair::new();
|
||||
|
@ -376,14 +265,40 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_lamports_of_sol() {
|
||||
let matches = app().get_matches_from(vec!["test", "--single", "50"]);
|
||||
assert_eq!(lamports_of_sol(&matches, "single"), Some(50_000_000_000));
|
||||
assert_eq!(lamports_of_sol(&matches, "multiple"), None);
|
||||
let matches = app().get_matches_from(vec!["test", "--single", "1.5"]);
|
||||
assert_eq!(lamports_of_sol(&matches, "single"), Some(1_500_000_000));
|
||||
assert_eq!(lamports_of_sol(&matches, "multiple"), None);
|
||||
let matches = app().get_matches_from(vec!["test", "--single", "0.03"]);
|
||||
assert_eq!(lamports_of_sol(&matches, "single"), Some(30_000_000));
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ where
|
|||
|
||||
// Return an error if string cannot be parsed as type T.
|
||||
// Takes a String to avoid second type parameter when used as a clap validator
|
||||
#[deprecated(since = "1.17.0", note = "please use `clap::value_parser!` instead")]
|
||||
pub fn is_parsable<T>(string: &str) -> Result<(), String>
|
||||
where
|
||||
T: FromStr,
|
||||
|
@ -35,6 +36,10 @@ where
|
|||
|
||||
// Return an error if string cannot be parsed as numeric type T, and value not within specified
|
||||
// range
|
||||
#[deprecated(
|
||||
since = "1.17.0",
|
||||
note = "please use `clap::builder::RangedI64ValueParser` instead"
|
||||
)]
|
||||
pub fn is_within_range<T, R>(string: String, range: R) -> Result<(), String>
|
||||
where
|
||||
T: FromStr + Copy + std::fmt::Debug + PartialOrd + std::ops::Add<Output = T> + From<usize>,
|
||||
|
@ -59,6 +64,10 @@ pub fn is_pubkey(string: &str) -> Result<(), String> {
|
|||
}
|
||||
|
||||
// Return an error if a hash cannot be parsed.
|
||||
#[deprecated(
|
||||
since = "1.17.0",
|
||||
note = "please use `clap::value_parser!(Hash)` instead"
|
||||
)]
|
||||
pub fn is_hash<T>(string: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
|
@ -144,6 +153,10 @@ where
|
|||
}
|
||||
|
||||
// Return an error if string cannot be parsed as pubkey=signature string
|
||||
#[deprecated(
|
||||
since = "1.17.0",
|
||||
note = "please use `clap::value_parser!(PubkeySignature)` instead"
|
||||
)]
|
||||
pub fn is_pubkey_sig<T>(string: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
|
@ -169,6 +182,7 @@ where
|
|||
}
|
||||
|
||||
// Return an error if a url cannot be parsed.
|
||||
#[deprecated(since = "1.17.0", note = "please use `parse_url` instead")]
|
||||
pub fn is_url<T>(string: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
|
@ -185,6 +199,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[deprecated(since = "1.17.0", note = "please use `parse_url_or_moniker` instead")]
|
||||
pub fn is_url_or_moniker<T>(string: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
|
@ -212,6 +227,10 @@ pub fn normalize_to_url_if_moniker<T: AsRef<str>>(url_or_moniker: T) -> String {
|
|||
.to_string()
|
||||
}
|
||||
|
||||
#[deprecated(
|
||||
since = "1.17.0",
|
||||
note = "please use `clap::value_parser!(Epoch)` instead"
|
||||
)]
|
||||
pub fn is_epoch<T>(epoch: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
|
@ -219,6 +238,10 @@ where
|
|||
is_parsable_generic::<Epoch, _>(epoch)
|
||||
}
|
||||
|
||||
#[deprecated(
|
||||
since = "1.17.0",
|
||||
note = "please use `clap::value_parser!(Slot)` instead"
|
||||
)]
|
||||
pub fn is_slot<T>(slot: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
|
@ -226,6 +249,7 @@ where
|
|||
is_parsable_generic::<Slot, _>(slot)
|
||||
}
|
||||
|
||||
#[deprecated(since = "1.17.0", note = "please use `parse_pow2` instead")]
|
||||
pub fn is_pow2<T>(bins: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
|
@ -242,6 +266,10 @@ where
|
|||
})
|
||||
}
|
||||
|
||||
#[deprecated(
|
||||
since = "1.17.0",
|
||||
note = "please use `clap_value_parser!(u16)` instead"
|
||||
)]
|
||||
pub fn is_port<T>(port: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
|
@ -249,6 +277,7 @@ where
|
|||
is_parsable_generic::<u16, _>(port)
|
||||
}
|
||||
|
||||
#[deprecated(since = "1.17.0", note = "please use `parse_percentage` instead")]
|
||||
pub fn is_valid_percentage<T>(percentage: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
|
@ -268,6 +297,7 @@ where
|
|||
})
|
||||
}
|
||||
|
||||
#[deprecated(since = "1.17.0", note = "please use `Amount::parse_decimal` instead")]
|
||||
pub fn is_amount<T>(amount: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
|
@ -281,6 +311,10 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[deprecated(
|
||||
since = "1.17.0",
|
||||
note = "please use `TokenAmount::parse_decimal` instead"
|
||||
)]
|
||||
pub fn is_amount_or_all<T>(amount: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
|
@ -297,6 +331,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[deprecated(since = "1.17.0", note = "please use `parse_rfc3339_datetime` instead")]
|
||||
pub fn is_rfc3339_datetime<T>(value: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
|
@ -306,6 +341,7 @@ where
|
|||
.map_err(|e| format!("{e}"))
|
||||
}
|
||||
|
||||
#[deprecated(since = "1.17.0", note = "please use `parse_derivation` instead")]
|
||||
pub fn is_derivation<T>(value: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
|
@ -328,6 +364,7 @@ where
|
|||
.map(|_| ())
|
||||
}
|
||||
|
||||
#[deprecated(since = "1.17.0", note = "please use `parse_structured_seed` instead")]
|
||||
pub fn is_structured_seed<T>(value: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
|
@ -373,6 +410,10 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[deprecated(
|
||||
since = "1.17.0",
|
||||
note = "please use `parse_derived_address_seed` instead"
|
||||
)]
|
||||
pub fn is_derived_address_seed<T>(value: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
|
@ -386,21 +427,3 @@ where
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_is_derivation() {
|
||||
assert_eq!(is_derivation("2"), Ok(()));
|
||||
assert_eq!(is_derivation("0"), Ok(()));
|
||||
assert_eq!(is_derivation("65537"), Ok(()));
|
||||
assert_eq!(is_derivation("0/2"), Ok(()));
|
||||
assert_eq!(is_derivation("0'/2'"), Ok(()));
|
||||
assert!(is_derivation("a").is_err());
|
||||
assert!(is_derivation("4294967296").is_err());
|
||||
assert!(is_derivation("a/b").is_err());
|
||||
assert!(is_derivation("0/4294967296").is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use {
|
||||
crate::{input_validators::*, ArgConstant},
|
||||
clap::{Arg, Command},
|
||||
crate::{input_parsers::signer::PubkeySignature, ArgConstant},
|
||||
clap::{value_parser, Arg, Command},
|
||||
solana_sdk::hash::Hash,
|
||||
};
|
||||
|
||||
pub const BLOCKHASH_ARG: ArgConstant<'static> = ArgConstant {
|
||||
|
@ -32,7 +33,7 @@ pub fn blockhash_arg<'a>() -> Arg<'a> {
|
|||
.long(BLOCKHASH_ARG.long)
|
||||
.takes_value(true)
|
||||
.value_name("BLOCKHASH")
|
||||
.validator(|s| is_hash(s))
|
||||
.value_parser(value_parser!(Hash))
|
||||
.help(BLOCKHASH_ARG.help)
|
||||
}
|
||||
|
||||
|
@ -49,7 +50,7 @@ fn signer_arg<'a>() -> Arg<'a> {
|
|||
.long(SIGNER_ARG.long)
|
||||
.takes_value(true)
|
||||
.value_name("PUBKEY=SIGNATURE")
|
||||
.validator(|s| is_pubkey_sig(s))
|
||||
.value_parser(value_parser!(PubkeySignature))
|
||||
.requires(BLOCKHASH_ARG.name)
|
||||
.multiple_occurrences(true)
|
||||
.multiple_values(true)
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
#![allow(clippy::arithmetic_side_effects)]
|
||||
use {
|
||||
bip39::{Mnemonic, MnemonicType, Seed},
|
||||
clap::{crate_description, crate_name, Arg, ArgMatches, Command},
|
||||
clap::{crate_description, crate_name, value_parser, Arg, ArgMatches, Command},
|
||||
solana_clap_v3_utils::{
|
||||
input_parsers::STDOUT_OUTFILE_TOKEN,
|
||||
input_validators::{is_parsable, is_prompt_signer_source},
|
||||
input_validators::is_prompt_signer_source,
|
||||
keygen::{
|
||||
check_for_overwrite,
|
||||
derivation_path::{acquire_derivation_path, derivation_path_arg},
|
||||
|
@ -339,7 +339,7 @@ fn app<'a>(num_threads: &'a str, crate_version: &'a str) -> Command<'a> {
|
|||
.long("num-threads")
|
||||
.value_name("NUMBER")
|
||||
.takes_value(true)
|
||||
.validator(is_parsable::<usize>)
|
||||
.value_parser(value_parser!(usize))
|
||||
.default_value(num_threads)
|
||||
.help("Specify the number of grind threads"),
|
||||
)
|
||||
|
@ -571,7 +571,7 @@ fn do_main(matches: &ArgMatches) -> Result<(), Box<dyn error::Error>> {
|
|||
);
|
||||
}
|
||||
|
||||
let num_threads: usize = matches.value_of_t_or_exit("num_threads");
|
||||
let num_threads = *matches.get_one::<usize>("num_threads").unwrap();
|
||||
|
||||
let grind_matches = grind_parse_args(
|
||||
ignore_case,
|
||||
|
|
Loading…
Reference in New Issue