Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 90 additions & 1 deletion cmd/crates/soroban-test/tests/it/emulator.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use stellar_ledger::Blob;

use soroban_test::{AssertExt, TestEnv};
use soroban_test::{AssertExt, TestEnv, Wasm};
use std::sync::Arc;

use stellar_ledger::emulator_test_support::*;
Expand All @@ -12,6 +12,8 @@ use soroban_cli::{

use test_case::test_case;

const HELLO_WORLD: &Wasm = &Wasm::Custom("test-wasms", "test_hello_world");

#[test_case("nanos", 0; "when the device is NanoS")]
#[test_case("nanox", 1; "when the device is NanoX")]
#[test_case("nanosp", 2; "when the device is NanoS Plus")]
Expand Down Expand Up @@ -88,3 +90,90 @@ async fn test_signer(ledger_device_model: &str, hd_path: u32) {
)
.unwrap();
}

// Mirrors `invoke_auth_with_non_source_identity` from the integration tests:
// invoke a contract whose `auth(addr, world)` calls `addr.require_auth()`,
// where the auth identity (`testone`) is a Ledger-backed alias and the
// transaction source (`test`) is a regular keypair. Exercises the Soroban
// auth-entry signing path through the Ledger device.
#[test_case("nanos", 0; "when the device is NanoS")]
#[test_case("nanox", 1; "when the device is NanoX")]
#[test_case("nanosp", 2; "when the device is NanoS Plus")]
#[tokio::test]
async fn invoke_auth_with_ledger_identity(ledger_device_model: &str, hd_path: u32) {
let sandbox = Arc::new(TestEnv::new());
let container = TestEnv::speculos_container(ledger_device_model).await;
let host_port = container.get_host_port_ipv4(9998).await.unwrap();
let ui_host_port = container.get_host_port_ipv4(5000).await.unwrap();

sandbox
.new_assert_cmd("keys")
.arg("fund")
.arg("test")
.assert()
.success();

sandbox
.new_assert_cmd("keys")
.arg("add")
.arg("testone")
.arg("--ledger")
.arg("--hd-path")
.arg(hd_path.to_string())
.env("SPECULOS_PORT", host_port.to_string())
.assert()
.success();

let addr = sandbox
.new_assert_cmd("keys")
.arg("address")
.arg("testone")
.assert()
.success()
.stdout_as_str();

let id = sandbox
.new_assert_cmd("contract")
.arg("deploy")
.arg("--source")
.arg("test")
.arg("--wasm")
.arg(HELLO_WORLD.path())
.arg("--ignore-checks")
.assert()
.success()
.stdout_as_str();

let invoke = tokio::task::spawn_blocking({
let sandbox = Arc::clone(&sandbox);
let id = id.clone();
let addr = addr.clone();
move || {
let stdout = sandbox
.new_assert_cmd("contract")
.arg("invoke")
.arg("--source")
.arg("test")
.arg("--id")
.arg(&id)
.arg("--")
.arg("auth")
.arg("--addr")
.arg("testone")
.arg("--world=world")
.env("SPECULOS_PORT", host_port.to_string())
.assert()
.success()
.stdout_as_str();
assert_eq!(stdout, format!("\"{addr}\""));
}
});

let approve = tokio::task::spawn(approve_tx_hash_signature(
ui_host_port,
ledger_device_model.to_string(),
));

invoke.await.unwrap();
approve.await.unwrap();
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ impl FromStr for DeviceModel {
"nanos" => Ok(DeviceModel::NanoS),
"nanosp" => Ok(DeviceModel::NanoSP),
"nanox" => Ok(DeviceModel::NanoX),
_ => Err(format!("Unsupported device model: {}", s)),
_ => Err(format!("Unsupported device model: {s}")),
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pub async fn click(ui_host_port: u16, url: &str) {
let current_events = get_emulator_events(ui_host_port).await;

if !(previous_events == current_events) {
screen_has_changed = true
screen_has_changed = true;
}
}

Expand Down
24 changes: 12 additions & 12 deletions cmd/soroban-cli/src/commands/contract/arg_parsing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,25 +93,25 @@ fn running_cmd() -> String {
format!("{} --", args.join(" "))
}

pub async fn build_host_function_parameters(
pub fn build_host_function_parameters(
contract_id: &stellar_strkey::Contract,
slop: &[OsString],
spec_entries: &[ScSpecEntry],
config: &config::Args,
) -> Result<HostFunctionParameters, Error> {
build_host_function_parameters_with_filter(contract_id, slop, spec_entries, config, true).await
build_host_function_parameters_with_filter(contract_id, slop, spec_entries, config, true)
}

pub async fn build_constructor_parameters(
pub fn build_constructor_parameters(
contract_id: &stellar_strkey::Contract,
slop: &[OsString],
spec_entries: &[ScSpecEntry],
config: &config::Args,
) -> Result<HostFunctionParameters, Error> {
build_host_function_parameters_with_filter(contract_id, slop, spec_entries, config, false).await
build_host_function_parameters_with_filter(contract_id, slop, spec_entries, config, false)
}

async fn build_host_function_parameters_with_filter(
fn build_host_function_parameters_with_filter(
contract_id: &stellar_strkey::Contract,
slop: &[OsString],
spec_entries: &[ScSpecEntry],
Expand All @@ -122,7 +122,7 @@ async fn build_host_function_parameters_with_filter(
let cmd = build_clap_command(&spec, filter_constructor)?;
let (function, matches_) = parse_command_matches(cmd, slop)?;
let func = get_function_spec(&spec, &function)?;
let (parsed_args, signers) = parse_function_arguments(&func, &matches_, &spec, config).await?;
let (parsed_args, signers) = parse_function_arguments(&func, &matches_, &spec, config)?;
let invoke_args = build_invoke_contract_args(contract_id, &function, parsed_args)?;

Ok((function, spec, invoke_args, signers))
Expand Down Expand Up @@ -187,7 +187,7 @@ fn get_function_spec(spec: &Spec, function: &str) -> Result<ScSpecFunctionV0, Er
})
}

async fn parse_function_arguments(
fn parse_function_arguments(
func: &ScSpecFunctionV0,
matches_: &clap::ArgMatches,
spec: &Spec,
Expand All @@ -197,13 +197,13 @@ async fn parse_function_arguments(
let mut signers = Vec::<Signer>::new();

for i in func.inputs.iter() {
parse_single_argument(i, matches_, spec, config, &mut signers, &mut parsed_args).await?;
parse_single_argument(i, matches_, spec, config, &mut signers, &mut parsed_args)?;
}

Ok((parsed_args, signers))
}

async fn parse_single_argument(
fn parse_single_argument(
input: &stellar_xdr::curr::ScSpecFunctionInputV0,
matches_: &clap::ArgMatches,
spec: &Spec,
Expand Down Expand Up @@ -234,7 +234,7 @@ async fn parse_single_argument(
ScSpecTypeDef::Address | ScSpecTypeDef::MuxedAddress
) {
let trimmed_s = s.trim_matches('"');
if let Some(signer) = resolve_signer(trimmed_s, config).await {
if let Some(signer) = resolve_signer(trimmed_s, config) {
signers.push(signer);
}
}
Expand Down Expand Up @@ -464,10 +464,10 @@ fn resolve_address(addr_or_alias: &str, config: &config::Args) -> Result<String,
Ok(account)
}

async fn resolve_signer(addr_or_alias: &str, config: &config::Args) -> Option<Signer> {
fn resolve_signer(addr_or_alias: &str, config: &config::Args) -> Option<Signer> {
let secret = config.locator.get_secret_key(addr_or_alias).ok()?;
let print = Print::new(false);
let signer = secret.signer(config.hd_path(), print).await.ok()?;
let signer = secret.signer(config.hd_path(), print).ok()?;
Some(signer)
}

Expand Down
3 changes: 1 addition & 2 deletions cmd/soroban-cli/src/commands/contract/deploy/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -385,8 +385,7 @@ impl Cmd {
&slop,
&entries,
config,
)
.await?
)?
.2,
)
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/soroban-cli/src/commands/contract/invoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ impl Cmd {

if let Some(spec_entries) = &spec_entries {
// For testing wasm arg parsing
build_host_function_parameters(&contract_id, &self.slop, spec_entries, config).await?;
build_host_function_parameters(&contract_id, &self.slop, spec_entries, config)?;
}

let client = network.rpc_client()?;
Expand All @@ -295,7 +295,7 @@ impl Cmd {
.map_err(Error::from)?;

let params =
build_host_function_parameters(&contract_id, &self.slop, &spec_entries, config).await?;
build_host_function_parameters(&contract_id, &self.slop, &spec_entries, config)?;

let (function, spec, host_function_params, signers) = params;

Expand Down
16 changes: 7 additions & 9 deletions cmd/soroban-cli/src/commands/keys/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,6 @@ pub enum Error {

#[error("--hd-path is not valid with a secret key; secret keys cannot be derived")]
HdPathNotSupportedForSecretKey,

#[error("--hd-path {0} is out of range for a Ledger account index")]
HdPathOutOfRange(usize),
}

#[derive(Debug, clap::Parser, Clone)]
Expand Down Expand Up @@ -94,7 +91,7 @@ pub struct Cmd {
/// without re-passing the flag. Not valid with `--public-key` or a raw
/// secret key.
#[arg(long)]
pub hd_path: Option<usize>,
pub hd_path: Option<u32>,
}

impl Cmd {
Expand Down Expand Up @@ -125,9 +122,10 @@ impl Cmd {
}

async fn derive_ledger_secret(&self) -> Result<Secret, Error> {
let raw = self.hd_path.unwrap_or(0);
let index: u32 = raw.try_into().map_err(|_| Error::HdPathOutOfRange(raw))?;
let public_key = ledger::new(index).await?.public_key().await?;
let public_key = ledger::new(self.hd_path.unwrap_or_default())
.await?
.public_key()
.await?;
Ok(Secret::Ledger {
hardware: HardwareKind::Ledger,
public_key: public_key.to_string(),
Expand Down Expand Up @@ -181,7 +179,7 @@ impl Cmd {
}
}

fn build_secret(input: &str, hd_path: Option<usize>) -> Result<Secret, Error> {
fn build_secret(input: &str, hd_path: Option<u32>) -> Result<Secret, Error> {
let secret: Secret = input.parse()?;
match (secret, hd_path) {
(Secret::SecretKey { .. }, Some(_)) => Err(Error::HdPathNotSupportedForSecretKey),
Expand Down Expand Up @@ -248,7 +246,7 @@ mod tests {

fn cmd_with_public_key(
public_key: &str,
hd_path: Option<usize>,
hd_path: Option<u32>,
) -> (tempfile::TempDir, locator::Args, Cmd) {
let (temp_dir, locator, mut cmd) = set_up_test();
cmd.public_key = Some(public_key.to_string());
Expand Down
2 changes: 1 addition & 1 deletion cmd/soroban-cli/src/commands/keys/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ pub struct Cmd {
/// `--secure-store` or plain seed-phrase storage it is persisted on the identity
/// so later commands derive the same account without re-passing the flag.
#[arg(long)]
pub hd_path: Option<usize>,
pub hd_path: Option<u32>,

#[command(flatten)]
pub network: network::Args,
Expand Down
12 changes: 5 additions & 7 deletions cmd/soroban-cli/src/commands/keys/public_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ pub enum Error {

#[error(transparent)]
Ledger(#[from] ledger::Error),

#[error("--hd-path {0} is out of range for a Ledger account index")]
HdPathOutOfRange(usize),
}

#[derive(Debug, clap::Parser, Clone)]
Expand All @@ -26,7 +23,7 @@ pub struct Cmd {
/// If identity is a seed phrase use this hd path, default is 0.
/// With --ledger this is the Ledger account index (default 0).
#[arg(long)]
pub hd_path: Option<usize>,
pub hd_path: Option<u32>,

/// Derive the address from a connected Ledger hardware wallet at
/// `m/44'/148'/N'`, where `N` defaults to 0 and can be set with
Expand All @@ -46,9 +43,10 @@ impl Cmd {

pub async fn public_key(&self) -> Result<stellar_strkey::ed25519::PublicKey, Error> {
if self.ledger {
let raw = self.hd_path.unwrap_or(0);
let index: u32 = raw.try_into().map_err(|_| Error::HdPathOutOfRange(raw))?;
return Ok(ledger::new(index).await?.public_key().await?);
return Ok(ledger::new(self.hd_path.unwrap_or_default())
.await?
.public_key()
.await?);
}
let name = self
.name
Expand Down
2 changes: 1 addition & 1 deletion cmd/soroban-cli/src/commands/keys/secret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub struct Cmd {

/// If identity is a seed phrase use this hd path, default is 0
#[arg(long, conflicts_with = "phrase")]
pub hd_path: Option<usize>,
pub hd_path: Option<u32>,

#[command(flatten)]
pub locator: locator::Args,
Expand Down
2 changes: 1 addition & 1 deletion cmd/soroban-cli/src/commands/ledger/entry/fetch/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub struct Cmd {

/// If identity is a seed phrase use this hd path, default is 0
#[arg(long)]
pub hd_path: Option<usize>,
pub hd_path: Option<u32>,
}

#[derive(thiserror::Error, Debug)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub struct Cmd {

/// If identity is a seed phrase use this hd path, default is 0
#[arg(long)]
pub hd_path: Option<usize>,
pub hd_path: Option<u32>,
}

#[derive(thiserror::Error, Debug)]
Expand Down
2 changes: 1 addition & 1 deletion cmd/soroban-cli/src/commands/ledger/entry/fetch/offer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub struct Cmd {

/// If identity is a seed phrase use this hd path, default is 0
#[arg(long)]
pub hd_path: Option<usize>,
pub hd_path: Option<u32>,
}

#[derive(thiserror::Error, Debug)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub struct Cmd {

/// If account is a seed phrase use this hd path, default is 0
#[arg(long)]
pub hd_path: Option<usize>,
pub hd_path: Option<u32>,
}

#[derive(thiserror::Error, Debug)]
Expand Down
Loading
Loading