use std::fmt::Display;
use std::fs;
use std::io::Read;
use std::marker::PhantomData;
use std::net::ToSocketAddrs;
use std::path::PathBuf;
use std::str::FromStr;
use std::time::Duration;
use clap::ArgMatches;
use faster_hex::hex_decode;
use url::Url;
use ckb_sdk::{
constants::{MULTISIG_TYPE_HASH, SIGHASH_TYPE_HASH},
util::zeroize_privkey,
Address, AddressPayload, HumanCapacity, NetworkType, OldAddress, ScriptId,
};
use ckb_signer::MasterPrivKey;
use ckb_types::{core::ScriptHashType, packed::OutPoint, prelude::*, H160, H256};
use crate::utils::cell_dep::CellDeps;
#[allow(clippy::wrong_self_convention)]
pub trait ArgParser<T> {
fn parse(&self, input: &str) -> Result<T, String>;
fn validate(&self, input: &str) -> Result<(), String> {
self.parse(input).map(|_| ())
}
fn from_matches<R: From<T>>(&self, matches: &ArgMatches, name: &str) -> Result<R, String> {
self.from_matches_option(matches, name, true)
.map(Option::unwrap)
}
fn from_matches_opt<R: From<T>>(
&self,
matches: &ArgMatches,
name: &str,
) -> Result<Option<R>, String> {
self.from_matches_option(matches, name, false)
}
fn from_matches_option<R: From<T>>(
&self,
matches: &ArgMatches,
name: &str,
required: bool,
) -> Result<Option<R>, String> {
if required && !matches.is_present(name) {
return Err(format!("<{}> is required", name));
}
matches
.value_of(name)
.map(|input| self.parse(input).map(Into::into))
.transpose()
}
fn from_matches_vec<R: From<T>>(
&self,
matches: &ArgMatches,
name: &str,
) -> Result<Vec<R>, String> {
matches
.values_of_lossy(name)
.unwrap_or_default()
.into_iter()
.map(|input| self.parse(&input).map(Into::into))
.collect()
}
}
#[allow(dead_code)]
pub struct NullParser;
impl ArgParser<String> for NullParser {
fn parse(&self, input: &str) -> Result<String, String> {
Ok(input.to_owned())
}
}
#[allow(dead_code)]
pub enum EitherValue<TA, TB> {
A(TA),
B(TB),
}
#[allow(dead_code)]
pub struct EitherParser<TA, TB, A, B> {
a: A,
b: B,
_ta: PhantomData<TA>,
_tb: PhantomData<TB>,
}
impl<TA, TB, A, B> EitherParser<TA, TB, A, B>
where
A: ArgParser<TA>,
B: ArgParser<TB>,
{
#[allow(dead_code)]
pub fn new(a: A, b: B) -> Self {
EitherParser {
a,
b,
_ta: PhantomData,
_tb: PhantomData,
}
}
}
impl<TA, TB, A, B> ArgParser<EitherValue<TA, TB>> for EitherParser<TA, TB, A, B>
where
A: ArgParser<TA>,
B: ArgParser<TB>,
{
fn parse(&self, input: &str) -> Result<EitherValue<TA, TB>, String> {
self.a
.parse(input)
.map(EitherValue::A)
.or_else(|_| self.b.parse(input).map(EitherValue::B))
}
}
#[derive(Debug, Default)]
pub struct FromStrParser<T: FromStr> {
_t: PhantomData<T>,
}
impl<T: FromStr> FromStrParser<T> {
pub fn new() -> FromStrParser<T> {
FromStrParser { _t: PhantomData }
}
}
impl<T> ArgParser<T> for FromStrParser<T>
where
T: FromStr,
<T as FromStr>::Err: Display,
{
fn parse(&self, input: &str) -> Result<T, String> {
T::from_str(input).map_err(|err| err.to_string())
}
}
pub struct UrlParser;
impl ArgParser<Url> for UrlParser {
fn parse(&self, input: &str) -> Result<Url, String> {
Url::parse(input).map_err(|err| err.to_string())
}
}
pub struct HexParser;
impl ArgParser<Vec<u8>> for HexParser {
fn parse(&self, mut input: &str) -> Result<Vec<u8>, String> {
if input.starts_with("0x") || input.starts_with("0X") {
input = &input[2..];
}
if input.len() % 2 != 0 {
return Err(format!("Invalid hex string lenth: {}", input.len()));
}
let mut bytes = vec![0u8; input.len() / 2];
hex_decode(input.as_bytes(), &mut bytes)
.map_err(|err| format!("parse hex string failed: {:?}", err))?;
Ok(bytes)
}
}
pub struct FeeRateStaticsTargetParser;
impl ArgParser<u64> for FeeRateStaticsTargetParser {
fn parse(&self, input: &str) -> Result<u64, String> {
let target = FromStrParser::<u64>::default().parse(input)?;
if target == 0 || target > 101 {
return Err(format!("target ({}) is out of range[1 ~ 101]", target));
}
Ok(target)
}
}
#[derive(Default)]
pub struct FixedHashParser<T> {
_h: PhantomData<T>,
}
impl ArgParser<H256> for FixedHashParser<H256> {
fn parse(&self, input: &str) -> Result<H256, String> {
let bytes = HexParser.parse(input)?;
H256::from_slice(&bytes).map_err(|err| err.to_string())
}
}
impl ArgParser<H160> for FixedHashParser<H160> {
fn parse(&self, input: &str) -> Result<H160, String> {
let bytes = HexParser.parse(input)?;
H160::from_slice(&bytes).map_err(|err| err.to_string())
}
}
#[derive(Default)]
pub struct PathParser {
should_exists: bool,
}
impl ArgParser<PathBuf> for PathParser {
fn parse(&self, input: &str) -> Result<PathBuf, String> {
let path = PathBuf::from(input);
if self.should_exists && !path.exists() {
Err(format!("path <{}> not exists", input))
} else {
Ok(path)
}
}
}
#[derive(Default)]
pub struct FilePathParser {
path_parser: PathParser,
}
impl FilePathParser {
pub fn new(should_exists: bool) -> FilePathParser {
FilePathParser {
path_parser: PathParser { should_exists },
}
}
}
impl ArgParser<PathBuf> for FilePathParser {
fn parse(&self, input: &str) -> Result<PathBuf, String> {
let path = self.path_parser.parse(input)?;
if path.exists() && !path.is_file() {
Err(format!("path <{}> is not file", input))
} else {
Ok(path)
}
}
}
#[derive(Default)]
pub struct DirPathParser {
path_parser: PathParser,
}
impl DirPathParser {
pub fn new(should_exists: bool) -> DirPathParser {
DirPathParser {
path_parser: PathParser { should_exists },
}
}
}
impl ArgParser<PathBuf> for DirPathParser {
fn parse(&self, input: &str) -> Result<PathBuf, String> {
let path = self.path_parser.parse(input)?;
if path.exists() && !path.is_dir() {
Err(format!("path <{}> is not directory", input))
} else {
Ok(path)
}
}
}
#[derive(Clone)]
pub struct PrivkeyWrapper(pub secp256k1::SecretKey);
impl Drop for PrivkeyWrapper {
fn drop(&mut self) {
zeroize_privkey(&mut self.0);
}
}
impl std::ops::Deref for PrivkeyWrapper {
type Target = secp256k1::SecretKey;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub struct HexFilePathParser;
impl ArgParser<Vec<u8>> for HexFilePathParser {
fn parse(&self, input: &str) -> Result<Vec<u8>, String> {
let path: PathBuf = FilePathParser::new(true).parse(input)?;
let content = fs::read_to_string(&path).map_err(|err| err.to_string())?;
HexParser.parse(content.as_str())
}
}
pub struct PrivkeyPathParser;
impl ArgParser<PrivkeyWrapper> for PrivkeyPathParser {
fn parse(&self, input: &str) -> Result<PrivkeyWrapper, String> {
let path: PathBuf = FilePathParser::new(true).parse(input)?;
let mut content = String::new();
let mut file = fs::File::open(&path).map_err(|err| err.to_string())?;
file.read_to_string(&mut content)
.map_err(|err| err.to_string())?;
let privkey_string: String = content
.split_whitespace()
.next()
.map(ToOwned::to_owned)
.ok_or_else(|| "File is empty".to_string())?;
let data: H256 = FixedHashParser::<H256>::default().parse(privkey_string.as_str())?;
secp256k1::SecretKey::from_slice(data.as_bytes())
.map(PrivkeyWrapper)
.map_err(|err| format!("Invalid secp256k1 secret key format, error: {}", err))
}
}
pub struct ExtendedPrivkeyPathParser;
impl ArgParser<MasterPrivKey> for ExtendedPrivkeyPathParser {
fn parse(&self, input: &str) -> Result<MasterPrivKey, String> {
let path: PathBuf = FilePathParser::new(true).parse(input)?;
let mut content = String::new();
let mut file = fs::File::open(&path).map_err(|err| err.to_string())?;
file.read_to_string(&mut content)
.map_err(|err| err.to_string())?;
let lines = content
.split_whitespace()
.map(ToOwned::to_owned)
.take(2)
.collect::<Vec<String>>();
if lines.len() < 2 {
return Err("Not enough line for parse extended private key".to_owned());
}
let hash_parser = FixedHashParser::<H256>::default();
let line1: H256 = hash_parser.parse(&lines[0])?;
let line2: H256 = hash_parser.parse(&lines[1])?;
let mut bytes = [0u8; 64];
bytes[0..32].copy_from_slice(&line1.as_bytes()[0..32]);
bytes[32..64].copy_from_slice(&line2.as_bytes()[0..32]);
MasterPrivKey::from_bytes(bytes).map_err(|err| err.to_string())
}
}
pub struct PubkeyHexParser;
impl ArgParser<secp256k1::PublicKey> for PubkeyHexParser {
fn parse(&self, input: &str) -> Result<secp256k1::PublicKey, String> {
let data = HexParser.parse(input)?;
secp256k1::PublicKey::from_slice(&data)
.map_err(|err| format!("Invalid secp256k1 public key format, error: {}", err))
}
}
#[derive(Default, Debug)]
pub struct CellDepsParser;
impl ArgParser<CellDeps> for CellDepsParser {
fn parse(&self, input: &str) -> Result<CellDeps, String> {
let path: PathBuf = FilePathParser::new(true).parse(input)?;
let content = fs::read_to_string(&path).map_err(|err| err.to_string())?;
if input.ends_with(".json") {
serde_json::from_str(content.as_str()).map_err(|err| err.to_string())
} else {
serde_yaml::from_str(content.as_str()).map_err(|err| err.to_string())
}
}
}
#[derive(Default, Debug)]
pub struct AddressParser {
network: Option<NetworkType>,
code_hash: Option<H256>,
hash_type: Option<ScriptHashType>,
}
impl AddressParser {
pub fn new(
network: Option<NetworkType>,
code_hash: Option<H256>,
hash_type: Option<ScriptHashType>,
) -> AddressParser {
AddressParser {
network,
code_hash,
hash_type,
}
}
pub fn new_sighash() -> Self {
AddressParser::new(None, Some(SIGHASH_TYPE_HASH), Some(ScriptHashType::Type))
}
pub fn new_multisig() -> Self {
AddressParser::new(None, Some(MULTISIG_TYPE_HASH), Some(ScriptHashType::Type))
}
pub fn set_network(&mut self, network: NetworkType) -> &mut Self {
self.network = Some(network);
self
}
pub fn set_network_opt(&mut self, network: Option<NetworkType>) -> &mut Self {
self.network = network;
self
}
}
impl ArgParser<Address> for AddressParser {
fn parse(&self, input: &str) -> Result<Address, String> {
if let Ok(address) = Address::from_str(input) {
if matches!(address.network(), NetworkType::Staging | NetworkType::Dev)
&& address.payload().is_short_acp()
{
return Err("only mainnet(line) and testnet(aggron) support short format anone-can-pay address".to_string());
}
if let Some(network) = self.network {
if address.network().to_prefix() != network.to_prefix() {
return Err(format!(
"Invalid network: {}, expected: {}",
address.network().to_prefix(),
network.to_prefix(),
));
}
}
let payload = address.payload();
if let Some(expected_code_hash) = self.code_hash.as_ref() {
let code_hash: H256 = payload.code_hash(Some(address.network())).unpack();
if *expected_code_hash != code_hash {
return Err(format!(
"Invalid address code hash: {:#x}, expected code hash: {:#x}",
code_hash, expected_code_hash
));
}
}
if let Some(expected_hash_type) = self.hash_type {
let hash_type = payload.hash_type();
if expected_hash_type != hash_type {
return Err(format!(
"Invalid address hash type: {:?}, expected hash type: {:?}",
hash_type, expected_hash_type
));
}
}
return Ok(address);
}
let prefix = input.chars().take(3).collect::<String>();
let network = NetworkType::from_prefix(prefix.as_str())
.ok_or_else(|| format!("Invalid address prefix: {}", prefix))?;
let old_address = OldAddress::from_input(network, input)?;
let payload = AddressPayload::from_pubkey_hash(old_address.hash().clone());
Ok(Address::new(NetworkType::Testnet, payload, false))
}
}
pub struct CapacityParser;
impl ArgParser<HumanCapacity> for CapacityParser {
fn parse(&self, input: &str) -> Result<HumanCapacity, String> {
HumanCapacity::from_str(input)
}
}
pub struct OutPointParser;
impl ArgParser<OutPoint> for OutPointParser {
fn parse(&self, input: &str) -> Result<OutPoint, String> {
let parts = input.split('-').collect::<Vec<_>>();
if parts.len() != 2 {
return Err(format!(
"Invalid OutPoint: {}, format: {{tx-hash}}-{{index}}",
input
));
}
let tx_hash: H256 = FixedHashParser::<H256>::default().parse(parts[0])?;
let index = FromStrParser::<u32>::default().parse(parts[1])?;
Ok(OutPoint::new(tx_hash.pack(), index))
}
}
pub struct UdtTargetParser {
address_parser: AddressParser,
}
impl UdtTargetParser {
pub fn new(address_parser: AddressParser) -> UdtTargetParser {
UdtTargetParser { address_parser }
}
}
impl ArgParser<(Address, u128)> for UdtTargetParser {
fn parse(&self, input: &str) -> Result<(Address, u128), String> {
if let Some((addr_str, amount_str)) = input.split_once(':') {
let address: Address = self.address_parser.parse(addr_str)?;
let amount = FromStrParser::<u128>::default()
.parse(amount_str)
.map_err(|err| format!("invalid amount: {}, error: {}", amount_str, err))?;
Ok((address, amount))
} else {
Err(format!(
"Invalid udt target: {}, format: {{address}}:{{amount}}",
input
))
}
}
}
pub struct ScriptIdParser;
impl ArgParser<ScriptId> for ScriptIdParser {
fn parse(&self, input: &str) -> Result<ScriptId, String> {
if let Some((code_hash_str, hash_type_str)) = input.split_once('-') {
let code_hash: H256 = FixedHashParser::<H256>::default().parse(code_hash_str)?;
match hash_type_str {
"type" => Ok(ScriptId::new_type(code_hash)),
"data" => Ok(ScriptId::new_data(code_hash)),
"data1" => Ok(ScriptId::new_data1(code_hash)),
_ => Err(format!("invalid hash_type: {}", hash_type_str)),
}
} else {
Err(format!(
"Invalid script id: {}, format: {{code_hash}}-{{hash_type}}, `hash_type` can be: [type, data, data1]",
input
))
}
}
}
pub struct DurationParser;
impl ArgParser<Duration> for DurationParser {
fn parse(&self, input: &str) -> Result<Duration, String> {
if input.is_empty() {
return Err("Missing input".to_owned());
}
let input_lower = input.to_lowercase();
let value_part = &input_lower[0..input_lower.len() - 1];
let value: u64 = value_part.parse::<u64>().map_err(|err| err.to_string())?;
let unit_part = &input_lower[input_lower.len() - 1..input_lower.len()];
let seconds = match unit_part {
"s" => value,
"m" => value * 60,
"h" => value * 3600,
"d" => value * 3600 * 24,
_ => {
return Err(
"Please give an unit, {{s: second, m: minute, h: hour, d: day}}".to_owned(),
);
}
};
Ok(Duration::from_secs(seconds))
}
}
pub struct SocketParser;
impl ArgParser<::std::net::SocketAddr> for SocketParser {
fn parse(&self, input: &str) -> Result<::std::net::SocketAddr, String> {
input
.to_socket_addrs()
.map_err(|e| e.to_string())
.and_then(|mut iter| iter.next().ok_or_else(|| "must socket format".to_string()))
}
}
#[cfg(test)]
mod tests {
use ckb_sdk::CodeHashIndex;
use ckb_types::{bytes::Bytes, core::ScriptHashType, h160, h256, prelude::*};
use std::net::IpAddr;
use super::*;
#[test]
fn test_from_str() {
assert_eq!(FromStrParser::<u64>::default().parse("456"), Ok(456));
assert_eq!(FromStrParser::<i64>::default().parse("-34"), Ok(-34));
assert_eq!(
FromStrParser::<IpAddr>::new().parse("192.168.1.1"),
Ok("192.168.1.1".parse().unwrap())
);
assert!(FromStrParser::<u64>::default().parse("-34").is_err());
assert!(FromStrParser::<u64>::default().parse("xxy").is_err());
assert!(FromStrParser::<u64>::default().parse("3x").is_err());
}
#[test]
fn test_hex() {
assert_eq!(HexParser.parse("0x3a"), Ok(vec![0x3a]));
assert_eq!(HexParser.parse("0Xaa"), Ok(vec![0xaa]));
assert_eq!(HexParser.parse("3a6665"), Ok(vec![0x3a, 0x66, 0x65]));
assert!(HexParser.parse("0x3a665").is_err());
assert!(HexParser.parse("abcdefghi").is_err());
}
#[test]
fn test_fixed_hash() {
assert_eq!(
FixedHashParser::<H256>::default()
.parse("0xac71d52d9c1c693a4136513d7c62b0a6441b14ced02518650fe673dfcb6c016c"),
Ok(h256!(
"0xac71d52d9c1c693a4136513d7c62b0a6441b14ced02518650fe673dfcb6c016c"
)),
);
assert_eq!(
FixedHashParser::<H256>::default()
.parse("ac71d52d9c1c693a4136513d7c62b0a6441b14ced02518650fe673dfcb6c016c"),
Ok(h256!(
"0xac71d52d9c1c693a4136513d7c62b0a6441b14ced02518650fe673dfcb6c016c"
)),
);
assert!(FixedHashParser::<H256>::default()
.parse("71d52d9c1c693a4136513d7c62b0a6441b14ced02518650fe673dfcb6c016c")
.is_err());
assert!(FixedHashParser::<H256>::default()
.parse("71d52d9c1c693a4136513d7c62b0a6441b14ced02518650fe673dfcb6c016ccccc")
.is_err());
}
#[test]
fn test_address() {
assert_eq!(
AddressParser::default().parse("ckt1q9gry5zgughh7wzcxzn4u59t0lzl6np4ky60r6ztpw69rl"),
Ok(Address::new(
NetworkType::Testnet,
AddressPayload::new_short(
CodeHashIndex::Sighash,
h160!("0xe22f7f385830a75e50ab7fc5fd4c35b134f1e84b")
),
false,
))
);
assert_eq!(
AddressParser::default().parse("ckb1qyqp8eqad7ffy42ezmchkjyz54rhcqf8q9pqrn323p"),
Ok(Address::new(
NetworkType::Mainnet,
AddressPayload::new_short(
CodeHashIndex::Sighash,
h160!("0x13e41d6F9292555916f17B4882a5477C01270142")
),
false
))
);
assert!(AddressParser::default()
.parse("kt1q9gry5zgzkfc6rznfaequqlcmdeh4fhta4uwn4qajhqxyc")
.is_err());
assert!(AddressParser::default()
.parse("ckt1q9gry5zgzkfc6rznfaequqlcmdeh4fhta4uwn4qajhqxy")
.is_err());
assert!(AddressParser::new_sighash()
.parse("ckb1qyqp8eqad7ffy42ezmchkjyz54rhcqfpqrn323p")
.is_err());
assert!(AddressParser::default()
.parse("kb1qyqp8eqad7ffy42ezmchkjyz54rhcqf8q9pqrn323p")
.is_err());
let mut args = vec![0u8; 20];
faster_hex::hex_decode(b"b39bbc0b3673c7d36450bc14cfcdad2d559c6c64", &mut args).unwrap();
assert_eq!(
AddressParser::default().parse("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdnnw7qkdnnclfkg59uzn8umtfd2kwxceqxwquc4"),
Ok(Address::new(
NetworkType::Mainnet,
AddressPayload::new_full(
ScriptHashType::Type,
h256!("0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8").pack(),
Bytes::from(args),
),
true,
))
)
}
}