diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index edc3987014..1e2f7d6759 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -53,6 +53,7 @@ Anything after the `--` double dash (the "slop") is parsed as arguments to the c - `snapshot` — Download a snapshot of a ledger from an archive - `tx` — Sign, Simulate, and Send transactions - `xdr` — Decode and encode XDR +- `strkey` — Decode and encode strkey - `completion` — Print shell completion code for the specified shell - `cache` — Cache for transactions and contract specs - `version` — Print version information @@ -1292,12 +1293,16 @@ Output an identity's secret key Set the default identity that will be used on all commands. This allows you to skip `--source-account` or setting a environment variable, while reusing this value in all commands that require it -**Usage:** `stellar keys use [OPTIONS] ` +**Usage:** `stellar keys use [OPTIONS] [NAME]` ###### **Arguments:** - `` — Set the default network name +###### **Options:** + +- `--clear` — Clear the default source account + ###### **Options (Global):** - `--global` — ⚠️ Deprecated: global config is always on @@ -4141,6 +4146,65 @@ Print version information **Usage:** `stellar xdr version` +## `stellar strkey` + +Decode and encode strkey + +**Usage:** `stellar strkey ` + +###### **Subcommands:** + +- `decode` — Decode strkey +- `encode` — Encode strkey +- `zero` — Generate the zero strkey +- `version` — Print version information + +## `stellar strkey decode` + +Decode strkey + +**Usage:** `stellar strkey decode ` + +###### **Arguments:** + +- `` — Strkey to decode + +## `stellar strkey encode` + +Encode strkey + +**Usage:** `stellar strkey encode ` + +###### **Arguments:** + +- `` — JSON for Strkey to encode + +## `stellar strkey zero` + +Generate the zero strkey + +**Usage:** `stellar strkey zero [OPTIONS] ` + +###### **Arguments:** + +- `` — Strkey type to generate the zero value for + + Possible values: `public_key_ed25519`, `pre_auth_tx`, `hash_x`, `muxed_account_ed25519`, `signed_payload_ed25519`, `contract`, `liquidity_pool`, `claimable_balance_v0` + +###### **Options:** + +- `--output ` — Output format + + Default value: `strkey` + + Possible values: `strkey`, `json` + +## `stellar strkey version` + +Print version information + +**Usage:** `stellar strkey version` + ## `stellar completion` Print shell completion code for the specified shell diff --git a/Makefile b/Makefile index 3d033115d5..a84cf70f22 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ endif REPOSITORY_BRANCH := "$(shell git rev-parse --abbrev-ref HEAD)" BUILD_TIMESTAMP ?= $(shell date '+%Y-%m-%dT%H:%M:%S') -SOROBAN_PORT?=8000 +STELLAR_PORT?=8000 # The following works around incompatibility between the rust and the go linkers - # the rust would generate an object file with min-version of 13.0 where-as the go @@ -52,7 +52,7 @@ test: build-test cargo test --workspace --exclude soroban-test --features additional-libs cargo test -p soroban-test -- --skip integration:: -# expects a quickstart container running with the rpc exposed at localhost:SOROBAN_PORT +# expects a quickstart container running with the rpc exposed at localhost:STELLAR_PORT rpc-test: cargo test --features it --test it -- integration --test-threads=4 diff --git a/cmd/crates/soroban-test/src/lib.rs b/cmd/crates/soroban-test/src/lib.rs index b1381be806..16716e2203 100644 --- a/cmd/crates/soroban-test/src/lib.rs +++ b/cmd/crates/soroban-test/src/lib.rs @@ -137,7 +137,7 @@ impl TestEnv { if let Ok(rpc_url) = std::env::var("STELLAR_RPC_URL") { return Self::with_rpc_url(&rpc_url); } - let host_port = std::env::var("SOROBAN_PORT") + let host_port = std::env::var("STELLAR_PORT") .as_deref() .ok() .and_then(|n| n.parse().ok()) @@ -159,9 +159,9 @@ impl TestEnv { let mut cmd: Command = self.bin(); cmd.arg(subcommand) - .env("SOROBAN_ACCOUNT", TEST_ACCOUNT) - .env("SOROBAN_RPC_URL", &self.network.rpc_url) - .env("SOROBAN_NETWORK_PASSPHRASE", LOCAL_NETWORK_PASSPHRASE) + .env("STELLAR_ACCOUNT", TEST_ACCOUNT) + .env("STELLAR_RPC_URL", &self.network.rpc_url) + .env("STELLAR_NETWORK_PASSPHRASE", LOCAL_NETWORK_PASSPHRASE) .env("XDG_CONFIG_HOME", self.dir().join("config").as_os_str()) .env("XDG_DATA_HOME", self.data_dir().as_os_str()) .current_dir(self.dir()); diff --git a/cmd/crates/soroban-test/tests/it/config.rs b/cmd/crates/soroban-test/tests/it/config.rs index acea840aa6..97d30daa1b 100644 --- a/cmd/crates/soroban-test/tests/it/config.rs +++ b/cmd/crates/soroban-test/tests/it/config.rs @@ -200,7 +200,7 @@ fn use_env() { sandbox .new_assert_cmd("keys") .env( - "SOROBAN_SECRET_KEY", + "STELLAR_SECRET_KEY", "SDIY6AQQ75WMD4W46EYB7O6UYMHOCGQHLAQGQTKHDX4J2DYQCHVCQYFD", ) .arg("add") @@ -225,7 +225,7 @@ fn set_default_identity() { sandbox .new_assert_cmd("keys") .env( - "SOROBAN_SECRET_KEY", + "STELLAR_SECRET_KEY", "SC4ZPYELVR7S7EE7KZDZN3ETFTNQHHLTUL34NUAAWZG5OK2RGJ4V2U3Z", ) .arg("add") @@ -245,7 +245,7 @@ fn set_default_identity() { sandbox .new_assert_cmd("env") - .env_remove("SOROBAN_ACCOUNT") + .env_remove("STELLAR_ACCOUNT") .assert() .stdout(predicate::str::contains("STELLAR_ACCOUNT=alice")) .success(); diff --git a/cmd/crates/soroban-test/tests/it/integration/keys.rs b/cmd/crates/soroban-test/tests/it/integration/keys.rs index e9a9742b8a..29b928a73c 100644 --- a/cmd/crates/soroban-test/tests/it/integration/keys.rs +++ b/cmd/crates/soroban-test/tests/it/integration/keys.rs @@ -1,4 +1,4 @@ -use predicates::prelude::predicate; +use predicates::prelude::{predicate, PredicateBooleanExt}; use soroban_test::AssertExt; use soroban_test::TestEnv; @@ -143,3 +143,103 @@ async fn overwrite_identity_with_add() { pubkey_for_identity(sandbox, "test3").trim() ); } + +#[tokio::test] +#[allow(clippy::too_many_lines)] +async fn set_default_identity() { + let sandbox = &TestEnv::new(); + sandbox + .new_assert_cmd("keys") + .arg("generate") + .arg("test4") + .assert() + .success(); + + sandbox + .new_assert_cmd("keys") + .arg("use") + .arg("test4") + .assert() + .stderr(predicate::str::contains( + "The default source account is set to `test4`", + )) + .success(); +} + +#[tokio::test] +#[allow(clippy::too_many_lines)] +async fn clear_default_identity() { + let sandbox = &TestEnv::new(); + sandbox + .new_assert_cmd("keys") + .arg("generate") + .arg("test5") + .assert() + .success(); + + sandbox + .new_assert_cmd("keys") + .arg("use") + .arg("test5") + .assert() + .stderr(predicate::str::contains( + "The default source account is set to `test5`", + )) + .success(); + + sandbox + .new_assert_cmd("env") + .env_remove("STELLAR_ACCOUNT") + .assert() + .stdout(predicate::str::contains("STELLAR_ACCOUNT=test5")) + .success(); + + sandbox + .new_assert_cmd("keys") + .arg("use") + .arg("--clear") + .assert() + .stderr(predicate::str::contains( + "The default source account has been cleared", + )) + .success(); + + sandbox + .new_assert_cmd("env") + .env_remove("STELLAR_ACCOUNT") + .assert() + .stdout(predicate::str::contains("STELLAR_ACCOUNT=").not()) + .success(); +} + +#[tokio::test] +#[allow(clippy::too_many_lines)] +async fn validate_use_without_name() { + let sandbox = &TestEnv::new(); + + sandbox + .new_assert_cmd("keys") + .arg("use") + .assert() + .stderr(predicate::str::contains( + "error: Identify name is required unless --clear is specified", + )) + .failure(); +} + +#[tokio::test] +#[allow(clippy::too_many_lines)] +async fn validate_use_with_both_name_and_clear() { + let sandbox = &TestEnv::new(); + + sandbox + .new_assert_cmd("keys") + .arg("use") + .arg("test5") + .arg("--clear") + .assert() + .stderr(predicate::str::contains( + "error: Identify name cannot be used with --clear", + )) + .failure(); +} diff --git a/cmd/soroban-cli/src/commands/keys/default.rs b/cmd/soroban-cli/src/commands/keys/default.rs index bff94cff16..83a540be33 100644 --- a/cmd/soroban-cli/src/commands/keys/default.rs +++ b/cmd/soroban-cli/src/commands/keys/default.rs @@ -4,13 +4,23 @@ use crate::{commands::global, config::locator, print::Print}; pub enum Error { #[error(transparent)] Config(#[from] locator::Error), + + #[error("Identify name is required unless --clear is specified")] + NameRequired, + + #[error("Identify name cannot be used with --clear")] + NameWithClear, } #[derive(Debug, clap::Parser, Clone)] #[group(skip)] pub struct Cmd { /// Set the default network name. - pub name: String, + pub name: Option, + + /// Clear the default source account. + #[arg(long)] + pub clear: bool, #[command(flatten)] pub config_locator: locator::Args, @@ -19,14 +29,26 @@ pub struct Cmd { impl Cmd { pub fn run(&self, global_args: &global::Args) -> Result<(), Error> { let printer = Print::new(global_args.quiet); - let _ = self.config_locator.read_identity(&self.name)?; - self.config_locator.write_default_identity(&self.name)?; + if self.clear && self.name.is_some() { + return Err(Error::NameWithClear); + } + + if !self.clear && self.name.is_none() { + return Err(Error::NameRequired); + } + + if self.clear { + self.config_locator.clear_default_identity()?; + printer.infoln("The default source account has been cleared".to_string()); + } else { + let name = self.name.as_ref().unwrap(); + let _ = self.config_locator.read_identity(name)?; + + self.config_locator.write_default_identity(name)?; - printer.infoln(format!( - "The default source account is set to `{}`", - self.name, - )); + printer.infoln(format!("The default source account is set to `{name}`")); + } Ok(()) } diff --git a/cmd/soroban-cli/src/config/locator.rs b/cmd/soroban-cli/src/config/locator.rs index c09f69d750..0a368c633e 100644 --- a/cmd/soroban-cli/src/config/locator.rs +++ b/cmd/soroban-cli/src/config/locator.rs @@ -215,6 +215,10 @@ impl Args { Config::new()?.set_identity(name).save() } + pub fn clear_default_identity(&self) -> Result<(), Error> { + Config::new()?.clear_identity().save() + } + pub fn list_identities(&self) -> Result, Error> { Ok(KeyType::Identity .list_paths(&self.local_and_global()?)? diff --git a/cmd/soroban-cli/src/config/mod.rs b/cmd/soroban-cli/src/config/mod.rs index 9df6f3d813..603ae743ed 100644 --- a/cmd/soroban-cli/src/config/mod.rs +++ b/cmd/soroban-cli/src/config/mod.rs @@ -207,6 +207,12 @@ impl Config { self } + #[must_use] + pub fn clear_identity(mut self) -> Self { + self.defaults.identity = None; + self + } + pub fn save(&self) -> Result<(), locator::Error> { let toml_string = toml::to_string(&self)?; let path = cli_config_file()?;