diff --git a/cmd/crates/soroban-test/src/lib.rs b/cmd/crates/soroban-test/src/lib.rs index 2c62578ef..96074e23d 100644 --- a/cmd/crates/soroban-test/src/lib.rs +++ b/cmd/crates/soroban-test/src/lib.rs @@ -204,7 +204,7 @@ impl TestEnv { let cmd = self.cmd_with_config::(command_str, None); self.run_cmd_with(cmd, source) .await - .map(|r| r.into_result().unwrap()) + .map(|tx| tx.into_result().unwrap()) } /// A convenience method for using the invoke command. diff --git a/cmd/crates/soroban-test/tests/it/help.rs b/cmd/crates/soroban-test/tests/it/help.rs index ef84a361b..7ddaf3afc 100644 --- a/cmd/crates/soroban-test/tests/it/help.rs +++ b/cmd/crates/soroban-test/tests/it/help.rs @@ -1,3 +1,5 @@ +use soroban_cli::commands::contract::arg_parsing::Error::HelpMessage; +use soroban_cli::commands::contract::invoke::Error::ArgParsing; use soroban_cli::commands::contract::{self, arg_parsing}; use soroban_test::TestEnv; @@ -5,7 +7,11 @@ use crate::util::{invoke_custom as invoke, CUSTOM_TYPES, DEFAULT_CONTRACT_ID}; async fn invoke_custom(func: &str, args: &str) -> Result { let e = &TestEnv::default(); - invoke(e, DEFAULT_CONTRACT_ID, func, args, &CUSTOM_TYPES.path()).await + let r = invoke(e, DEFAULT_CONTRACT_ID, func, args, &CUSTOM_TYPES.path()).await; + if let Err(ArgParsing(HelpMessage(e))) = r { + return Ok(e); + } + r } #[tokio::test] @@ -35,6 +41,7 @@ async fn tuple_help() { #[tokio::test] async fn strukt_help() { let output = invoke_custom("strukt", "--help").await.unwrap(); + println!("{output}"); assert!(output.contains("--strukt '{ \"a\": 1, \"b\": true, \"c\": \"hello\" }'",)); assert!(output.contains("This is from the rust doc above the struct Test",)); } diff --git a/cmd/crates/soroban-test/tests/it/util.rs b/cmd/crates/soroban-test/tests/it/util.rs index a74428666..7f652bb68 100644 --- a/cmd/crates/soroban-test/tests/it/util.rs +++ b/cmd/crates/soroban-test/tests/it/util.rs @@ -1,10 +1,9 @@ -use std::path::Path; - use soroban_cli::{ commands::contract, config::{locator::KeyType, secret::Secret}, }; use soroban_test::{TestEnv, Wasm, TEST_ACCOUNT}; +use std::path::Path; pub const CUSTOM_TYPES: &Wasm = &Wasm::Custom("test-wasms", "test_custom_types"); @@ -54,10 +53,8 @@ pub async fn invoke_custom( let mut i: contract::invoke::Cmd = sandbox.cmd_with_config(&["--id", id, "--", func, arg], None); i.wasm = Some(wasm.to_path_buf()); - sandbox - .run_cmd_with(i, TEST_ACCOUNT) - .await - .map(|r| r.into_result().unwrap()) + let s = sandbox.run_cmd_with(i, TEST_ACCOUNT).await; + s.map(|tx| tx.into_result().unwrap()) } pub const DEFAULT_CONTRACT_ID: &str = "CDR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OO5Z"; diff --git a/cmd/soroban-cli/src/commands/contract/arg_parsing.rs b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs index 21fa2f383..27350bb74 100644 --- a/cmd/soroban-cli/src/commands/contract/arg_parsing.rs +++ b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs @@ -1,21 +1,20 @@ -use std::collections::HashMap; -use std::convert::TryInto; -use std::ffi::OsString; -use std::fmt::Debug; -use std::path::PathBuf; - -use clap::value_parser; -use ed25519_dalek::SigningKey; -use heck::ToKebabCase; - +use crate::commands::contract::arg_parsing::Error::HelpMessage; +use crate::commands::txn_result::TxnResult; +use crate::config::{self}; use crate::xdr::{ self, Hash, InvokeContractArgs, ScAddress, ScSpecEntry, ScSpecFunctionV0, ScSpecTypeDef, ScVal, ScVec, }; - -use crate::commands::txn_result::TxnResult; -use crate::config::{self}; +use clap::error::ErrorKind::DisplayHelp; +use clap::value_parser; +use ed25519_dalek::SigningKey; +use heck::ToKebabCase; use soroban_spec_tools::Spec; +use std::collections::HashMap; +use std::convert::TryInto; +use std::ffi::OsString; +use std::fmt::Debug; +use std::path::PathBuf; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -43,14 +42,18 @@ pub enum Error { MissingArgument(String), #[error("")] MissingFileArg(PathBuf), + #[error("")] + HelpMessage(String), } +pub type HostFunctionParameters = (String, Spec, InvokeContractArgs, Vec); + pub fn build_host_function_parameters( contract_id: &stellar_strkey::Contract, slop: &[OsString], spec_entries: &[ScSpecEntry], config: &config::Args, -) -> Result<(String, Spec, InvokeContractArgs, Vec), Error> { +) -> Result { let spec = Spec(Some(spec_entries.to_vec())); let mut cmd = clap::Command::new(contract_id.to_string()) .no_binary_name(true) @@ -63,12 +66,20 @@ pub fn build_host_function_parameters( cmd.build(); let long_help = cmd.render_long_help(); - // get_matches_from exits the process if `help`, `--help` or `-h`are passed in the slop - // see clap documentation for more info: https://github.com/clap-rs/clap/blob/v4.1.8/src/builder/command.rs#L631 - let mut matches_ = cmd.get_matches_from(slop); - let Some((function, matches_)) = &matches_.remove_subcommand() else { - println!("{long_help}"); - std::process::exit(1); + // try_get_matches_from returns an error if `help`, `--help` or `-h`are passed in the slop + // see clap documentation for more info: https://github.com/clap-rs/clap/blob/v4.1.8/src/builder/command.rs#L586 + let maybe_matches = cmd.try_get_matches_from(slop); + let Some((function, matches_)) = (match maybe_matches { + Ok(mut matches) => &matches.remove_subcommand(), + Err(e) => { + // to not exit immediately (to be able to fetch help message in tests), check for an error + if e.kind() == DisplayHelp { + return Err(HelpMessage(e.to_string())); + } + e.exit(); + } + }) else { + return Err(HelpMessage(format!("{long_help}"))); }; let func = spec.find_function(function)?; diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index 1d85832b0..bc4548b11 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -1,8 +1,3 @@ -use std::array::TryFromSliceError; -use std::ffi::OsString; -use std::fmt::Debug; -use std::num::ParseIntError; - use crate::xdr::{ AccountId, ContractExecutable, ContractIdPreimage, ContractIdPreimageFromAddress, CreateContractArgs, CreateContractArgsV2, Error as XdrError, Hash, HostFunction, @@ -13,9 +8,14 @@ use crate::xdr::{ use clap::{arg, command, Parser}; use rand::Rng; use regex::Regex; - use soroban_spec_tools::contract as contract_spec; +use std::array::TryFromSliceError; +use std::ffi::OsString; +use std::fmt::Debug; +use std::num::ParseIntError; +use crate::commands::contract::arg_parsing::Error::HelpMessage; +use crate::commands::contract::deploy::wasm::Error::ArgParse; use crate::{ assembled::simulate_and_assemble_transaction, commands::{ @@ -128,26 +128,32 @@ pub enum Error { impl Cmd { pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { - let res = self - .run_against_rpc_server(Some(global_args), None) - .await? - .to_envelope(); + let res = self.run_against_rpc_server(Some(global_args), None).await; match res { - TxnEnvelopeResult::TxnEnvelope(tx) => println!("{}", tx.to_xdr_base64(Limits::none())?), - TxnEnvelopeResult::Res(contract) => { - let network = self.config.get_network()?; - - if let Some(alias) = self.alias.clone() { - self.config.locator.save_contract_id( - &network.network_passphrase, - &contract, - &alias, - )?; + Ok(res) => match res.to_envelope() { + TxnEnvelopeResult::TxnEnvelope(tx) => { + println!("{}", tx.to_xdr_base64(Limits::none())?); } - - println!("{contract}"); + TxnEnvelopeResult::Res(contract) => { + let network = self.config.get_network()?; + + if let Some(alias) = self.alias.clone() { + self.config.locator.save_contract_id( + &network.network_passphrase, + &contract, + &alias, + )?; + } + + println!("{contract}"); + } + }, + Err(ArgParse(HelpMessage(help))) => { + println!("{help}"); } + Err(e) => return Err(e), } + Ok(()) } } @@ -248,15 +254,13 @@ impl NetworkRunnable for Cmd { } else { let mut slop = vec![OsString::from(CONSTRUCTOR_FUNCTION_NAME)]; slop.extend_from_slice(&self.slop); - Some( - arg_parsing::build_host_function_parameters( - &stellar_strkey::Contract(contract_id.0), - &slop, - &entries, - config, - )? - .2, - ) + let params = arg_parsing::build_host_function_parameters( + &stellar_strkey::Contract(contract_id.0), + &slop, + &entries, + config, + )?; + Some(params.2) } } else { None diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index c7b631343..a42f0a926 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -6,12 +6,13 @@ use std::str::FromStr; use std::{fmt::Debug, fs, io}; use clap::{arg, command, Parser, ValueEnum}; - use soroban_rpc::{Client, SimulateHostFunctionResult, SimulateTransactionResponse}; use soroban_spec::read::FromWasmError; use super::super::events; use super::arg_parsing; +use crate::commands::contract::arg_parsing::Error::HelpMessage; +use crate::commands::contract::invoke::Error::ArgParsing; use crate::{ assembled::simulate_and_assemble_transaction, commands::{ @@ -131,12 +132,20 @@ impl From for Error { impl Cmd { pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { - let res = self.invoke(global_args).await?.to_envelope(); + let res = self.invoke(global_args).await; match res { - TxnEnvelopeResult::TxnEnvelope(tx) => println!("{}", tx.to_xdr_base64(Limits::none())?), - TxnEnvelopeResult::Res(output) => { - println!("{output}"); + Ok(res) => match res.to_envelope() { + TxnEnvelopeResult::TxnEnvelope(tx) => { + println!("{}", tx.to_xdr_base64(Limits::none())?); + } + TxnEnvelopeResult::Res(output) => { + println!("{output}"); + } + }, + Err(ArgParsing(HelpMessage(help))) => { + println!("{help}"); } + Err(e) => return Err(e), } Ok(()) } @@ -221,7 +230,7 @@ impl NetworkRunnable for Cmd { let spec_entries = self.spec_entries()?; if let Some(spec_entries) = &spec_entries { // For testing wasm arg parsing - let _ = build_host_function_parameters(&contract_id, &self.slop, spec_entries, config)?; + build_host_function_parameters(&contract_id, &self.slop, spec_entries, config)?; } let client = network.rpc_client()?; @@ -235,9 +244,11 @@ impl NetworkRunnable for Cmd { .await .map_err(Error::from)?; - let (function, spec, host_function_params, signers) = + let params = build_host_function_parameters(&contract_id, &self.slop, &spec_entries, config)?; + let (function, spec, host_function_params, signers) = params; + let should_send_tx = self .should_send_after_sim(host_function_params.clone(), client.clone()) .await?;