diff --git a/cmd/crates/soroban-test/tests/it/help.rs b/cmd/crates/soroban-test/tests/it/help.rs index a66c449ed..ef84a361b 100644 --- a/cmd/crates/soroban-test/tests/it/help.rs +++ b/cmd/crates/soroban-test/tests/it/help.rs @@ -1,4 +1,4 @@ -use soroban_cli::commands::contract; +use soroban_cli::commands::contract::{self, arg_parsing}; use soroban_test::TestEnv; use crate::util::{invoke_custom as invoke, CUSTOM_TYPES, DEFAULT_CONTRACT_ID}; @@ -55,7 +55,7 @@ async fn complex_enum_help() { async fn multi_arg_failure() { assert!(matches!( invoke_custom("multi_args", "--b").await.unwrap_err(), - contract::invoke::Error::MissingArgument(_) + contract::invoke::Error::ArgParsing(arg_parsing::Error::MissingArgument(_)) )); } @@ -64,7 +64,9 @@ async fn handle_arg_larger_than_i32_failure() { let res = invoke_custom("i32_", &format!("--i32_={}", u32::MAX)).await; assert!(matches!( res, - Err(contract::invoke::Error::CannotParseArg { .. }) + Err(contract::invoke::Error::ArgParsing( + arg_parsing::Error::CannotParseArg { .. } + )) )); } @@ -73,7 +75,9 @@ async fn handle_arg_larger_than_i64_failure() { let res = invoke_custom("i64_", &format!("--i64_={}", u64::MAX)).await; assert!(matches!( res, - Err(contract::invoke::Error::CannotParseArg { .. }) + Err(contract::invoke::Error::ArgParsing( + arg_parsing::Error::CannotParseArg { .. } + )) )); } diff --git a/cmd/soroban-cli/src/commands/contract/arg_parsing.rs b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs new file mode 100644 index 000000000..4a8af47e2 --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs @@ -0,0 +1,245 @@ +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 soroban_env_host::xdr::{ + self, Hash, InvokeContractArgs, ScAddress, ScSpecEntry, ScSpecFunctionV0, ScSpecTypeDef, ScVal, + ScVec, +}; + +use crate::commands::txn_result::TxnResult; +use crate::config::{self}; +use soroban_spec_tools::Spec; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("parsing argument {arg}: {error}")] + CannotParseArg { + arg: String, + error: soroban_spec_tools::Error, + }, + #[error("cannot print result {result:?}: {error}")] + CannotPrintResult { + result: ScVal, + error: soroban_spec_tools::Error, + }, + #[error("function {0} was not found in the contract")] + FunctionNotFoundInContractSpec(String), + #[error("function name {0} is too long")] + FunctionNameTooLong(String), + #[error("argument count ({current}) surpasses maximum allowed count ({maximum})")] + MaxNumberOfArgumentsReached { current: usize, maximum: usize }, + #[error(transparent)] + Xdr(#[from] xdr::Error), + #[error(transparent)] + StrVal(#[from] soroban_spec_tools::Error), + #[error("Missing argument {0}")] + MissingArgument(String), + #[error("")] + MissingFileArg(PathBuf), +} + +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> { + let spec = Spec(Some(spec_entries.to_vec())); + let mut cmd = clap::Command::new(contract_id.to_string()) + .no_binary_name(true) + .term_width(300) + .max_term_width(300); + + for ScSpecFunctionV0 { name, .. } in spec.find_functions()? { + cmd = cmd.subcommand(build_custom_cmd(&name.to_utf8_string_lossy(), &spec)?); + } + cmd.build(); + let long_help = cmd.render_long_help(); + let mut matches_ = cmd.get_matches_from(slop); + let Some((function, matches_)) = &matches_.remove_subcommand() else { + println!("{long_help}"); + std::process::exit(1); + }; + + let func = spec.find_function(function)?; + // create parsed_args in same order as the inputs to func + let mut signers: Vec = vec![]; + let parsed_args = func + .inputs + .iter() + .map(|i| { + let name = i.name.to_utf8_string()?; + if let Some(mut val) = matches_.get_raw(&name) { + let mut s = val.next().unwrap().to_string_lossy().to_string(); + if matches!(i.type_, ScSpecTypeDef::Address) { + let cmd = crate::commands::keys::address::Cmd { + name: s.clone(), + hd_path: Some(0), + locator: config.locator.clone(), + }; + if let Ok(address) = cmd.public_key() { + s = address.to_string(); + } + if let Ok(key) = cmd.private_key() { + signers.push(key); + } + } + spec.from_string(&s, &i.type_) + .map_err(|error| Error::CannotParseArg { arg: name, error }) + } else if matches!(i.type_, ScSpecTypeDef::Option(_)) { + Ok(ScVal::Void) + } else if let Some(arg_path) = matches_.get_one::(&fmt_arg_file_name(&name)) { + if matches!(i.type_, ScSpecTypeDef::Bytes | ScSpecTypeDef::BytesN(_)) { + Ok(ScVal::try_from( + &std::fs::read(arg_path) + .map_err(|_| Error::MissingFileArg(arg_path.clone()))?, + ) + .map_err(|()| Error::CannotParseArg { + arg: name.clone(), + error: soroban_spec_tools::Error::Unknown, + })?) + } else { + let file_contents = std::fs::read_to_string(arg_path) + .map_err(|_| Error::MissingFileArg(arg_path.clone()))?; + tracing::debug!( + "file {arg_path:?}, has contents:\n{file_contents}\nAnd type {:#?}\n{}", + i.type_, + file_contents.len() + ); + spec.from_string(&file_contents, &i.type_) + .map_err(|error| Error::CannotParseArg { arg: name, error }) + } + } else { + Err(Error::MissingArgument(name)) + } + }) + .collect::, Error>>()?; + + let contract_address_arg = ScAddress::Contract(Hash(contract_id.0)); + let function_symbol_arg = function + .try_into() + .map_err(|()| Error::FunctionNameTooLong(function.clone()))?; + + let final_args = + parsed_args + .clone() + .try_into() + .map_err(|_| Error::MaxNumberOfArgumentsReached { + current: parsed_args.len(), + maximum: ScVec::default().max_len(), + })?; + + let invoke_args = InvokeContractArgs { + contract_address: contract_address_arg, + function_name: function_symbol_arg, + args: final_args, + }; + + Ok((function.clone(), spec, invoke_args, signers)) +} + +fn build_custom_cmd(name: &str, spec: &Spec) -> Result { + let func = spec + .find_function(name) + .map_err(|_| Error::FunctionNotFoundInContractSpec(name.to_string()))?; + + // Parse the function arguments + let inputs_map = &func + .inputs + .iter() + .map(|i| (i.name.to_utf8_string().unwrap(), i.type_.clone())) + .collect::>(); + let name: &'static str = Box::leak(name.to_string().into_boxed_str()); + let mut cmd = clap::Command::new(name) + .no_binary_name(true) + .term_width(300) + .max_term_width(300); + let kebab_name = name.to_kebab_case(); + if kebab_name != name { + cmd = cmd.alias(kebab_name); + } + let doc: &'static str = Box::leak(func.doc.to_utf8_string_lossy().into_boxed_str()); + let long_doc: &'static str = Box::leak(arg_file_help(doc).into_boxed_str()); + + cmd = cmd.about(Some(doc)).long_about(long_doc); + for (name, type_) in inputs_map { + let mut arg = clap::Arg::new(name); + let file_arg_name = fmt_arg_file_name(name); + let mut file_arg = clap::Arg::new(&file_arg_name); + arg = arg + .long(name) + .alias(name.to_kebab_case()) + .num_args(1) + .value_parser(clap::builder::NonEmptyStringValueParser::new()) + .long_help(spec.doc(name, type_)?); + + file_arg = file_arg + .long(&file_arg_name) + .alias(file_arg_name.to_kebab_case()) + .num_args(1) + .hide(true) + .value_parser(value_parser!(PathBuf)) + .conflicts_with(name); + + if let Some(value_name) = spec.arg_value_name(type_, 0) { + let value_name: &'static str = Box::leak(value_name.into_boxed_str()); + arg = arg.value_name(value_name); + } + + // Set up special-case arg rules + arg = match type_ { + ScSpecTypeDef::Bool => arg + .num_args(0..1) + .default_missing_value("true") + .default_value("false") + .num_args(0..=1), + ScSpecTypeDef::Option(_val) => arg.required(false), + ScSpecTypeDef::I256 | ScSpecTypeDef::I128 | ScSpecTypeDef::I64 | ScSpecTypeDef::I32 => { + arg.allow_hyphen_values(true) + } + _ => arg, + }; + + cmd = cmd.arg(arg); + cmd = cmd.arg(file_arg); + } + Ok(cmd) +} + +fn fmt_arg_file_name(name: &str) -> String { + format!("{name}-file-path") +} + +fn arg_file_help(docs: &str) -> String { + format!( + r#"{docs} +Usage Notes: +Each arg has a corresponding ---file-path which is a path to a file containing the corresponding JSON argument. +Note: The only types which aren't JSON are Bytes and BytesN, which are raw bytes"# + ) +} + +pub fn output_to_string( + spec: &Spec, + res: &ScVal, + function: &str, +) -> Result, Error> { + let mut res_str = String::new(); + if let Some(output) = spec.find_function(function)?.outputs.first() { + res_str = spec + .xdr_to_json(res, output) + .map_err(|e| Error::CannotPrintResult { + result: res.clone(), + error: e, + })? + .to_string(); + } + Ok(TxnResult::Res(res_str)) +} diff --git a/cmd/soroban-cli/src/commands/contract/extend.rs b/cmd/soroban-cli/src/commands/contract/extend.rs index 03c7c70cf..a0e563e58 100644 --- a/cmd/soroban-cli/src/commands/contract/extend.rs +++ b/cmd/soroban-cli/src/commands/contract/extend.rs @@ -130,11 +130,7 @@ impl NetworkRunnable for Cmd { let config = config.unwrap_or(&self.config); let network = config.get_network()?; tracing::trace!(?network); - let contract = config.locator.resolve_contract_id( - self.key.contract_id.as_ref().unwrap(), - &network.network_passphrase, - )?; - let keys = self.key.parse_keys(contract)?; + let keys = self.key.parse_keys(&config.locator, &network)?; let network = &config.get_network()?; let client = Client::new(&network.rpc_url)?; let key = config.key_pair()?; diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 6fcfb1e31..293867c76 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::convert::{Infallible, TryInto}; use std::ffi::OsString; use std::num::ParseIntError; @@ -6,18 +5,15 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use std::{fmt::Debug, fs, io}; -use clap::{arg, command, value_parser, Parser, ValueEnum}; -use ed25519_dalek::SigningKey; -use heck::ToKebabCase; +use clap::{arg, command, Parser, ValueEnum}; use soroban_env_host::{ xdr::{ self, AccountEntry, AccountEntryExt, AccountId, ContractEvent, ContractEventType, - DiagnosticEvent, Hash, HostFunction, InvokeContractArgs, InvokeHostFunctionOp, - LedgerEntryData, Limits, Memo, MuxedAccount, Operation, OperationBody, Preconditions, - PublicKey, ScAddress, ScSpecEntry, ScSpecFunctionV0, ScSpecTypeDef, ScVal, ScVec, - SequenceNumber, String32, StringM, Thresholds, Transaction, TransactionExt, Uint256, VecM, - WriteXdr, + DiagnosticEvent, HostFunction, InvokeContractArgs, InvokeHostFunctionOp, LedgerEntryData, + Limits, Memo, MuxedAccount, Operation, OperationBody, Preconditions, PublicKey, + ScSpecEntry, SequenceNumber, String32, StringM, Thresholds, Transaction, TransactionExt, + Uint256, VecM, WriteXdr, }, HostError, }; @@ -26,6 +22,8 @@ use soroban_rpc::{SimulateHostFunctionResult, SimulateTransactionResponse}; use soroban_spec::read::FromWasmError; use super::super::events; +use super::arg_parsing; +use crate::commands::contract::arg_parsing::{build_host_function_parameters, output_to_string}; use crate::commands::txn_result::{TxnEnvelopeResult, TxnResult}; use crate::commands::NetworkRunnable; use crate::get_spec::{self, get_remote_contract_spec}; @@ -35,7 +33,7 @@ use crate::{ config::{self, data, locator, network}, rpc, Pwd, }; -use soroban_spec_tools::{contract, Spec}; +use soroban_spec_tools::contract; #[derive(Parser, Debug, Default, Clone)] #[allow(clippy::struct_excessive_bools)] @@ -79,11 +77,6 @@ impl Pwd for Cmd { #[derive(thiserror::Error, Debug)] pub enum Error { - #[error("parsing argument {arg}: {error}")] - CannotParseArg { - arg: String, - error: soroban_spec_tools::Error, - }, #[error("cannot add contract to ledger entries: {0}")] CannotAddContractToLedgerEntries(xdr::Error), #[error(transparent)] @@ -97,19 +90,8 @@ pub enum Error { filepath: std::path::PathBuf, error: events::Error, }, - #[error("function {0} was not found in the contract")] - FunctionNotFoundInContractSpec(String), #[error("parsing contract spec: {0}")] CannotParseContractSpec(FromWasmError), - #[error("function name {0} is too long")] - FunctionNameTooLong(String), - #[error("argument count ({current}) surpasses maximum allowed count ({maximum})")] - MaxNumberOfArgumentsReached { current: usize, maximum: usize }, - #[error("cannot print result {result:?}: {error}")] - CannotPrintResult { - result: ScVal, - error: soroban_spec_tools::Error, - }, #[error(transparent)] Xdr(#[from] xdr::Error), #[error("error parsing int: {0}")] @@ -120,16 +102,12 @@ pub enum Error { UnexpectedContractCodeDataType(LedgerEntryData), #[error("missing operation result")] MissingOperationResult, - #[error(transparent)] - StrVal(#[from] soroban_spec_tools::Error), #[error("error loading signing key: {0}")] SignatureError(#[from] ed25519_dalek::SignatureError), #[error(transparent)] Config(#[from] config::Error), #[error("unexpected ({length}) simulate transaction result length")] UnexpectedSimulateTransactionResultSize { length: usize }, - #[error("Missing argument {0}")] - MissingArgument(String), #[error(transparent)] Clap(#[from] clap::Error), #[error(transparent)] @@ -140,8 +118,6 @@ pub enum Error { StrKey(#[from] stellar_strkey::DecodeError), #[error(transparent)] ContractSpec(#[from] contract::Error), - #[error("")] - MissingFileArg(PathBuf), #[error(transparent)] Io(#[from] std::io::Error), #[error(transparent)] @@ -150,6 +126,8 @@ pub enum Error { Network(#[from] network::Error), #[error(transparent)] GetSpecError(#[from] get_spec::Error), + #[error(transparent)] + ArgParsing(#[from] arg_parsing::Error), } impl From for Error { @@ -159,108 +137,6 @@ impl From for Error { } impl Cmd { - fn build_host_function_parameters( - &self, - contract_id: [u8; 32], - spec_entries: &[ScSpecEntry], - config: &config::Args, - ) -> Result<(String, Spec, InvokeContractArgs, Vec), Error> { - let spec = Spec(Some(spec_entries.to_vec())); - let mut cmd = clap::Command::new(self.contract_id.clone()) - .no_binary_name(true) - .term_width(300) - .max_term_width(300); - - for ScSpecFunctionV0 { name, .. } in spec.find_functions()? { - cmd = cmd.subcommand(build_custom_cmd(&name.to_utf8_string_lossy(), &spec)?); - } - cmd.build(); - let long_help = cmd.render_long_help(); - let mut matches_ = cmd.get_matches_from(&self.slop); - let Some((function, matches_)) = &matches_.remove_subcommand() else { - println!("{long_help}"); - std::process::exit(1); - }; - - let func = spec.find_function(function)?; - // create parsed_args in same order as the inputs to func - let mut signers: Vec = vec![]; - let parsed_args = func - .inputs - .iter() - .map(|i| { - let name = i.name.to_utf8_string()?; - if let Some(mut val) = matches_.get_raw(&name) { - let mut s = val.next().unwrap().to_string_lossy().to_string(); - if matches!(i.type_, ScSpecTypeDef::Address) { - let cmd = crate::commands::keys::address::Cmd { - name: s.clone(), - hd_path: Some(0), - locator: config.locator.clone(), - }; - if let Ok(address) = cmd.public_key() { - s = address.to_string(); - } - if let Ok(key) = cmd.private_key() { - signers.push(key); - } - } - spec.from_string(&s, &i.type_) - .map_err(|error| Error::CannotParseArg { arg: name, error }) - } else if matches!(i.type_, ScSpecTypeDef::Option(_)) { - Ok(ScVal::Void) - } else if let Some(arg_path) = - matches_.get_one::(&fmt_arg_file_name(&name)) - { - if matches!(i.type_, ScSpecTypeDef::Bytes | ScSpecTypeDef::BytesN(_)) { - Ok(ScVal::try_from( - &std::fs::read(arg_path) - .map_err(|_| Error::MissingFileArg(arg_path.clone()))?, - ) - .map_err(|()| Error::CannotParseArg { - arg: name.clone(), - error: soroban_spec_tools::Error::Unknown, - })?) - } else { - let file_contents = std::fs::read_to_string(arg_path) - .map_err(|_| Error::MissingFileArg(arg_path.clone()))?; - tracing::debug!( - "file {arg_path:?}, has contents:\n{file_contents}\nAnd type {:#?}\n{}", - i.type_, - file_contents.len() - ); - spec.from_string(&file_contents, &i.type_) - .map_err(|error| Error::CannotParseArg { arg: name, error }) - } - } else { - Err(Error::MissingArgument(name)) - } - }) - .collect::, Error>>()?; - - let contract_address_arg = ScAddress::Contract(Hash(contract_id)); - let function_symbol_arg = function - .try_into() - .map_err(|()| Error::FunctionNameTooLong(function.clone()))?; - - let final_args = - parsed_args - .clone() - .try_into() - .map_err(|_| Error::MaxNumberOfArgumentsReached { - current: parsed_args.len(), - maximum: ScVec::default().max_len(), - })?; - - let invoke_args = InvokeContractArgs { - contract_address: contract_address_arg, - function_name: function_symbol_arg, - args: final_args, - }; - - Ok((function.clone(), spec, invoke_args, signers)) - } - pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { let res = self.invoke(global_args).await?.to_envelope(); match res { @@ -326,12 +202,12 @@ impl NetworkRunnable for Cmd { let contract_id = self .config .locator - .resolve_contract_id(&self.contract_id, &network.network_passphrase)? - .0; + .resolve_contract_id(&self.contract_id, &network.network_passphrase)?; + let spec_entries = self.spec_entries()?; if let Some(spec_entries) = &spec_entries { // For testing wasm arg parsing - let _ = self.build_host_function_parameters(contract_id, spec_entries, config)?; + let _ = build_host_function_parameters(&contract_id, &self.slop, spec_entries, config)?; } let client = rpc::Client::new(&network.rpc_url)?; let account_details = if self.is_view { @@ -351,7 +227,7 @@ impl NetworkRunnable for Cmd { let AccountId(PublicKey::PublicKeyTypeEd25519(account_id)) = account_details.account_id; let spec_entries = get_remote_contract_spec( - &contract_id, + &contract_id.0, &config.locator, &config.network, global_args, @@ -362,7 +238,7 @@ impl NetworkRunnable for Cmd { // Get the ledger footprint let (function, spec, host_function_params, signers) = - self.build_host_function_parameters(contract_id, &spec_entries, config)?; + build_host_function_parameters(&contract_id, &self.slop, &spec_entries, config)?; let tx = build_invoke_contract_tx( host_function_params.clone(), sequence + 1, @@ -411,7 +287,7 @@ impl NetworkRunnable for Cmd { } }; crate::log::events(&events); - output_to_string(&spec, &return_value, &function) + Ok(output_to_string(&spec, &return_value, &function)?) } } @@ -460,24 +336,6 @@ fn default_account_entry() -> AccountEntry { } } -pub fn output_to_string( - spec: &Spec, - res: &ScVal, - function: &str, -) -> Result, Error> { - let mut res_str = String::new(); - if let Some(output) = spec.find_function(function)?.outputs.first() { - res_str = spec - .xdr_to_json(res, output) - .map_err(|e| Error::CannotPrintResult { - result: res.clone(), - error: e, - })? - .to_string(); - } - Ok(TxnResult::Res(res_str)) -} - fn build_invoke_contract_tx( parameters: InvokeContractArgs, sequence: i64, @@ -502,87 +360,6 @@ fn build_invoke_contract_tx( }) } -fn build_custom_cmd(name: &str, spec: &Spec) -> Result { - let func = spec - .find_function(name) - .map_err(|_| Error::FunctionNotFoundInContractSpec(name.to_string()))?; - - // Parse the function arguments - let inputs_map = &func - .inputs - .iter() - .map(|i| (i.name.to_utf8_string().unwrap(), i.type_.clone())) - .collect::>(); - let name: &'static str = Box::leak(name.to_string().into_boxed_str()); - let mut cmd = clap::Command::new(name) - .no_binary_name(true) - .term_width(300) - .max_term_width(300); - let kebab_name = name.to_kebab_case(); - if kebab_name != name { - cmd = cmd.alias(kebab_name); - } - let doc: &'static str = Box::leak(func.doc.to_utf8_string_lossy().into_boxed_str()); - let long_doc: &'static str = Box::leak(arg_file_help(doc).into_boxed_str()); - - cmd = cmd.about(Some(doc)).long_about(long_doc); - for (name, type_) in inputs_map { - let mut arg = clap::Arg::new(name); - let file_arg_name = fmt_arg_file_name(name); - let mut file_arg = clap::Arg::new(&file_arg_name); - arg = arg - .long(name) - .alias(name.to_kebab_case()) - .num_args(1) - .value_parser(clap::builder::NonEmptyStringValueParser::new()) - .long_help(spec.doc(name, type_)?); - - file_arg = file_arg - .long(&file_arg_name) - .alias(file_arg_name.to_kebab_case()) - .num_args(1) - .hide(true) - .value_parser(value_parser!(PathBuf)) - .conflicts_with(name); - - if let Some(value_name) = spec.arg_value_name(type_, 0) { - let value_name: &'static str = Box::leak(value_name.into_boxed_str()); - arg = arg.value_name(value_name); - } - - // Set up special-case arg rules - arg = match type_ { - ScSpecTypeDef::Bool => arg - .num_args(0..1) - .default_missing_value("true") - .default_value("false") - .num_args(0..=1), - ScSpecTypeDef::Option(_val) => arg.required(false), - ScSpecTypeDef::I256 | ScSpecTypeDef::I128 | ScSpecTypeDef::I64 | ScSpecTypeDef::I32 => { - arg.allow_hyphen_values(true) - } - _ => arg, - }; - - cmd = cmd.arg(arg); - cmd = cmd.arg(file_arg); - } - Ok(cmd) -} - -fn fmt_arg_file_name(name: &str) -> String { - format!("{name}-file-path") -} - -fn arg_file_help(docs: &str) -> String { - format!( - r#"{docs} -Usage Notes: -Each arg has a corresponding ---file-path which is a path to a file containing the corresponding JSON argument. -Note: The only types which aren't JSON are Bytes and BytesN, which are raw bytes"# - ) -} - #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum, Default)] pub enum Send { /// Send transaction if simulation indicates there are ledger writes, diff --git a/cmd/soroban-cli/src/commands/contract/mod.rs b/cmd/soroban-cli/src/commands/contract/mod.rs index 3a3ea0b50..9d574335c 100644 --- a/cmd/soroban-cli/src/commands/contract/mod.rs +++ b/cmd/soroban-cli/src/commands/contract/mod.rs @@ -1,4 +1,5 @@ pub mod alias; +pub mod arg_parsing; pub mod asset; pub mod bindings; pub mod build; diff --git a/cmd/soroban-cli/src/commands/contract/read.rs b/cmd/soroban-cli/src/commands/contract/read.rs index bef3f3737..3cb253bb1 100644 --- a/cmd/soroban-cli/src/commands/contract/read.rs +++ b/cmd/soroban-cli/src/commands/contract/read.rs @@ -186,11 +186,7 @@ impl NetworkRunnable for Cmd { let network = config.get_network()?; tracing::trace!(?network); let client = Client::new(&network.rpc_url)?; - let contract = config.locator.resolve_contract_id( - self.key.contract_id.as_ref().unwrap(), - &network.network_passphrase, - )?; - let keys = self.key.parse_keys(contract)?; + let keys = self.key.parse_keys(&config.locator, &network)?; Ok(client.get_full_ledger_entries(&keys).await?) } } diff --git a/cmd/soroban-cli/src/commands/contract/restore.rs b/cmd/soroban-cli/src/commands/contract/restore.rs index 192af3140..e3e8e65ae 100644 --- a/cmd/soroban-cli/src/commands/contract/restore.rs +++ b/cmd/soroban-cli/src/commands/contract/restore.rs @@ -133,11 +133,7 @@ impl NetworkRunnable for Cmd { let config = config.unwrap_or(&self.config); let network = config.get_network()?; tracing::trace!(?network); - let contract = config.locator.resolve_contract_id( - self.key.contract_id.as_ref().unwrap(), - &network.network_passphrase, - )?; - let entry_keys = self.key.parse_keys(contract)?; + let entry_keys = self.key.parse_keys(&config.locator, &network)?; let client = Client::new(&network.rpc_url)?; let key = config.key_pair()?; diff --git a/cmd/soroban-cli/src/key.rs b/cmd/soroban-cli/src/key.rs index 885295936..b4fd358aa 100644 --- a/cmd/soroban-cli/src/key.rs +++ b/cmd/soroban-cli/src/key.rs @@ -1,12 +1,14 @@ +use crate::{ + commands::contract::Durability, + config::{locator, network::Network}, + wasm, +}; use clap::arg; use soroban_env_host::xdr::{ self, LedgerKey, LedgerKeyContractCode, LedgerKeyContractData, Limits, ReadXdr, ScAddress, ScVal, }; use std::path::PathBuf; -use stellar_strkey::Contract; - -use crate::{commands::contract::Durability, wasm}; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -18,6 +20,8 @@ pub enum Error { CannotParseContractId(String, stellar_strkey::DecodeError), #[error(transparent)] Wasm(#[from] wasm::Error), + #[error(transparent)] + Locator(#[from] locator::Error), } #[derive(Debug, clap::Args, Clone)] @@ -61,7 +65,13 @@ pub struct Args { } impl Args { - pub fn parse_keys(&self, contract: Contract) -> Result, Error> { + pub fn parse_keys( + &self, + locator: &locator::Args, + Network { + network_passphrase, .. + }: &Network, + ) -> Result, Error> { let keys = if let Some(keys) = &self.key { keys.iter() .map(|key| { @@ -87,6 +97,8 @@ impl Args { } else { vec![ScVal::LedgerKeyContractInstance] }; + let contract = + locator.resolve_contract_id(self.contract_id.as_ref().unwrap(), network_passphrase)?; Ok(keys .into_iter()