From 311140f6f68da42779eaa4b6076cf1ab24102b5b Mon Sep 17 00:00:00 2001 From: jwinterm Date: Tue, 2 Jun 2026 21:02:24 -0400 Subject: [PATCH] De-panic contract CLI argument parsing The contract new/sign/revoke parsers panicked or unwrapped on user input: conflicting --no-payjoin/--use-inputs, and unparseable receive / send / num-participants / tx-id values would abort the process. Return ParseError instead, and add a parse_grin_amount helper for the Grin->nanogram conversion. --- src/cmd/wallet_args.rs | 45 ++++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/src/cmd/wallet_args.rs b/src/cmd/wallet_args.rs index a0f48d2..60c96a8 100644 --- a/src/cmd/wallet_args.rs +++ b/src/cmd/wallet_args.rs @@ -288,6 +288,17 @@ fn parse_u64_or_none(arg: Option<&str>) -> Option { } } +// Parse a value expressed in Grin (e.g. "1.5") into nanograms. +fn parse_grin_amount(arg: &str, name: &str) -> Result { + match arg.parse::() { + Ok(g) => Ok((g * consensus::GRIN_BASE as f64) as u64), + Err(e) => Err(ParseError::ArgumentError(format!( + "Could not parse {} '{}' as a number. e={}", + name, arg, e + ))), + } +} + pub fn parse_global_args( config: &WalletConfig, args: &ArgMatches, @@ -971,17 +982,17 @@ pub fn parse_contract_new_args( args: &ArgMatches, account: &String, ) -> Result { - let counterparty_addr = args.value_of("encrypt-for").unwrap(); + let counterparty_addr = parse_required(args, "encrypt-for")?; // TODO: Make sure the values are in some expected bounds. // TODO: How to deal with decimals and precision? we probably want users to express the value in Grin. // Parse receive and send params and convert them to nano grin let receive = match args.value_of("receive") { - Some(g) => Some((g.parse::().unwrap() * consensus::GRIN_BASE as f64) as u64), + Some(g) => Some(parse_grin_amount(g, "receive")?), None => None, }; let send = match args.value_of("send") { - Some(g) => Some((g.parse::().unwrap() * consensus::GRIN_BASE as f64) as u64), + Some(g) => Some(parse_grin_amount(g, "send")?), None => None, }; if receive.is_some() && send.is_some() { @@ -997,7 +1008,9 @@ pub fn parse_contract_new_args( let use_inputs = match args.value_of("use-inputs") { Some(v) => { if no_payjoin { - panic!("Can't use --no-payjoin with --use-inputs."); + return Err(ParseError::ArgumentError(String::from( + "Can't use --no-payjoin with --use-inputs.", + ))); } Some(String::from(v)) } @@ -1015,11 +1028,12 @@ pub fn parse_contract_new_args( None => None, }; - let num_participants = args - .value_of("num-participants") - .unwrap() - .parse::() - .unwrap(); + let num_participants = match args.value_of("num-participants") { + Some(v) => v.parse::().map_err(|e| { + ParseError::ArgumentError(format!("Could not parse num-participants '{}'. e={}", v, e)) + })?, + None => 2, + }; Ok(command::ContractNewArgs { counterparty_addr: String::from(counterparty_addr), @@ -1049,11 +1063,11 @@ pub fn parse_contract_setup_args( }; // Parse receive and send params and convert them to nano grin let receive = match args.value_of("receive") { - Some(g) => Some((g.parse::().unwrap() * consensus::GRIN_BASE as f64) as u64), + Some(g) => Some(parse_grin_amount(g, "receive")?), None => None, }; let send = match args.value_of("send") { - Some(g) => Some((g.parse::().unwrap() * consensus::GRIN_BASE as f64) as u64), + Some(g) => Some(parse_grin_amount(g, "send")?), None => None, }; if receive.is_some() && send.is_some() { @@ -1066,7 +1080,9 @@ pub fn parse_contract_setup_args( let use_inputs = match args.value_of("use-inputs") { Some(v) => { if no_payjoin { - panic!("Can't use --no-payjoin with --use-inputs."); + return Err(ParseError::ArgumentError(String::from( + "Can't use --no-payjoin with --use-inputs.", + ))); } Some(String::from(v)) } @@ -1103,7 +1119,10 @@ pub fn parse_contract_setup_args( pub fn parse_contract_revoke_args( args: &ArgMatches, ) -> Result { - let tx_id = args.value_of("tx-id").unwrap().parse::().unwrap(); + let tx_id_str = parse_required(args, "tx-id")?; + let tx_id = tx_id_str.parse::().map_err(|e| { + ParseError::ArgumentError(format!("Could not parse tx-id '{}'. e={}", tx_id_str, e)) + })?; Ok(command::ContractRevokeArgs { tx_id: tx_id }) }