Skip to content

Commit 8efe856

Browse files
Merge branch 'main' into ledger-entry-get
2 parents 9784093 + 56d19c5 commit 8efe856

18 files changed

Lines changed: 383 additions & 240 deletions

File tree

.github/workflows/binaries.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ jobs:
5656
5757
- name: Build
5858
run: |
59-
cargo build --package ${{ matrix.crate.name }} --features opt --release --target ${{ matrix.sys.target }}
59+
cargo build --package ${{ matrix.crate.name }} --features opt,additional-libs --release --target ${{ matrix.sys.target }}
6060
- name: Build provenance for binary attestation (release only)
6161
if: github.event_name == 'release'
6262
uses: actions/attest-build-provenance@v2

.github/workflows/ledger-emulator.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@ jobs:
2626
sudo apt update && sudo apt install -y libudev-dev libdbus-1-dev
2727
- run: |
2828
cargo test --manifest-path cmd/crates/stellar-ledger/Cargo.toml --features "emulator-tests" -- --nocapture
29-
- run: cargo build --features emulator-tests
29+
- run: cargo build --features emulator-tests,additional-libs
3030
- run: |
3131
cargo test --features emulator-tests --package soroban-test --test it -- emulator

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ generate-full-help-doc:
4949

5050
test: build-test
5151
cargo test --workspace --exclude soroban-test
52+
cargo test --workspace --exclude soroban-test --features additional-libs
5253
cargo test -p soroban-test -- --skip integration::
5354

5455
e2e-test:

cmd/crates/soroban-test/tests/it/plugin.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ fn has_no_path_failure() {
6464
.unwrap_or_else(|_| assert_cmd::Command::new("stellar"))
6565
.arg("hello")
6666
.assert()
67-
.stderr(predicates::str::contains("error: no such command: `hello`"));
67+
.stderr(predicates::str::contains("unrecognized subcommand 'hello'"));
6868
}
6969

7070
fn target_bin() -> PathBuf {

cmd/soroban-cli/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ version_lt_23 = []
4040
version_gte_23 = []
4141
opt = ["dep:wasm-opt"]
4242
emulator-tests = ["stellar-ledger/emulator-tests"]
43+
additional-libs = ["dep:keyring", "dep:stellar-ledger"]
4344

4445
[dependencies]
4546
stellar-xdr = { workspace = true, features = ["cli"] }
@@ -52,7 +53,7 @@ soroban-ledger-snapshot = { workspace = true }
5253
stellar-strkey = { workspace = true }
5354
soroban-sdk = { workspace = true }
5455
soroban-rpc = { workspace = true }
55-
stellar-ledger = { workspace = true }
56+
stellar-ledger = { workspace = true, optional = true }
5657

5758
clap = { workspace = true, features = [
5859
"derive",
@@ -131,7 +132,7 @@ open = "5.3.0"
131132
url = "2.5.2"
132133
wasm-gen = "0.1.4"
133134
zeroize = "1.8.1"
134-
keyring = { version = "3", features = ["apple-native", "windows-native", "sync-secret-service"] }
135+
keyring = { version = "3", features = ["apple-native", "windows-native", "sync-secret-service"], optional = true }
135136
whoami = "1.5.2"
136137
serde_with = "3.11.0"
137138

cmd/soroban-cli/src/commands/keys/add.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ impl Cmd {
8181

8282
let seed_phrase: SeedPhrase = secret_key.parse()?;
8383

84-
Ok(secure_store::save_secret(print, &self.name, seed_phrase)?)
84+
let secret = secure_store::save_secret(print, &self.name, &seed_phrase)?;
85+
Ok(secret.parse()?)
8586
} else {
8687
let prompt = "Type a secret key or 12/24 word seed phrase:";
8788
let secret_key = read_password(print, prompt)?;

cmd/soroban-cli/src/commands/keys/generate.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,8 @@ impl Cmd {
127127
fn secret(&self, print: &Print) -> Result<Secret, Error> {
128128
let seed_phrase = self.seed_phrase()?;
129129
if self.secure_store {
130-
Ok(secure_store::save_secret(print, &self.name, seed_phrase)?)
130+
let secret = secure_store::save_secret(print, &self.name, &seed_phrase)?;
131+
Ok(secret.parse()?)
131132
} else if self.as_secret {
132133
let secret: Secret = seed_phrase.into();
133134
Ok(secret.private_key(self.hd_path)?.into())
@@ -144,7 +145,6 @@ impl Cmd {
144145
#[cfg(test)]
145146
mod tests {
146147
use crate::config::{address::KeyName, key::Key, secret::Secret};
147-
use keyring::{mock, set_default_credential_builder};
148148

149149
fn set_up_test() -> (super::locator::Args, super::Cmd) {
150150
let temp_dir = tempfile::tempdir().unwrap();
@@ -200,8 +200,10 @@ mod tests {
200200
assert!(matches!(identity, Key::Secret(Secret::SecretKey { .. })));
201201
}
202202

203+
#[cfg(feature = "additional-libs")]
203204
#[tokio::test]
204205
async fn test_storing_secret_in_secure_store() {
206+
use keyring::{mock, set_default_credential_builder};
205207
set_default_credential_builder(mock::default_credential_builder());
206208
let (test_locator, mut cmd) = set_up_test();
207209
cmd.secure_store = true;
@@ -212,4 +214,26 @@ mod tests {
212214
let identity = test_locator.read_identity("test_name").unwrap();
213215
assert!(matches!(identity, Key::Secret(Secret::SecureStore { .. })));
214216
}
217+
218+
#[cfg(not(feature = "additional-libs"))]
219+
#[tokio::test]
220+
async fn test_storing_in_secure_store_returns_error_when_additional_libs_not_enabled() {
221+
let (test_locator, mut cmd) = set_up_test();
222+
cmd.secure_store = true;
223+
let global_args = global_args();
224+
225+
let result = cmd.run(&global_args).await;
226+
assert!(result.is_err());
227+
assert_eq!(
228+
result.unwrap_err().to_string(),
229+
format!("Secure Store keys are not allowed: additional-libs feature must be enabled")
230+
);
231+
232+
let identity_result = test_locator.read_identity("test_name");
233+
assert!(identity_result.is_err());
234+
assert_eq!(
235+
identity_result.unwrap_err().to_string(),
236+
format!("Failed to find config identity for test_name")
237+
);
238+
}
215239
}

cmd/soroban-cli/src/commands/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,16 @@ impl Root {
8484
Self::try_parse().map_err(|e| {
8585
if std::env::args().any(|s| s == "--list") {
8686
let plugins = plugin::list().unwrap_or_default();
87+
8788
if plugins.is_empty() {
88-
println!("No Plugins installed. E.g. soroban-hello");
89+
println!("No Plugins installed. E.g. stellar-hello");
8990
} else {
9091
println!("Installed Plugins:\n {}", plugins.join("\n "));
9192
}
93+
9294
std::process::exit(0);
9395
}
96+
9497
match e.kind() {
9598
ErrorKind::InvalidSubcommand => match plugin::run() {
9699
Ok(()) => Error::Clap(e),
@@ -108,6 +111,7 @@ impl Root {
108111
{
109112
Self::from_arg_matches_mut(&mut Self::command().get_matches_from(itr))
110113
}
114+
111115
pub async fn run(&mut self) -> Result<(), Error> {
112116
match &mut self.cmd {
113117
Cmd::Completion(completion) => completion.run(),
Lines changed: 41 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,33 @@
11
use std::{path::PathBuf, process::Command};
2-
3-
use clap::CommandFactory;
42
use which::which;
53

6-
use crate::{utils, Root};
4+
use crate::utils;
75

86
#[derive(thiserror::Error, Debug)]
97
pub enum Error {
10-
#[error("Plugin not provided. Should be `stellar plugin` for a binary `stellar-plugin`")]
11-
MissingSubcommand,
128
#[error(transparent)]
139
IO(#[from] std::io::Error),
14-
#[error(
15-
r"error: no such command: `{0}`
16-
17-
{1}View all installed plugins with `stellar --list`"
18-
)]
19-
ExecutableNotFound(String, String),
10+
2011
#[error(transparent)]
2112
Which(#[from] which::Error),
13+
2214
#[error(transparent)]
2315
Regex(#[from] regex::Error),
2416
}
2517

26-
const SUBCOMMAND_TOLERANCE: f64 = 0.75;
27-
const PLUGIN_TOLERANCE: f64 = 0.75;
28-
const MIN_LENGTH: usize = 4;
29-
30-
/// Tries to run a plugin, if the plugin's name is similar enough to any of the current subcommands return Ok.
31-
/// Otherwise only errors can be returned because this process will exit with the plugin.
3218
pub fn run() -> Result<(), Error> {
33-
let (name, args) = {
34-
let mut args = std::env::args().skip(1);
35-
let name = args.next().ok_or(Error::MissingSubcommand)?;
36-
(name, args)
37-
};
38-
39-
if Root::command().get_subcommands().any(|c| {
40-
let sc_name = c.get_name();
41-
sc_name.starts_with(&name)
42-
|| (name.len() >= MIN_LENGTH && strsim::jaro(sc_name, &name) >= SUBCOMMAND_TOLERANCE)
43-
}) {
44-
return Ok(());
19+
if let Some((plugin_bin, args)) = find_plugin() {
20+
std::process::exit(
21+
Command::new(plugin_bin)
22+
.args(args)
23+
.spawn()?
24+
.wait()?
25+
.code()
26+
.unwrap(),
27+
);
4528
}
4629

47-
let bin = find_bin(&name).map_err(|_| {
48-
let suggestion = if let Ok(bins) = list() {
49-
let suggested_name = bins
50-
.iter()
51-
.map(|b| (b, strsim::jaro_winkler(&name, b)))
52-
.filter(|(_, i)| *i > PLUGIN_TOLERANCE)
53-
.min_by(|a, b| a.1.total_cmp(&b.1))
54-
.map(|(a, _)| a.to_string())
55-
.unwrap_or_default();
56-
57-
if suggested_name.is_empty() {
58-
suggested_name
59-
} else {
60-
format!(
61-
r"Did you mean `{suggested_name}`?
62-
"
63-
)
64-
}
65-
} else {
66-
String::new()
67-
};
68-
69-
Error::ExecutableNotFound(name, suggestion)
70-
})?;
71-
72-
std::process::exit(
73-
Command::new(bin)
74-
.args(args)
75-
.spawn()?
76-
.wait()?
77-
.code()
78-
.unwrap(),
79-
);
30+
Ok(())
8031
}
8132

8233
const MAX_HEX_LENGTH: usize = 10;
@@ -107,3 +58,31 @@ pub fn list() -> Result<Vec<String>, Error> {
10758
.map(|s| s.replace("soroban-", "").replace("stellar-", ""))
10859
.collect())
10960
}
61+
62+
fn find_plugin() -> Option<(PathBuf, Vec<String>)> {
63+
let args_vec: Vec<String> = std::env::args().skip(1).collect();
64+
let mut chain: Vec<String> = args_vec
65+
.iter()
66+
.take_while(|arg| !arg.starts_with("--"))
67+
.map(ToString::to_string)
68+
.collect();
69+
70+
while !chain.is_empty() {
71+
let name = chain.join("-");
72+
let bin = find_bin(&name).ok();
73+
74+
if let Some(bin) = &bin {
75+
let index = chain.len();
76+
let args = args_vec[index..]
77+
.iter()
78+
.map(ToString::to_string)
79+
.collect::<Vec<String>>();
80+
81+
return Some((bin.into(), args));
82+
}
83+
84+
chain.pop();
85+
}
86+
87+
None
88+
}

cmd/soroban-cli/src/config/address.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ pub enum Error {
4848
InvalidKeyName(String),
4949
#[error("Ledger not supported in this context")]
5050
LedgerNotSupported,
51+
#[error(transparent)]
52+
Ledger(#[from] signer::ledger::Error),
5153
}
5254

5355
impl FromStr for UnresolvedMuxedAccount {
@@ -85,7 +87,7 @@ impl UnresolvedMuxedAccount {
8587
) -> Result<xdr::MuxedAccount, Error> {
8688
match self {
8789
UnresolvedMuxedAccount::Ledger(hd_path) => Ok(xdr::MuxedAccount::Ed25519(
88-
ledger(*hd_path).await?.public_key().await?.0.into(),
90+
ledger::new(*hd_path).await?.public_key().await?.0.into(),
8991
)),
9092
UnresolvedMuxedAccount::Resolved(_) | UnresolvedMuxedAccount::AliasOrSecret(_) => {
9193
self.resolve_muxed_account_sync(locator, hd_path)

0 commit comments

Comments
 (0)