2019-11-23 08:55:43 -08:00
|
|
|
use crate::keypair::{keypair_from_seed_phrase, ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG};
|
2019-12-19 14:37:47 -08:00
|
|
|
use chrono::DateTime;
|
2019-09-18 09:29:57 -07:00
|
|
|
use clap::ArgMatches;
|
2020-02-07 10:26:56 -08:00
|
|
|
use solana_remote_wallet::remote_wallet::DerivationPath;
|
2019-09-18 09:29:57 -07:00
|
|
|
use solana_sdk::{
|
2019-12-19 14:37:47 -08:00
|
|
|
clock::UnixTimestamp,
|
2019-10-01 10:53:28 -07:00
|
|
|
native_token::sol_to_lamports,
|
2019-09-18 09:29:57 -07:00
|
|
|
pubkey::Pubkey,
|
2019-11-25 21:09:57 -08:00
|
|
|
signature::{read_keypair_file, Keypair, KeypairUtil, Signature},
|
2019-09-18 09:29:57 -07:00
|
|
|
};
|
2019-11-25 21:09:57 -08:00
|
|
|
use std::str::FromStr;
|
2019-09-18 09:29:57 -07:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-30 21:57:47 -08:00
|
|
|
pub fn unix_timestamp_from_rfc3339_datetime(
|
|
|
|
matches: &ArgMatches<'_>,
|
|
|
|
name: &str,
|
|
|
|
) -> Option<UnixTimestamp> {
|
2019-12-19 14:37:47 -08:00
|
|
|
matches.value_of(name).and_then(|value| {
|
|
|
|
DateTime::parse_from_rfc3339(value)
|
|
|
|
.ok()
|
|
|
|
.map(|date_time| date_time.timestamp())
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-09-18 09:29:57 -07:00
|
|
|
// 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) {
|
2019-11-23 08:55:43 -08:00
|
|
|
if value == ASK_KEYWORD {
|
|
|
|
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
|
2019-12-06 06:55:00 -08:00
|
|
|
keypair_from_seed_phrase(name, skip_validation, true).ok()
|
2019-11-23 08:55:43 -08:00
|
|
|
} else {
|
|
|
|
read_keypair_file(value).ok()
|
|
|
|
}
|
2019-09-18 09:29:57 -07:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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()))
|
|
|
|
}
|
|
|
|
|
2020-01-17 11:10:52 -08:00
|
|
|
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()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-11-25 21:09:57 -08:00
|
|
|
// 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()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-09-29 21:18:15 -07:00
|
|
|
pub fn amount_of(matches: &ArgMatches<'_>, name: &str, unit: &str) -> Option<u64> {
|
|
|
|
if matches.value_of(unit) == Some("lamports") {
|
|
|
|
value_of(matches, name)
|
|
|
|
} else {
|
|
|
|
value_of(matches, name).map(sol_to_lamports)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-07 10:26:56 -08:00
|
|
|
pub fn derivation_of(matches: &ArgMatches<'_>, name: &str) -> Option<DerivationPath> {
|
|
|
|
matches.value_of(name).map(|derivation_str| {
|
|
|
|
let derivation_str = derivation_str.replace("'", "");
|
|
|
|
let mut parts = derivation_str.split('/');
|
|
|
|
let account = parts.next().unwrap().parse::<u16>().unwrap();
|
|
|
|
let change = parts.next().map(|change| change.parse::<u16>().unwrap());
|
|
|
|
DerivationPath { account, change }
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-09-18 09:29:57 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use clap::{App, Arg};
|
2019-10-10 16:01:03 -07:00
|
|
|
use solana_sdk::signature::write_keypair_file;
|
2019-09-18 09:29:57 -07:00
|
|
|
use std::fs;
|
|
|
|
|
|
|
|
fn app<'ab, 'v>() -> App<'ab, 'v> {
|
|
|
|
App::new("test")
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("multiple")
|
|
|
|
.long("multiple")
|
|
|
|
.takes_value(true)
|
|
|
|
.multiple(true),
|
|
|
|
)
|
|
|
|
.arg(Arg::with_name("single").takes_value(true).long("single"))
|
2019-12-10 00:24:44 -08:00
|
|
|
.arg(Arg::with_name("unit").takes_value(true).long("unit"))
|
2019-09-18 09:29:57 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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!("{}/tmp/{}-{}", out_dir, name, pubkey.to_string())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_values_of() {
|
|
|
|
let matches =
|
|
|
|
app()
|
|
|
|
.clone()
|
|
|
|
.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 = Pubkey::new_rand();
|
|
|
|
let pubkey1 = Pubkey::new_rand();
|
|
|
|
let matches = app().clone().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()
|
|
|
|
.clone()
|
|
|
|
.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 = Pubkey::new_rand();
|
|
|
|
let matches = app()
|
|
|
|
.clone()
|
|
|
|
.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();
|
2020-01-17 11:10:52 -08:00
|
|
|
let outfile = tmp_file_path("test_keypair_of.json", &keypair.pubkey());
|
2019-10-10 16:01:03 -07:00
|
|
|
let _ = write_keypair_file(&keypair, &outfile).unwrap();
|
2019-09-18 09:29:57 -07:00
|
|
|
|
|
|
|
let matches = app()
|
|
|
|
.clone()
|
|
|
|
.get_matches_from(vec!["test", "--single", &outfile]);
|
2019-11-07 17:08:10 -08:00
|
|
|
assert_eq!(
|
|
|
|
keypair_of(&matches, "single").unwrap().pubkey(),
|
|
|
|
keypair.pubkey()
|
|
|
|
);
|
|
|
|
assert!(keypair_of(&matches, "multiple").is_none());
|
2019-09-18 09:29:57 -07:00
|
|
|
|
|
|
|
let matches =
|
|
|
|
app()
|
|
|
|
.clone()
|
|
|
|
.get_matches_from(vec!["test", "--single", "random_keypair_file.json"]);
|
2019-11-07 17:08:10 -08:00
|
|
|
assert!(keypair_of(&matches, "single").is_none());
|
2019-09-18 09:29:57 -07:00
|
|
|
|
|
|
|
fs::remove_file(&outfile).unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_pubkey_of() {
|
|
|
|
let keypair = Keypair::new();
|
2020-01-17 11:10:52 -08:00
|
|
|
let outfile = tmp_file_path("test_pubkey_of.json", &keypair.pubkey());
|
2019-10-10 16:01:03 -07:00
|
|
|
let _ = write_keypair_file(&keypair, &outfile).unwrap();
|
2019-09-18 09:29:57 -07:00
|
|
|
|
|
|
|
let matches = app()
|
|
|
|
.clone()
|
|
|
|
.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()
|
|
|
|
.clone()
|
|
|
|
.get_matches_from(vec!["test", "--single", &keypair.pubkey().to_string()]);
|
|
|
|
assert_eq!(pubkey_of(&matches, "single"), Some(keypair.pubkey()));
|
|
|
|
|
|
|
|
let matches =
|
|
|
|
app()
|
|
|
|
.clone()
|
|
|
|
.get_matches_from(vec!["test", "--single", "random_keypair_file.json"]);
|
|
|
|
assert_eq!(pubkey_of(&matches, "single"), None);
|
|
|
|
|
|
|
|
fs::remove_file(&outfile).unwrap();
|
|
|
|
}
|
2019-11-25 21:09:57 -08:00
|
|
|
|
2020-01-17 11:10:52 -08:00
|
|
|
#[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().clone().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();
|
|
|
|
}
|
|
|
|
|
2019-11-25 21:09:57 -08:00
|
|
|
#[test]
|
|
|
|
fn test_pubkeys_sigs_of() {
|
|
|
|
let key1 = Pubkey::new_rand();
|
|
|
|
let key2 = 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().clone().get_matches_from(vec![
|
|
|
|
"test",
|
|
|
|
"--multiple",
|
|
|
|
&signer1,
|
|
|
|
"--multiple",
|
|
|
|
&signer2,
|
|
|
|
]);
|
|
|
|
assert_eq!(
|
|
|
|
pubkeys_sigs_of(&matches, "multiple"),
|
|
|
|
Some(vec![(key1, sig1), (key2, sig2)])
|
|
|
|
);
|
|
|
|
}
|
2019-12-10 00:24:44 -08:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_amount_of() {
|
|
|
|
let matches = app()
|
|
|
|
.clone()
|
|
|
|
.get_matches_from(vec!["test", "--single", "50", "--unit", "lamports"]);
|
|
|
|
assert_eq!(amount_of(&matches, "single", "unit"), Some(50));
|
2019-12-10 10:29:17 -08:00
|
|
|
assert_eq!(amount_of(&matches, "multiple", "unit"), None);
|
2019-12-10 00:24:44 -08:00
|
|
|
let matches = app()
|
|
|
|
.clone()
|
|
|
|
.get_matches_from(vec!["test", "--single", "50", "--unit", "SOL"]);
|
|
|
|
assert_eq!(amount_of(&matches, "single", "unit"), Some(50000000000));
|
2019-12-10 10:29:17 -08:00
|
|
|
let matches = app()
|
|
|
|
.clone()
|
|
|
|
.get_matches_from(vec!["test", "--single", "1.5", "--unit", "SOL"]);
|
|
|
|
assert_eq!(amount_of(&matches, "single", "unit"), Some(1500000000));
|
|
|
|
let matches = app()
|
|
|
|
.clone()
|
|
|
|
.get_matches_from(vec!["test", "--single", "1.5", "--unit", "lamports"]);
|
|
|
|
assert_eq!(amount_of(&matches, "single", "unit"), None);
|
2019-12-10 00:24:44 -08:00
|
|
|
}
|
2020-02-07 10:26:56 -08:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_derivation_of() {
|
|
|
|
let matches = app()
|
|
|
|
.clone()
|
|
|
|
.get_matches_from(vec!["test", "--single", "2/3"]);
|
|
|
|
assert_eq!(
|
|
|
|
derivation_of(&matches, "single"),
|
|
|
|
Some(DerivationPath {
|
|
|
|
account: 2,
|
|
|
|
change: Some(3)
|
|
|
|
})
|
|
|
|
);
|
|
|
|
assert_eq!(derivation_of(&matches, "another"), None);
|
|
|
|
let matches = app()
|
|
|
|
.clone()
|
|
|
|
.get_matches_from(vec!["test", "--single", "2"]);
|
|
|
|
assert_eq!(
|
|
|
|
derivation_of(&matches, "single"),
|
|
|
|
Some(DerivationPath {
|
|
|
|
account: 2,
|
|
|
|
change: None
|
|
|
|
})
|
|
|
|
);
|
|
|
|
assert_eq!(derivation_of(&matches, "another"), None);
|
|
|
|
let matches = app()
|
|
|
|
.clone()
|
|
|
|
.get_matches_from(vec!["test", "--single", "2'/3'"]);
|
|
|
|
assert_eq!(
|
|
|
|
derivation_of(&matches, "single"),
|
|
|
|
Some(DerivationPath {
|
|
|
|
account: 2,
|
|
|
|
change: Some(3)
|
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
2019-09-18 09:29:57 -07:00
|
|
|
}
|