From 95148e12c4d522c7bdea10640e50f11c9076094e Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Mon, 22 Jul 2024 23:35:47 +1000 Subject: [PATCH 1/4] Help tweaks --- .../src/commands/config/locator.rs | 2 +- cmd/soroban-cli/src/commands/mod.rs | 43 +++++++++++-------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/cmd/soroban-cli/src/commands/config/locator.rs b/cmd/soroban-cli/src/commands/config/locator.rs index 5dc4dae85..cbcb9e8b3 100644 --- a/cmd/soroban-cli/src/commands/config/locator.rs +++ b/cmd/soroban-cli/src/commands/config/locator.rs @@ -80,7 +80,7 @@ pub struct Args { pub global: bool, /// Location of config directory, default is "." - #[arg(long, help_heading = "TESTING_OPTIONS")] + #[arg(long)] pub config_dir: Option, } diff --git a/cmd/soroban-cli/src/commands/mod.rs b/cmd/soroban-cli/src/commands/mod.rs index 0d5fcff57..5f2c65b5a 100644 --- a/cmd/soroban-cli/src/commands/mod.rs +++ b/cmd/soroban-cli/src/commands/mod.rs @@ -18,13 +18,15 @@ pub mod version; pub mod txn_result; pub const HEADING_RPC: &str = "Options (RPC)"; -const ABOUT: &str = "With the Stellar CLI you can: +const ABOUT: &str = "Work seamlessly with Stellar accounts, contracts, and assets from the command line. -- build, deploy and interact with contracts -- set identities to sign with -- configure networks -- generate keys -- more! +- Generate and manage keys and accounts +- Build, deploy, and interact with contracts +- Deploy asset contracts +- Stream events +- Start local testnets +- Decode, encode XDR +- More! For additional information see: @@ -35,21 +37,21 @@ For additional information see: // long_about is shown when someone uses `--help`; short help when using `-h` const LONG_ABOUT: &str = " -The easiest way to get started is to generate a new identity: +To get started generate a new identity: stellar keys generate alice -You can use identities with the `--source` flag in other commands later. +Use keys with the `--source` flag in other commands. -Commands that relate to smart contract interactions are organized under the `contract` subcommand. List them: +Commands that work with contracts are organized under the `contract` subcommand. List them: stellar contract --help -A Soroban contract has its interface schema types embedded in the binary that gets deployed on-chain, making it possible to dynamically generate a custom CLI for each. The invoke subcommand makes use of this: +Use contracts like a CLI: stellar contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- --help -Anything after the `--` double dash (the \"slop\") is parsed as arguments to the contract-specific CLI, generated on-the-fly from the embedded schema. For the hello world example, with a function called `hello` that takes one string argument `to`, here's how you invoke it: +Anything after the `--` double dash (the \"slop\") is parsed as arguments to the contract-specific CLI, generated on-the-fly from the contract schema. For the hello world example, with a function called `hello` that takes one string argument `to`, here's how you invoke it: stellar contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- hello --to world "; @@ -125,30 +127,35 @@ impl FromStr for Root { #[derive(Parser, Debug)] pub enum Cmd { - /// Print shell completion code for the specified shell. - #[command(long_about = completion::LONG_ABOUT)] - Completion(completion::Cmd), /// Tools for smart contract developers #[command(subcommand)] Contract(contract::Cmd), /// Watch the network for contract events Events(events::Cmd), + /// Create and manage identities including keys and addresses #[command(subcommand)] Keys(keys::Cmd), - /// Decode and encode XDR - Xdr(stellar_xdr::cli::Root), + /// Start and configure networks #[command(subcommand)] Network(network::Cmd), - /// Print version information - Version(version::Cmd), + /// Sign, Simulate, and Send transactions #[command(subcommand)] Tx(tx::Cmd), + + /// Decode and encode XDR + Xdr(stellar_xdr::cli::Root), + + /// Print shell completion code for the specified shell. + #[command(long_about = completion::LONG_ABOUT)] + Completion(completion::Cmd), /// Cache for transactions and contract specs #[command(subcommand)] Cache(cache::Cmd), + /// Print version information + Version(version::Cmd), } #[derive(thiserror::Error, Debug)] From 1c3ac4d175c25a013db9d988fc8372742e631801 Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Mon, 22 Jul 2024 23:48:46 +1000 Subject: [PATCH 2/4] update help\ --- FULL_HELP_DOCS.md | 416 +++++++++++++++++++++++----------------------- 1 file changed, 209 insertions(+), 207 deletions(-) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 689ce3036..0900ab0a5 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -4,13 +4,15 @@ This document contains the help content for the `stellar` command-line program. ## `stellar` -With the Stellar CLI you can: +Work seamlessly with Stellar accounts, contracts, and assets from the command line. -- build, deploy and interact with contracts -- set identities to sign with -- configure networks -- generate keys -- more! +- Generate and manage keys and accounts +- Build, deploy, and interact with contracts +- Deploy asset contracts +- Stream events +- Start local testnets +- Decode, encode XDR +- More! For additional information see: @@ -18,21 +20,21 @@ For additional information see: - Smart Contract Docs: https://developers.stellar.org/docs/build/smart-contracts/overview - CLI Docs: https://developers.stellar.org/docs/tools/stellar-cli -The easiest way to get started is to generate a new identity: +To get started generate a new identity: stellar keys generate alice -You can use identities with the `--source` flag in other commands later. +Use keys with the `--source` flag in other commands. -Commands that relate to smart contract interactions are organized under the `contract` subcommand. List them: +Commands that work with contracts are organized under the `contract` subcommand. List them: stellar contract --help -A Soroban contract has its interface schema types embedded in the binary that gets deployed on-chain, making it possible to dynamically generate a custom CLI for each. The invoke subcommand makes use of this: +Use contracts like a CLI: stellar contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- --help -Anything after the `--` double dash (the "slop") is parsed as arguments to the contract-specific CLI, generated on-the-fly from the embedded schema. For the hello world example, with a function called `hello` that takes one string argument `to`, here's how you invoke it: +Anything after the `--` double dash (the "slop") is parsed as arguments to the contract-specific CLI, generated on-the-fly from the contract schema. For the hello world example, with a function called `hello` that takes one string argument `to`, here's how you invoke it: stellar contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- hello --to world @@ -41,15 +43,15 @@ Anything after the `--` double dash (the "slop") is parsed as arguments to the c ###### **Subcommands:** -* `completion` — Print shell completion code for the specified shell * `contract` — Tools for smart contract developers * `events` — Watch the network for contract events * `keys` — Create and manage identities including keys and addresses -* `xdr` — Decode and encode XDR * `network` — Start and configure networks -* `version` — Print version information * `tx` — Sign, Simulate, and Send transactions +* `xdr` — Decode and encode XDR +* `completion` — Print shell completion code for the specified shell * `cache` — Cache for transactions and contract specs +* `version` — Print version information ###### **Options:** @@ -64,28 +66,6 @@ Anything after the `--` double dash (the "slop") is parsed as arguments to the c -## `stellar completion` - -Print shell completion code for the specified shell - -Ensure the completion package for your shell is installed, e.g. bash-completion for bash. - -To enable autocomplete in the current bash shell, run: `source <(stellar completion --shell bash)` - -To enable autocomplete permanently, run: `echo "source <(stellar completion --shell bash)" >> ~/.bashrc` - - -**Usage:** `stellar completion --shell ` - -###### **Options:** - -* `--shell ` — The shell type - - Possible values: `bash`, `elvish`, `fish`, `powershell`, `zsh` - - - - ## `stellar contract` Tools for smart contract developers @@ -818,169 +798,6 @@ Given an identity return its private key -## `stellar xdr` - -Decode and encode XDR - -**Usage:** `stellar xdr [CHANNEL] ` - -###### **Subcommands:** - -* `types` — View information about types -* `guess` — Guess the XDR type -* `decode` — Decode XDR -* `encode` — Encode XDR -* `version` — Print version information - -###### **Arguments:** - -* `` — Channel of XDR to operate on - - Default value: `+curr` - - Possible values: `+curr`, `+next` - - - - -## `stellar xdr types` - -View information about types - -**Usage:** `stellar xdr types ` - -###### **Subcommands:** - -* `list` — -* `schema` — - - - -## `stellar xdr types list` - -**Usage:** `stellar xdr types list [OPTIONS]` - -###### **Options:** - -* `--output ` - - Default value: `plain` - - Possible values: `plain`, `json`, `json-formatted` - - - - -## `stellar xdr types schema` - -**Usage:** `stellar xdr types schema [OPTIONS] --type ` - -###### **Options:** - -* `--type ` — XDR type to decode -* `--output ` - - Default value: `json-schema-draft201909` - - Possible values: `json-schema-draft7`, `json-schema-draft201909` - - - - -## `stellar xdr guess` - -Guess the XDR type - -**Usage:** `stellar xdr guess [OPTIONS] [FILE]` - -###### **Arguments:** - -* `` — File to decode, or stdin if omitted - -###### **Options:** - -* `--input ` - - Default value: `single-base64` - - Possible values: `single`, `single-base64`, `stream`, `stream-base64`, `stream-framed` - -* `--output ` - - Default value: `list` - - Possible values: `list` - -* `--certainty ` — Certainty as an arbitrary value - - Default value: `2` - - - -## `stellar xdr decode` - -Decode XDR - -**Usage:** `stellar xdr decode [OPTIONS] --type [FILES]...` - -###### **Arguments:** - -* `` — Files to decode, or stdin if omitted - -###### **Options:** - -* `--type ` — XDR type to decode -* `--input ` - - Default value: `stream-base64` - - Possible values: `single`, `single-base64`, `stream`, `stream-base64`, `stream-framed` - -* `--output ` - - Default value: `json` - - Possible values: `json`, `json-formatted`, `rust-debug`, `rust-debug-formatted` - - - - -## `stellar xdr encode` - -Encode XDR - -**Usage:** `stellar xdr encode [OPTIONS] --type [FILES]...` - -###### **Arguments:** - -* `` — Files to encode, or stdin if omitted - -###### **Options:** - -* `--type ` — XDR type to encode -* `--input ` - - Default value: `json` - - Possible values: `json` - -* `--output ` - - Default value: `single-base64` - - Possible values: `single`, `single-base64` - - - - -## `stellar xdr version` - -Print version information - -**Usage:** `stellar xdr version` - - - ## `stellar network` Start and configure networks @@ -1189,14 +1006,6 @@ Stop a network started with `network container start`. For example, if you ran ` -## `stellar version` - -Print version information - -**Usage:** `stellar version` - - - ## `stellar tx` Sign, Simulate, and Send transactions @@ -1227,6 +1036,191 @@ Simulate a transaction envelope from stdin +## `stellar xdr` + +Decode and encode XDR + +**Usage:** `stellar xdr [CHANNEL] ` + +###### **Subcommands:** + +* `types` — View information about types +* `guess` — Guess the XDR type +* `decode` — Decode XDR +* `encode` — Encode XDR +* `version` — Print version information + +###### **Arguments:** + +* `` — Channel of XDR to operate on + + Default value: `+curr` + + Possible values: `+curr`, `+next` + + + + +## `stellar xdr types` + +View information about types + +**Usage:** `stellar xdr types ` + +###### **Subcommands:** + +* `list` — +* `schema` — + + + +## `stellar xdr types list` + +**Usage:** `stellar xdr types list [OPTIONS]` + +###### **Options:** + +* `--output ` + + Default value: `plain` + + Possible values: `plain`, `json`, `json-formatted` + + + + +## `stellar xdr types schema` + +**Usage:** `stellar xdr types schema [OPTIONS] --type ` + +###### **Options:** + +* `--type ` — XDR type to decode +* `--output ` + + Default value: `json-schema-draft201909` + + Possible values: `json-schema-draft7`, `json-schema-draft201909` + + + + +## `stellar xdr guess` + +Guess the XDR type + +**Usage:** `stellar xdr guess [OPTIONS] [FILE]` + +###### **Arguments:** + +* `` — File to decode, or stdin if omitted + +###### **Options:** + +* `--input ` + + Default value: `single-base64` + + Possible values: `single`, `single-base64`, `stream`, `stream-base64`, `stream-framed` + +* `--output ` + + Default value: `list` + + Possible values: `list` + +* `--certainty ` — Certainty as an arbitrary value + + Default value: `2` + + + +## `stellar xdr decode` + +Decode XDR + +**Usage:** `stellar xdr decode [OPTIONS] --type [FILES]...` + +###### **Arguments:** + +* `` — Files to decode, or stdin if omitted + +###### **Options:** + +* `--type ` — XDR type to decode +* `--input ` + + Default value: `stream-base64` + + Possible values: `single`, `single-base64`, `stream`, `stream-base64`, `stream-framed` + +* `--output ` + + Default value: `json` + + Possible values: `json`, `json-formatted`, `rust-debug`, `rust-debug-formatted` + + + + +## `stellar xdr encode` + +Encode XDR + +**Usage:** `stellar xdr encode [OPTIONS] --type [FILES]...` + +###### **Arguments:** + +* `` — Files to encode, or stdin if omitted + +###### **Options:** + +* `--type ` — XDR type to encode +* `--input ` + + Default value: `json` + + Possible values: `json` + +* `--output ` + + Default value: `single-base64` + + Possible values: `single`, `single-base64` + + + + +## `stellar xdr version` + +Print version information + +**Usage:** `stellar xdr version` + + + +## `stellar completion` + +Print shell completion code for the specified shell + +Ensure the completion package for your shell is installed, e.g. bash-completion for bash. + +To enable autocomplete in the current bash shell, run: `source <(stellar completion --shell bash)` + +To enable autocomplete permanently, run: `echo "source <(stellar completion --shell bash)" >> ~/.bashrc` + + +**Usage:** `stellar completion --shell ` + +###### **Options:** + +* `--shell ` — The shell type + + Possible values: `bash`, `elvish`, `fish`, `powershell`, `zsh` + + + + ## `stellar cache` Cache for transactions and contract specs @@ -1296,3 +1290,11 @@ Read cached action +## `stellar version` + +Print version information + +**Usage:** `stellar version` + + + From d5fa04bac4f27680effb9e4c16a79aaa97d30a96 Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Tue, 23 Jul 2024 08:25:17 +1000 Subject: [PATCH 3/4] fix --- cmd/soroban-cli/src/commands/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/soroban-cli/src/commands/mod.rs b/cmd/soroban-cli/src/commands/mod.rs index 5f2c65b5a..a9f10bdd8 100644 --- a/cmd/soroban-cli/src/commands/mod.rs +++ b/cmd/soroban-cli/src/commands/mod.rs @@ -18,7 +18,8 @@ pub mod version; pub mod txn_result; pub const HEADING_RPC: &str = "Options (RPC)"; -const ABOUT: &str = "Work seamlessly with Stellar accounts, contracts, and assets from the command line. +const ABOUT: &str = + "Work seamlessly with Stellar accounts, contracts, and assets from the command line. - Generate and manage keys and accounts - Build, deploy, and interact with contracts From 2819d7c7c0e19ccac7e6dcca6361db38e6820570 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 23 Jul 2024 17:05:20 -0400 Subject: [PATCH 4/4] fix: update main (#1480) --- Cargo.lock | 1 + FULL_HELP_DOCS.md | 16 ++ .../soroban-test/tests/it/integration/tx.rs | 19 +++ cmd/soroban-cli/Cargo.toml | 1 + cmd/soroban-cli/src/commands/contract/init.rs | 155 ++++++++++++++---- cmd/soroban-cli/src/commands/tx/hash.rs | 33 ++++ cmd/soroban-cli/src/commands/tx/mod.rs | 7 + 7 files changed, 201 insertions(+), 31 deletions(-) create mode 100644 cmd/soroban-cli/src/commands/tx/hash.rs diff --git a/Cargo.lock b/Cargo.lock index 4d70268b9..edab8bb9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4732,6 +4732,7 @@ dependencies = [ "tracing-subscriber", "ulid", "ureq", + "walkdir", "wasm-opt", "wasmparser 0.90.0", "which", diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index d7cdce2c6..f443311ca 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -404,6 +404,7 @@ Initialize a Soroban project with an example contract * `-f`, `--frontend-template ` — An optional flag to pass in a url for a frontend template repository. Default value: `` +* `-o`, `--overwrite` — Overwrite all existing files. @@ -1006,6 +1007,7 @@ Sign, Simulate, and Send transactions ###### **Subcommands:** * `simulate` — Simulate a transaction envelope from stdin +* `hash` — Calculate the hash of a transaction envelope from stdin @@ -1027,6 +1029,20 @@ Simulate a transaction envelope from stdin +## `stellar tx hash` + +Calculate the hash of a transaction envelope from stdin + +**Usage:** `stellar tx hash [OPTIONS]` + +###### **Options:** + +* `--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 xdr` Decode and encode XDR diff --git a/cmd/crates/soroban-test/tests/it/integration/tx.rs b/cmd/crates/soroban-test/tests/it/integration/tx.rs index 9f5204a8d..bcb880b18 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx.rs @@ -30,3 +30,22 @@ async fn txn_simulate() { assembled_str ); } + +#[tokio::test] +async fn txn_hash() { + let sandbox = &TestEnv::new(); + + let xdr_base64 = "AAAAAgAAAACVk/0xt9tV/cUbF53iwQ3tkKLlq9zG2wV5qd9lRjZjlQAHt/sAFsKTAAAABAAAAAEAAAAAAAAAAAAAAABmOg6nAAAAAAAAAAEAAAAAAAAAGAAAAAAAAAABfcHs35M1GZ/JkY2+DHMs4dEUaqjynMnDYK/Gp0eulN8AAAAIdHJhbnNmZXIAAAADAAAAEgAAAAEFO1FR2Wg49QFY5KPOFAQ0bV5fN+7LD2GSQvOaHSH44QAAABIAAAAAAAAAAJWT/TG321X9xRsXneLBDe2QouWr3MbbBXmp32VGNmOVAAAACgAAAAAAAAAAAAAAADuaygAAAAABAAAAAQAAAAEFO1FR2Wg49QFY5KPOFAQ0bV5fN+7LD2GSQvOaHSH44QAAAY9SyLSVABbC/QAAABEAAAABAAAAAwAAAA8AAAASYXV0aGVudGljYXRvcl9kYXRhAAAAAAANAAAAJUmWDeWIDoxodDQXD2R2YFuP5K65ooYyx5lc87qDHZdjHQAAAAAAAAAAAAAPAAAAEGNsaWVudF9kYXRhX2pzb24AAAANAAAAcnsidHlwZSI6IndlYmF1dGhuLmdldCIsImNoYWxsZW5nZSI6ImhnMlRhOG8wWTliWFlyWlMyZjhzWk1kRFp6ektCSXhQNTZSd1FaNE90bTgiLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjQ1MDcifQAAAAAADwAAAAlzaWduYXR1cmUAAAAAAAANAAAAQBcpuTFMxzkAdBs+5VIyJCBHaNuwEAva+kZVET4YuHVKF8gNII567RhxsnhBBSo5dDvssTN6vf2i42eEty66MtoAAAAAAAAAAX3B7N+TNRmfyZGNvgxzLOHRFGqo8pzJw2CvxqdHrpTfAAAACHRyYW5zZmVyAAAAAwAAABIAAAABBTtRUdloOPUBWOSjzhQENG1eXzfuyw9hkkLzmh0h+OEAAAASAAAAAAAAAACVk/0xt9tV/cUbF53iwQ3tkKLlq9zG2wV5qd9lRjZjlQAAAAoAAAAAAAAAAAAAAAA7msoAAAAAAAAAAAEAAAAAAAAAAwAAAAYAAAABfcHs35M1GZ/JkY2+DHMs4dEUaqjynMnDYK/Gp0eulN8AAAAUAAAAAQAAAAYAAAABBTtRUdloOPUBWOSjzhQENG1eXzfuyw9hkkLzmh0h+OEAAAAUAAAAAQAAAAeTiL4Gr2piUAmsXTev1ZzJ4kE2NUGZ0QMObd05iAMyzAAAAAMAAAAGAAAAAX3B7N+TNRmfyZGNvgxzLOHRFGqo8pzJw2CvxqdHrpTfAAAAEAAAAAEAAAACAAAADwAAAAdCYWxhbmNlAAAAABIAAAABBTtRUdloOPUBWOSjzhQENG1eXzfuyw9hkkLzmh0h+OEAAAABAAAAAAAAAACVk/0xt9tV/cUbF53iwQ3tkKLlq9zG2wV5qd9lRjZjlQAAAAYAAAABBTtRUdloOPUBWOSjzhQENG1eXzfuyw9hkkLzmh0h+OEAAAAVAAABj1LItJUAAAAAAEyTowAAGMgAAAG4AAAAAAADJBsAAAABRjZjlQAAAEASFnAIzNqpfdzv6yT0rSLMUDFgt7a/inCHurNCG55Jp8Imho04qRH+JNdkq0BgMC7yAJqH4N6Y2iGflFt3Lp4L"; + + let expected_hash = "bcc9fa60c8f6607c981d6e1c65d77ae07617720113f9080fe5883d8e4a331a68"; + + let hash = sandbox + .new_assert_cmd("tx") + .arg("hash") + .write_stdin(xdr_base64.as_bytes()) + .assert() + .success() + .stdout_as_str(); + + assert_eq!(hash.trim(), expected_hash); +} diff --git a/cmd/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml index 3b0b90153..6d40ddd90 100644 --- a/cmd/soroban-cli/Cargo.toml +++ b/cmd/soroban-cli/Cargo.toml @@ -130,3 +130,4 @@ ureq = { version = "2.9.1", features = ["json"] } assert_cmd = "2.0.4" assert_fs = "1.0.7" predicates = "2.1.5" +walkdir = "2.5.0" diff --git a/cmd/soroban-cli/src/commands/contract/init.rs b/cmd/soroban-cli/src/commands/contract/init.rs index 21b9d48bb..26bf5816a 100644 --- a/cmd/soroban-cli/src/commands/contract/init.rs +++ b/cmd/soroban-cli/src/commands/contract/init.rs @@ -47,6 +47,9 @@ pub struct Cmd { long_help = "An optional flag to pass in a url for a frontend template repository." )] pub frontend_template: String, + + #[arg(short, long, long_help = "Overwrite all existing files.")] + pub overwrite: bool, } fn possible_example_values() -> ValueParser { @@ -89,7 +92,12 @@ impl Cmd { println!("ℹ️ Initializing project at {}", self.project_path); let project_path = Path::new(&self.project_path); - init(project_path, &self.frontend_template, &self.with_example)?; + init( + project_path, + &self.frontend_template, + &self.with_example, + self.overwrite, + )?; Ok(()) } @@ -103,13 +111,14 @@ fn init( project_path: &Path, frontend_template: &str, with_examples: &[String], + overwrite: bool, ) -> Result<(), Error> { // create a project dir, and copy the contents of the base template (contract-init-template) into it create_dir_all(project_path).map_err(|e| { eprintln!("Error creating new project directory: {project_path:?}"); e })?; - copy_template_files(project_path)?; + copy_template_files(project_path, overwrite)?; if !check_internet_connection() { println!("⚠️ It doesn't look like you're connected to the internet. We're still able to initialize a new project, but additional examples and the frontend template will not be included."); @@ -127,7 +136,7 @@ fn init( clone_repo(frontend_template, fe_template_dir.path())?; // copy the frontend template files into the project - copy_frontend_files(fe_template_dir.path(), project_path)?; + copy_frontend_files(fe_template_dir.path(), project_path, overwrite)?; } // if there are --with-example flags, include the example contracts @@ -142,17 +151,17 @@ fn init( clone_repo(SOROBAN_EXAMPLES_URL, examples_dir.path())?; // copy the example contracts into the project - copy_example_contracts(examples_dir.path(), project_path, with_examples)?; + copy_example_contracts(examples_dir.path(), project_path, with_examples, overwrite)?; } Ok(()) } -fn copy_template_files(project_path: &Path) -> Result<(), Error> { +fn copy_template_files(project_path: &Path, overwrite: bool) -> Result<(), Error> { for item in TemplateFiles::iter() { let mut to = project_path.join(item.as_ref()); - - if file_exists(&to) { + let exists = file_exists(&to); + if exists && !overwrite { println!( "ℹ️ Skipped creating {} as it already exists", &to.to_string_lossy() @@ -184,7 +193,11 @@ fn copy_template_files(project_path: &Path) -> Result<(), Error> { to = project_path.join(item_parent_path).join("Cargo.toml"); } - println!("➕ Writing {}", &to.to_string_lossy()); + if exists { + println!("🔄 Overwriting {}", &to.to_string_lossy()); + } else { + println!("➕ Writing {}", &to.to_string_lossy()); + } write(&to, file_contents).map_err(|e| { eprintln!("Error writing file: {to:?}"); e @@ -193,7 +206,7 @@ fn copy_template_files(project_path: &Path) -> Result<(), Error> { Ok(()) } -fn copy_contents(from: &Path, to: &Path) -> Result<(), Error> { +fn copy_contents(from: &Path, to: &Path, overwrite: bool) -> Result<(), Error> { let contents_to_exclude_from_copy = [ ".git", ".github", @@ -223,24 +236,26 @@ fn copy_contents(from: &Path, to: &Path) -> Result<(), Error> { eprintln!("Error creating directory: {new_path:?}"); e })?; - copy_contents(&path, &new_path)?; + copy_contents(&path, &new_path, overwrite)?; } else { - if file_exists(&new_path) { - if new_path.to_string_lossy().contains(".gitignore") { - append_contents(&path, &new_path)?; - } - if new_path.to_string_lossy().contains("README.md") { + let exists = file_exists(&new_path); + let new_path_str = new_path.to_string_lossy(); + if exists { + let append = + new_path_str.contains(".gitignore") || new_path_str.contains("README.md"); + if append { append_contents(&path, &new_path)?; } - println!( - "ℹ️ Skipped creating {} as it already exists", - &new_path.to_string_lossy() - ); - continue; + if overwrite && !append { + println!("🔄 Overwriting {new_path_str}"); + } else { + println!("ℹ️ Skipped creating {new_path_str} as it already exists"); + continue; + } + } else { + println!("➕ Writing {new_path_str}"); } - - println!("➕ Writing {}", &new_path.to_string_lossy()); copy(&path, &new_path).map_err(|e| { eprintln!( "Error copying from {:?} to {:?}", @@ -302,7 +317,12 @@ fn clone_repo(from_url: &str, to_path: &Path) -> Result<(), Error> { Ok(()) } -fn copy_example_contracts(from: &Path, to: &Path, contracts: &[String]) -> Result<(), Error> { +fn copy_example_contracts( + from: &Path, + to: &Path, + contracts: &[String], + overwrite: bool, +) -> Result<(), Error> { let project_contracts_path = to.join("contracts"); for contract in contracts { println!("ℹ️ Initializing example contract: {contract}"); @@ -315,7 +335,7 @@ fn copy_example_contracts(from: &Path, to: &Path, contracts: &[String]) -> Resul e })?; - copy_contents(&from_contract_path, &to_contract_path)?; + copy_contents(&from_contract_path, &to_contract_path, overwrite)?; edit_contract_cargo_file(&to_contract_path)?; } @@ -354,9 +374,9 @@ fn edit_contract_cargo_file(contract_path: &Path) -> Result<(), Error> { Ok(()) } -fn copy_frontend_files(from: &Path, to: &Path) -> Result<(), Error> { +fn copy_frontend_files(from: &Path, to: &Path, overwrite: bool) -> Result<(), Error> { println!("ℹ️ Initializing with frontend template"); - copy_contents(from, to)?; + copy_contents(from, to, overwrite)?; edit_package_json_files(to) } @@ -447,7 +467,13 @@ fn get_merged_file_delimiter(file_path: &Path) -> String { #[cfg(test)] mod tests { use itertools::Itertools; - use std::fs::{self, read_to_string}; + use std::{ + collections::HashMap, + fs::{self, read_to_string}, + path::PathBuf, + time::SystemTime, + }; + use walkdir::WalkDir; use super::*; @@ -458,7 +484,8 @@ mod tests { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); let with_examples = vec![]; - init(project_dir.as_path(), "", &with_examples).unwrap(); + let overwrite = false; + init(project_dir.as_path(), "", &with_examples, overwrite).unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -476,7 +503,8 @@ mod tests { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); let with_examples = ["alloc".to_owned()]; - init(project_dir.as_path(), "", &with_examples).unwrap(); + let overwrite = false; + init(project_dir.as_path(), "", &with_examples, overwrite).unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -499,7 +527,8 @@ mod tests { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join("project"); let with_examples = ["account".to_owned(), "atomic_swap".to_owned()]; - init(project_dir.as_path(), "", &with_examples).unwrap(); + let overwrite = false; + init(project_dir.as_path(), "", &with_examples, overwrite).unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -523,7 +552,8 @@ mod tests { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join("project"); let with_examples = ["invalid_example".to_owned(), "atomic_swap".to_owned()]; - assert!(init(project_dir.as_path(), "", &with_examples,).is_err()); + let overwrite = false; + assert!(init(project_dir.as_path(), "", &with_examples, overwrite).is_err()); temp_dir.close().unwrap(); } @@ -533,10 +563,12 @@ mod tests { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); let with_examples = vec![]; + let overwrite = false; init( project_dir.as_path(), "https://github.com/stellar/soroban-astro-template", &with_examples, + overwrite, ) .unwrap(); @@ -556,15 +588,73 @@ mod tests { temp_dir.close().unwrap(); } + #[test] + fn test_init_with_overwrite() { + let temp_dir = tempfile::tempdir().unwrap(); + let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); + let with_examples = vec![]; + + // First initialization + init( + project_dir.as_path(), + "https://github.com/stellar/soroban-astro-template", + &with_examples, + false, + ) + .unwrap(); + + // Get initial modification times + let initial_mod_times = get_mod_times(&project_dir); + + // Second initialization with overwrite + init( + project_dir.as_path(), + "https://github.com/stellar/soroban-astro-template", + &with_examples, + true, // overwrite = true + ) + .unwrap(); + + // Get new modification times + let new_mod_times = get_mod_times(&project_dir); + + // Compare modification times + for (path, initial_time) in initial_mod_times { + let new_time = new_mod_times.get(&path).expect("File should still exist"); + assert!( + new_time > &initial_time, + "File {} should have a later modification time", + path.display() + ); + } + + temp_dir.close().unwrap(); + } + + fn get_mod_times(dir: &Path) -> HashMap { + let mut mod_times = HashMap::new(); + for entry in WalkDir::new(dir) { + let entry = entry.unwrap(); + if entry.file_type().is_file() { + let path = entry.path().to_owned(); + let metadata = fs::metadata(&path).unwrap(); + mod_times.insert(path, metadata.modified().unwrap()); + } + } + mod_times + } + #[test] fn test_init_from_within_an_existing_project() { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join("./"); let with_examples = vec![]; + let overwrite = false; init( project_dir.as_path(), "https://github.com/stellar/soroban-astro-template", &with_examples, + overwrite, ) .unwrap(); @@ -591,10 +681,12 @@ mod tests { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); let with_examples = vec![]; + let overwrite = false; init( project_dir.as_path(), "https://github.com/stellar/soroban-astro-template", &with_examples, + overwrite, ) .unwrap(); @@ -603,6 +695,7 @@ mod tests { project_dir.as_path(), "https://github.com/stellar/soroban-astro-template", &with_examples, + overwrite, ) .unwrap(); diff --git a/cmd/soroban-cli/src/commands/tx/hash.rs b/cmd/soroban-cli/src/commands/tx/hash.rs new file mode 100644 index 000000000..d5b2c42e6 --- /dev/null +++ b/cmd/soroban-cli/src/commands/tx/hash.rs @@ -0,0 +1,33 @@ +use crate::{commands::global, utils::transaction_hash}; +use hex; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + TxEnvelopeFromStdin(#[from] super::xdr::Error), + #[error(transparent)] + XdrToBase64(#[from] soroban_env_host::xdr::Error), + #[error(transparent)] + Config(#[from] super::super::network::Error), +} + +// Command to return the transaction hash submitted to a network +/// e.g. `cat file.txt | soroban tx hash` +#[derive(Debug, clap::Parser, Clone, Default)] +#[group(skip)] +pub struct Cmd { + #[clap(flatten)] + pub network: super::super::network::Args, +} + +impl Cmd { + pub fn run(&self, global_args: &global::Args) -> Result<(), Error> { + let tx = super::xdr::unwrap_envelope_v1(super::xdr::tx_envelope_from_stdin()?)?; + let network = &self.network.get(&global_args.locator)?; + println!( + "{}", + hex::encode(transaction_hash(&tx, &network.network_passphrase)?) + ); + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/tx/mod.rs b/cmd/soroban-cli/src/commands/tx/mod.rs index 48161e3c7..59f07228a 100644 --- a/cmd/soroban-cli/src/commands/tx/mod.rs +++ b/cmd/soroban-cli/src/commands/tx/mod.rs @@ -2,6 +2,7 @@ use clap::Parser; use super::global; +pub mod hash; pub mod simulate; pub mod xdr; @@ -9,6 +10,8 @@ pub mod xdr; pub enum Cmd { /// Simulate a transaction envelope from stdin Simulate(simulate::Cmd), + /// Calculate the hash of a transaction envelope from stdin + Hash(hash::Cmd), } #[derive(thiserror::Error, Debug)] @@ -16,12 +19,16 @@ pub enum Error { /// An error during the simulation #[error(transparent)] Simulate(#[from] simulate::Error), + /// An error during hash calculation + #[error(transparent)] + Hash(#[from] hash::Error), } impl Cmd { pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { match self { Cmd::Simulate(cmd) => cmd.run(global_args).await?, + Cmd::Hash(cmd) => cmd.run(global_args)?, }; Ok(()) }