diff --git a/Cargo.lock b/Cargo.lock index 6a4243532..e60e34613 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4675,7 +4675,7 @@ dependencies = [ [[package]] name = "soroban-cli" -version = "21.4.0" +version = "21.4.1" dependencies = [ "assert_cmd", "assert_fs", @@ -4837,7 +4837,7 @@ dependencies = [ [[package]] name = "soroban-hello" -version = "21.4.0" +version = "21.4.1" [[package]] name = "soroban-ledger-snapshot" @@ -4907,7 +4907,7 @@ dependencies = [ [[package]] name = "soroban-spec-json" -version = "21.4.0" +version = "21.4.1" dependencies = [ "pretty_assertions", "serde", @@ -4937,7 +4937,7 @@ dependencies = [ [[package]] name = "soroban-spec-tools" -version = "21.4.0" +version = "21.4.1" dependencies = [ "base64 0.21.7", "ethnum", @@ -4956,7 +4956,7 @@ dependencies = [ [[package]] name = "soroban-spec-typescript" -version = "21.4.0" +version = "21.4.1" dependencies = [ "base64 0.21.7", "heck 0.4.1", @@ -4977,7 +4977,7 @@ dependencies = [ [[package]] name = "soroban-test" -version = "21.4.0" +version = "21.4.1" dependencies = [ "assert_cmd", "assert_fs", @@ -5049,14 +5049,14 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stellar-cli" -version = "21.4.0" +version = "21.4.1" dependencies = [ "soroban-cli", ] [[package]] name = "stellar-ledger" -version = "21.4.0" +version = "21.4.1" dependencies = [ "async-trait", "bollard", @@ -5362,35 +5362,35 @@ dependencies = [ [[package]] name = "test_custom_account" -version = "21.4.0" +version = "21.4.1" dependencies = [ "soroban-sdk", ] [[package]] name = "test_custom_types" -version = "21.4.0" +version = "21.4.1" dependencies = [ "soroban-sdk", ] [[package]] name = "test_hello_world" -version = "21.4.0" +version = "21.4.1" dependencies = [ "soroban-sdk", ] [[package]] name = "test_swap" -version = "21.4.0" +version = "21.4.1" dependencies = [ "soroban-sdk", ] [[package]] name = "test_token" -version = "21.4.0" +version = "21.4.1" dependencies = [ "soroban-sdk", "soroban-token-sdk", @@ -5398,7 +5398,7 @@ dependencies = [ [[package]] name = "test_udt" -version = "21.4.0" +version = "21.4.1" dependencies = [ "soroban-sdk", ] diff --git a/Cargo.toml b/Cargo.toml index c67464665..d58335a21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ default-members = ["cmd/soroban-cli", "cmd/crates/soroban-spec-tools", "cmd/crat exclude = ["cmd/crates/soroban-test/tests/fixtures/hello"] [workspace.package] -version = "21.4.0" +version = "21.4.1" rust-version = "1.79.0" [workspace.dependencies.soroban-env-host] @@ -27,15 +27,15 @@ version = "=21.5.0" version = "=21.5.0" [workspace.dependencies.soroban-spec-json] -version = "=21.4.0" +version = "=21.4.1" path = "./cmd/crates/soroban-spec-json" [workspace.dependencies.soroban-spec-typescript] -version = "21.4.0" +version = "21.4.1" path = "./cmd/crates/soroban-spec-typescript" [workspace.dependencies.soroban-spec-tools] -version = "21.4.0" +version = "21.4.1" path = "./cmd/crates/soroban-spec-tools" [workspace.dependencies.soroban-sdk] @@ -48,7 +48,7 @@ version = "=21.2.0" version = "=21.2.0" [workspace.dependencies.soroban-cli] -version = "=21.4.0" +version = "=21.4.1" path = "cmd/soroban-cli" [workspace.dependencies.soroban-rpc] diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index c13e002c1..57a4ef692 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -76,6 +76,7 @@ Tools for smart contract developers ###### **Subcommands:** * `asset` — Utilities to deploy a Stellar Asset Contract or get its id +* `alias` — Utilities to manage contract aliases * `bindings` — Generate code client bindings for a contract * `build` — Build a contract from source * `extend` — Extend the time to live ledger of a contract-data ledger entry @@ -151,6 +152,82 @@ Deploy builtin Soroban Asset Contract +## `stellar contract alias` + +Utilities to manage contract aliases + +**Usage:** `stellar contract alias ` + +###### **Subcommands:** + +* `remove` — Remove contract alias +* `add` — Add contract alias +* `show` — Show the contract id associated with a given alias + + + +## `stellar contract alias remove` + +Remove contract alias + +**Usage:** `stellar contract alias remove [OPTIONS] ` + +###### **Arguments:** + +* `` — The contract alias that will be removed + +###### **Options:** + +* `--global` — Use global config +* `--config-dir ` — Location of config directory, default is "." +* `--rpc-url ` — RPC server endpoint +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config + + + +## `stellar contract alias add` + +Add contract alias + +**Usage:** `stellar contract alias add [OPTIONS] --id ` + +###### **Arguments:** + +* `` — The contract alias that will be removed + +###### **Options:** + +* `--global` — Use global config +* `--config-dir ` — Location of config directory, default is "." +* `--rpc-url ` — RPC server endpoint +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--overwrite` — Overwrite the contract alias if it already exists +* `--id ` — The contract id that will be associated with the alias + + + +## `stellar contract alias show` + +Show the contract id associated with a given alias + +**Usage:** `stellar contract alias show [OPTIONS] ` + +###### **Arguments:** + +* `` — The contract alias that will be displayed + +###### **Options:** + +* `--global` — Use global config +* `--config-dir ` — Location of config directory, default is "." +* `--rpc-url ` — RPC server endpoint +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config + + + ## `stellar contract bindings` Generate code client bindings for a contract diff --git a/cmd/crates/soroban-test/Cargo.toml b/cmd/crates/soroban-test/Cargo.toml index ec6c78807..643db6ec3 100644 --- a/cmd/crates/soroban-test/Cargo.toml +++ b/cmd/crates/soroban-test/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/stellar/soroban-test" authors = ["Stellar Development Foundation "] license = "Apache-2.0" readme = "README.md" -version = "21.4.0" +version = "21.4.1" edition = "2021" rust-version.workspace = true autobins = false diff --git a/cmd/crates/soroban-test/tests/fixtures/hello/Cargo.toml b/cmd/crates/soroban-test/tests/fixtures/hello/Cargo.toml index 8e6c049d7..64d861184 100644 --- a/cmd/crates/soroban-test/tests/fixtures/hello/Cargo.toml +++ b/cmd/crates/soroban-test/tests/fixtures/hello/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "soroban-hello" -version = "21.4.0" +version = "21.4.1" edition = "2021" publish = false diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_account/Cargo.toml b/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_account/Cargo.toml index 4c7c1de98..db31464f2 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_account/Cargo.toml +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_account/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "test_custom_account" -version = "21.4.0" +version = "21.4.1" authors = ["Stellar Development Foundation "] license = "Apache-2.0" edition = "2021" diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_type/Cargo.toml b/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_type/Cargo.toml index 7096eb1fd..61b1cd4c6 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_type/Cargo.toml +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_type/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "test_custom_types" -version = "21.4.0" +version = "21.4.1" authors = ["Stellar Development Foundation "] license = "Apache-2.0" edition = "2021" diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world/Cargo.toml b/cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world/Cargo.toml index 65c3e6e9b..c2bb8ce80 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world/Cargo.toml +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "test_hello_world" -version = "21.4.0" +version = "21.4.1" authors = ["Stellar Development Foundation "] license = "Apache-2.0" edition = "2021" 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/Cargo.toml b/cmd/soroban-cli/Cargo.toml index 12502aa00..3ffb7466e 100644 --- a/cmd/soroban-cli/Cargo.toml +++ b/cmd/soroban-cli/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/stellar/stellar-cli" authors = ["Stellar Development Foundation "] license = "Apache-2.0" readme = "README.md" -version = "21.4.0" +version = "21.4.1" edition = "2021" rust-version.workspace = true autobins = false diff --git a/cmd/soroban-cli/src/commands/contract/alias.rs b/cmd/soroban-cli/src/commands/contract/alias.rs new file mode 100644 index 000000000..bea808cb4 --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/alias.rs @@ -0,0 +1,40 @@ +use crate::commands::global; + +pub mod add; +pub mod remove; +pub mod show; + +#[derive(Debug, clap::Subcommand)] +pub enum Cmd { + /// Remove contract alias + Remove(remove::Cmd), + + /// Add contract alias + Add(add::Cmd), + + /// Show the contract id associated with a given alias + Show(show::Cmd), +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Remove(#[from] remove::Error), + + #[error(transparent)] + Add(#[from] add::Error), + + #[error(transparent)] + Show(#[from] show::Error), +} + +impl Cmd { + pub fn run(&self, global_args: &global::Args) -> Result<(), Error> { + match &self { + Cmd::Remove(remove) => remove.run(global_args)?, + Cmd::Add(add) => add.run(global_args)?, + Cmd::Show(show) => show.run(global_args)?, + } + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/contract/alias/add.rs b/cmd/soroban-cli/src/commands/contract/alias/add.rs new file mode 100644 index 000000000..d5c304452 --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/alias/add.rs @@ -0,0 +1,84 @@ +use std::fmt::Debug; + +use clap::{command, Parser}; + +use crate::commands::{config::network, global}; +use crate::config::locator; +use crate::print::Print; + +#[derive(Parser, Debug, Clone)] +#[group(skip)] +pub struct Cmd { + #[command(flatten)] + pub config_locator: locator::Args, + + #[command(flatten)] + network: network::Args, + + /// The contract alias that will be removed. + pub alias: String, + + /// Overwrite the contract alias if it already exists. + #[arg(long)] + pub overwrite: bool, + + /// The contract id that will be associated with the alias. + #[arg(long = "id")] + pub contract_id: stellar_strkey::Contract, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Locator(#[from] locator::Error), + + #[error(transparent)] + Network(#[from] network::Error), + + #[error( + "alias '{alias}' is already referencing contract '{contract}' on network '{network_passphrase}'" + )] + AlreadyExist { + alias: String, + network_passphrase: String, + contract: String, + }, +} + +impl Cmd { + pub fn run(&self, global_args: &global::Args) -> Result<(), Error> { + let print = Print::new(global_args.quiet); + let alias = &self.alias; + let network = self.network.get(&self.config_locator)?; + let network_passphrase = &network.network_passphrase; + + let contract = self + .config_locator + .get_contract_id(&self.alias, network_passphrase)?; + + if let Some(contract) = contract { + if contract != self.contract_id.to_string() && !self.overwrite { + return Err(Error::AlreadyExist { + alias: alias.to_string(), + network_passphrase: network_passphrase.to_string(), + contract, + }); + } + }; + + print.infoln(format!( + "Contract alias '{alias}' will reference {contract} on network '{network_passphrase}'", + contract = self.contract_id + )); + + self.config_locator.save_contract_id( + &network.network_passphrase, + &self.contract_id.to_string(), + alias, + )?; + + print.checkln(format!("Contract alias '{alias}' has been added")); + + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/contract/alias/remove.rs b/cmd/soroban-cli/src/commands/contract/alias/remove.rs new file mode 100644 index 000000000..7a4190595 --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/alias/remove.rs @@ -0,0 +1,65 @@ +use std::fmt::Debug; + +use clap::{command, Parser}; + +use crate::commands::{config::network, global}; +use crate::config::locator; +use crate::print::Print; + +#[derive(Parser, Debug, Clone)] +#[group(skip)] +pub struct Cmd { + #[command(flatten)] + pub config_locator: locator::Args, + + #[command(flatten)] + network: network::Args, + + /// The contract alias that will be removed. + pub alias: String, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Locator(#[from] locator::Error), + + #[error(transparent)] + Network(#[from] network::Error), + + #[error("no contract found with alias '{alias}' for network '{network_passphrase}'")] + NoContract { + alias: String, + network_passphrase: String, + }, +} + +impl Cmd { + pub fn run(&self, global_args: &global::Args) -> Result<(), Error> { + let print = Print::new(global_args.quiet); + let alias = &self.alias; + let network = self.network.get(&self.config_locator)?; + let network_passphrase = &network.network_passphrase; + + let Some(contract) = self + .config_locator + .get_contract_id(&self.alias, network_passphrase)? + else { + return Err(Error::NoContract { + alias: alias.into(), + network_passphrase: network_passphrase.into(), + }); + }; + + print.infoln(format!( + "Contract alias '{alias}' references {contract} on network '{network_passphrase}'" + )); + + self.config_locator + .remove_contract_id(&network.network_passphrase, alias)?; + + print.checkln(format!("Contract alias '{alias}' has been removed")); + + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/contract/alias/show.rs b/cmd/soroban-cli/src/commands/contract/alias/show.rs new file mode 100644 index 000000000..fc233b613 --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/alias/show.rs @@ -0,0 +1,62 @@ +use std::fmt::Debug; + +use clap::{command, Parser}; + +use crate::commands::{config::network, global}; +use crate::config::locator; +use crate::print::Print; + +#[derive(Parser, Debug, Clone)] +#[group(skip)] +pub struct Cmd { + #[command(flatten)] + pub config_locator: locator::Args, + + #[command(flatten)] + network: network::Args, + + /// The contract alias that will be displayed. + pub alias: String, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Locator(#[from] locator::Error), + + #[error(transparent)] + Network(#[from] network::Error), + + #[error("no contract found with alias '{alias}' for network '{network_passphrase}'")] + NoContract { + alias: String, + network_passphrase: String, + }, +} + +impl Cmd { + pub fn run(&self, global_args: &global::Args) -> Result<(), Error> { + let print = Print::new(global_args.quiet); + let alias = &self.alias; + let network = self.network.get(&self.config_locator)?; + let network_passphrase = &network.network_passphrase; + + if let Some(contract) = self + .config_locator + .get_contract_id(&self.alias, network_passphrase)? + { + print.infoln(format!( + "Contract alias '{alias}' references {contract} on network '{network_passphrase}'" + )); + + println!("{contract}"); + + Ok(()) + } else { + Err(Error::NoContract { + alias: alias.into(), + network_passphrase: network_passphrase.into(), + }) + } + } +} 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/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index b047f7dc9..74491249f 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -197,7 +197,7 @@ impl NetworkRunnable for Cmd { } })?); - print.info(format!("Using wasm hash {wasm_hash}").as_str()); + print.infoln(format!("Using wasm hash {wasm_hash}").as_str()); let network = config.get_network()?; let salt: [u8; 32] = match &self.salt { @@ -230,21 +230,21 @@ impl NetworkRunnable for Cmd { )?; if self.fee.build_only { - print.check("Transaction built!"); + print.checkln("Transaction built!"); return Ok(TxnResult::Txn(txn)); } - print.info("Simulating deploy transaction…"); + print.infoln("Simulating deploy transaction…"); let txn = client.simulate_and_assemble_transaction(&txn).await?; let txn = self.fee.apply_to_assembled_txn(txn).transaction().clone(); if self.fee.sim_only { - print.check("Done!"); + print.checkln("Done!"); return Ok(TxnResult::Txn(txn)); } - print.globe("Submitting deploy transaction…"); + print.globeln("Submitting deploy transaction…"); print.log_transaction(&txn, &network, true)?; let get_txn_resp = client @@ -259,10 +259,10 @@ impl NetworkRunnable for Cmd { let contract_id = stellar_strkey::Contract(contract_id.0).to_string(); if let Some(url) = utils::explorer_url_for_contract(&network, &contract_id) { - print.link(url); + print.linkln(url); } - print.check("Deployed!"); + print.checkln("Deployed!"); Ok(TxnResult::Res(contract_id)) } 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/install.rs b/cmd/soroban-cli/src/commands/contract/install.rs index 8474c9621..98c866271 100644 --- a/cmd/soroban-cli/src/commands/contract/install.rs +++ b/cmd/soroban-cli/src/commands/contract/install.rs @@ -163,7 +163,7 @@ impl NetworkRunnable for Cmd { // Skip reupload if this isn't V0 because V1 extension already // exists. if code.ext.ne(&ContractCodeEntryExt::V0) { - print.info("Skipping install because wasm already installed"); + print.infoln("Skipping install because wasm already installed"); return Ok(TxnResult::Res(hash)); } } @@ -175,7 +175,7 @@ impl NetworkRunnable for Cmd { } } - print.info("Simulating install transaction…"); + print.infoln("Simulating install transaction…"); let txn = client .simulate_and_assemble_transaction(&tx_without_preflight) @@ -186,7 +186,7 @@ impl NetworkRunnable for Cmd { return Ok(TxnResult::Txn(txn)); } - print.globe("Submitting install transaction…"); + print.globeln("Submitting install transaction…"); let txn_resp = client .send_transaction_polling(&self.config.sign_with_local_key(txn).await?) 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 afd49c6ab..9d574335c 100644 --- a/cmd/soroban-cli/src/commands/contract/mod.rs +++ b/cmd/soroban-cli/src/commands/contract/mod.rs @@ -1,3 +1,5 @@ +pub mod alias; +pub mod arg_parsing; pub mod asset; pub mod bindings; pub mod build; @@ -21,6 +23,11 @@ pub enum Cmd { /// Utilities to deploy a Stellar Asset Contract or get its id #[command(subcommand)] Asset(asset::Cmd), + + /// Utilities to manage contract aliases + #[command(subcommand)] + Alias(alias::Cmd), + /// Generate code client bindings for a contract #[command(subcommand)] Bindings(bindings::Cmd), @@ -82,6 +89,9 @@ pub enum Error { #[error(transparent)] Asset(#[from] asset::Error), + #[error(transparent)] + Alias(#[from] alias::Error), + #[error(transparent)] Bindings(#[from] bindings::Error), @@ -132,6 +142,7 @@ impl Cmd { Cmd::Bindings(bindings) => bindings.run().await?, Cmd::Build(build) => build.run()?, Cmd::Extend(extend) => extend.run().await?, + Cmd::Alias(alias) => alias.run(global_args)?, Cmd::Deploy(deploy) => deploy.run(global_args).await?, Cmd::Id(id) => id.run()?, Cmd::Info(info) => info.run().await?, 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/config/locator.rs b/cmd/soroban-cli/src/config/locator.rs index 9aefdd9e0..97386cb7d 100644 --- a/cmd/soroban-cli/src/config/locator.rs +++ b/cmd/soroban-cli/src/config/locator.rs @@ -68,6 +68,8 @@ pub enum Error { Json(#[from] serde_json::Error), #[error("cannot access config dir for alias file")] CannotAccessConfigDir, + #[error("cannot access alias config file (no permission or doesn't exist)")] + CannotAccessAliasConfigFile, #[error("cannot parse contract ID {0}: {1}")] CannotParseContractId(String, DecodeError), } @@ -277,6 +279,29 @@ impl Args { Ok(to_file.write_all(content.as_bytes())?) } + pub fn remove_contract_id(&self, network_passphrase: &str, alias: &str) -> Result<(), Error> { + let path = self.alias_path(alias)?; + + if !path.is_file() { + return Err(Error::CannotAccessAliasConfigFile); + } + + let content = fs::read_to_string(&path).unwrap_or_default(); + let mut data: alias::Data = serde_json::from_str(&content).unwrap_or_default(); + + let mut to_file = OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(path)?; + + data.ids.remove::(network_passphrase); + + let content = serde_json::to_string(&data)?; + + Ok(to_file.write_all(content.as_bytes())?) + } + pub fn get_contract_id( &self, alias: &str, 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() diff --git a/cmd/stellar-cli/Cargo.toml b/cmd/stellar-cli/Cargo.toml index f430a23ee..40fa29dcf 100644 --- a/cmd/stellar-cli/Cargo.toml +++ b/cmd/stellar-cli/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/stellar/stellar-cli" authors = ["Stellar Development Foundation "] license = "Apache-2.0" readme = "README.md" -version = "21.4.0" +version = "21.4.1" edition = "2021" rust-version.workspace = true autobins = false