From 5254c13aaa86a4ffa034882199433b82e2e457d6 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Fri, 16 Aug 2024 13:39:26 -0400 Subject: [PATCH 01/18] Print status messages when pulling quickstart image (#1546) * Print status messages when pulling quickstart image * Logging when there is no internet connection --------- Co-authored-by: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> --- .../src/commands/network/container/start.rs | 39 +++++++++++++------ cmd/soroban-cli/src/print.rs | 1 + 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/cmd/soroban-cli/src/commands/network/container/start.rs b/cmd/soroban-cli/src/commands/network/container/start.rs index 73c80893dc..a4d840f0fe 100644 --- a/cmd/soroban-cli/src/commands/network/container/start.rs +++ b/cmd/soroban-cli/src/commands/network/container/start.rs @@ -82,17 +82,34 @@ impl Runner { let docker = self.args.container_args.connect_to_docker().await?; let image = self.get_image_name(); - docker - .create_image( - Some(CreateImageOptions { - from_image: image.clone(), - ..Default::default() - }), - None, - None, - ) - .try_collect::>() - .await?; + let mut stream = docker.create_image( + Some(CreateImageOptions { + from_image: image.clone(), + ..Default::default() + }), + None, + None, + ); + + while let Some(result) = stream.try_next().await.transpose() { + if let Ok(item) = result { + if let Some(status) = item.status { + if status.contains("Pulling from") + || status.contains("Digest") + || status.contains("Status") + { + self.print.infoln(status); + } + } + } else { + self.print + .warnln(format!("Failed to fetch image: {image}.")); + self.print.warnln( + "Attempting to start local quickstart image. The image may be out-of-date.", + ); + break; + } + } let config = Config { image: Some(image), diff --git a/cmd/soroban-cli/src/print.rs b/cmd/soroban-cli/src/print.rs index fd1eb3d586..ca95f652c0 100644 --- a/cmd/soroban-cli/src/print.rs +++ b/cmd/soroban-cli/src/print.rs @@ -88,3 +88,4 @@ create_print_functions!(info, infoln, "ℹ️"); create_print_functions!(link, linkln, "🔗"); create_print_functions!(save, saveln, "💾"); create_print_functions!(search, searchln, "🔎"); +create_print_functions!(warn, warnln, "⚠️"); From 9ad9e8ecce7237433fb337d287318846368914f7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 20 Aug 2024 20:57:38 +0000 Subject: [PATCH 02/18] Bump version to 21.4.0 (#1555) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- Cargo.lock | 28 +++++++++---------- Cargo.toml | 10 +++---- cmd/crates/soroban-test/Cargo.toml | 2 +- .../tests/fixtures/hello/Cargo.toml | 2 +- .../test-wasms/custom_account/Cargo.toml | 2 +- .../test-wasms/custom_type/Cargo.toml | 2 +- .../test-wasms/hello_world/Cargo.toml | 2 +- cmd/soroban-cli/Cargo.toml | 2 +- cmd/stellar-cli/Cargo.toml | 2 +- 9 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d59c1baa83..6a42435327 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4675,7 +4675,7 @@ dependencies = [ [[package]] name = "soroban-cli" -version = "21.3.0" +version = "21.4.0" dependencies = [ "assert_cmd", "assert_fs", @@ -4837,7 +4837,7 @@ dependencies = [ [[package]] name = "soroban-hello" -version = "21.3.0" +version = "21.4.0" [[package]] name = "soroban-ledger-snapshot" @@ -4907,7 +4907,7 @@ dependencies = [ [[package]] name = "soroban-spec-json" -version = "21.3.0" +version = "21.4.0" dependencies = [ "pretty_assertions", "serde", @@ -4937,7 +4937,7 @@ dependencies = [ [[package]] name = "soroban-spec-tools" -version = "21.3.0" +version = "21.4.0" dependencies = [ "base64 0.21.7", "ethnum", @@ -4956,7 +4956,7 @@ dependencies = [ [[package]] name = "soroban-spec-typescript" -version = "21.3.0" +version = "21.4.0" dependencies = [ "base64 0.21.7", "heck 0.4.1", @@ -4977,7 +4977,7 @@ dependencies = [ [[package]] name = "soroban-test" -version = "21.3.0" +version = "21.4.0" dependencies = [ "assert_cmd", "assert_fs", @@ -5049,14 +5049,14 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stellar-cli" -version = "21.3.0" +version = "21.4.0" dependencies = [ "soroban-cli", ] [[package]] name = "stellar-ledger" -version = "21.3.0" +version = "21.4.0" dependencies = [ "async-trait", "bollard", @@ -5362,35 +5362,35 @@ dependencies = [ [[package]] name = "test_custom_account" -version = "21.3.0" +version = "21.4.0" dependencies = [ "soroban-sdk", ] [[package]] name = "test_custom_types" -version = "21.3.0" +version = "21.4.0" dependencies = [ "soroban-sdk", ] [[package]] name = "test_hello_world" -version = "21.3.0" +version = "21.4.0" dependencies = [ "soroban-sdk", ] [[package]] name = "test_swap" -version = "21.3.0" +version = "21.4.0" dependencies = [ "soroban-sdk", ] [[package]] name = "test_token" -version = "21.3.0" +version = "21.4.0" dependencies = [ "soroban-sdk", "soroban-token-sdk", @@ -5398,7 +5398,7 @@ dependencies = [ [[package]] name = "test_udt" -version = "21.3.0" +version = "21.4.0" dependencies = [ "soroban-sdk", ] diff --git a/Cargo.toml b/Cargo.toml index c4db83a064..c674646658 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.3.0" +version = "21.4.0" 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.3.0" +version = "=21.4.0" path = "./cmd/crates/soroban-spec-json" [workspace.dependencies.soroban-spec-typescript] -version = "21.3.0" +version = "21.4.0" path = "./cmd/crates/soroban-spec-typescript" [workspace.dependencies.soroban-spec-tools] -version = "21.3.0" +version = "21.4.0" 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.3.0" +version = "=21.4.0" path = "cmd/soroban-cli" [workspace.dependencies.soroban-rpc] diff --git a/cmd/crates/soroban-test/Cargo.toml b/cmd/crates/soroban-test/Cargo.toml index 48f94ddcae..ec6c78807b 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.3.0" +version = "21.4.0" 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 0b28f4ef11..8e6c049d7c 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.3.0" +version = "21.4.0" 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 bfb870d35f..4c7c1de988 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.3.0" +version = "21.4.0" 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 8eb1875c6d..7096eb1fd2 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.3.0" +version = "21.4.0" 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 dcdae0280c..65c3e6e9b4 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.3.0" +version = "21.4.0" authors = ["Stellar Development Foundation "] license = "Apache-2.0" edition = "2021" diff --git a/cmd/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml index 05d60bec5b..12502aa00f 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.3.0" +version = "21.4.0" edition = "2021" rust-version.workspace = true autobins = false diff --git a/cmd/stellar-cli/Cargo.toml b/cmd/stellar-cli/Cargo.toml index ab87541992..f430a23ee2 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.3.0" +version = "21.4.0" edition = "2021" rust-version.workspace = true autobins = false From b1c5d5e2a54f701bcf36930317a8b2a919e77efd Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 22 Aug 2024 16:52:31 -0400 Subject: [PATCH 03/18] fix: use print.infoln, etc (#1558) --- .../src/commands/contract/deploy/wasm.rs | 14 +++++++------- cmd/soroban-cli/src/commands/contract/install.rs | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index b047f7dc98..74491249f6 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/install.rs b/cmd/soroban-cli/src/commands/contract/install.rs index 8474c96218..98c8662713 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?) From c0e61aa0e31e88fa26da42efb6d45f8e1c7049c5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 25 Aug 2024 23:14:50 -0700 Subject: [PATCH 04/18] Bump version to 21.4.1 (#1559) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- Cargo.lock | 28 +++++++++---------- Cargo.toml | 10 +++---- cmd/crates/soroban-test/Cargo.toml | 2 +- .../tests/fixtures/hello/Cargo.toml | 2 +- .../test-wasms/custom_account/Cargo.toml | 2 +- .../test-wasms/custom_type/Cargo.toml | 2 +- .../test-wasms/hello_world/Cargo.toml | 2 +- cmd/soroban-cli/Cargo.toml | 2 +- cmd/stellar-cli/Cargo.toml | 2 +- 9 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6a42435327..e60e34613c 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 c674646658..d58335a211 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/cmd/crates/soroban-test/Cargo.toml b/cmd/crates/soroban-test/Cargo.toml index ec6c78807b..643db6ec31 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 8e6c049d7c..64d861184d 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 4c7c1de988..db31464f2a 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 7096eb1fd2..61b1cd4c64 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 65c3e6e9b4..c2bb8ce802 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/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml index 12502aa00f..3ffb7466ea 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/stellar-cli/Cargo.toml b/cmd/stellar-cli/Cargo.toml index f430a23ee2..40fa29dcfe 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 From 05e362bd5fa76d0e5c90ce0f5a4996881cd72cc3 Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Mon, 26 Aug 2024 19:07:11 -0700 Subject: [PATCH 05/18] Add `contract alias remove`. (#1563) --- FULL_HELP_DOCS.md | 33 ++++++++++ .../src/commands/contract/alias.rs | 24 +++++++ .../src/commands/contract/alias/remove.rs | 62 +++++++++++++++++++ cmd/soroban-cli/src/commands/contract/mod.rs | 10 +++ cmd/soroban-cli/src/config/locator.rs | 25 ++++++++ 5 files changed, 154 insertions(+) create mode 100644 cmd/soroban-cli/src/commands/contract/alias.rs create mode 100644 cmd/soroban-cli/src/commands/contract/alias/remove.rs diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index c13e002c14..e61227444c 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,38 @@ Deploy builtin Soroban Asset Contract +## `stellar contract alias` + +Utilities to manage contract aliases + +**Usage:** `stellar contract alias ` + +###### **Subcommands:** + +* `remove` — Remove contract 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 bindings` Generate code client bindings for a contract 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 0000000000..4dedd35ffd --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/alias.rs @@ -0,0 +1,24 @@ +use crate::commands::global; + +pub mod remove; + +#[derive(Debug, clap::Subcommand)] +pub enum Cmd { + /// Remove contract alias + Remove(remove::Cmd), +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Remove(#[from] remove::Error), +} + +impl Cmd { + pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { + match &self { + Cmd::Remove(remove) => remove.run(global_args).await?, + } + 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 0000000000..27c92e6ea9 --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/alias/remove.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 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}`")] + NoContract { alias: String }, +} + +impl Cmd { + #[allow(clippy::unused_async)] + pub async 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(), + }); + }; + + 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/mod.rs b/cmd/soroban-cli/src/commands/contract/mod.rs index afd49c6abb..45fd00b96d 100644 --- a/cmd/soroban-cli/src/commands/contract/mod.rs +++ b/cmd/soroban-cli/src/commands/contract/mod.rs @@ -1,3 +1,4 @@ +pub mod alias; pub mod asset; pub mod bindings; pub mod build; @@ -21,6 +22,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 +88,9 @@ pub enum Error { #[error(transparent)] Asset(#[from] asset::Error), + #[error(transparent)] + Alias(#[from] alias::Error), + #[error(transparent)] Bindings(#[from] bindings::Error), @@ -132,6 +141,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).await?, 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/config/locator.rs b/cmd/soroban-cli/src/config/locator.rs index 9aefdd9e01..97386cb7db 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, From 1fdc4de33f600a92faffec5ca9bd1e2a0f1a526a Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 27 Aug 2024 03:18:57 -0400 Subject: [PATCH 06/18] Apply suggestions from code review Co-authored-by: Gleb --- cmd/soroban-cli/src/commands/contract/invoke.rs | 1 + cmd/soroban-cli/src/config/sign_with.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 6f5ae326b0..d8f1c4e87d 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -233,6 +233,7 @@ impl Cmd { let sc_val = if let Some(mut val) = matches_.get_raw(&name) { let mut s = val.next().unwrap().to_string_lossy().to_string(); if matches!(input.type_, ScSpecTypeDef::Address) { + // Currently we only support local keys, same as input for --sign-with-key`, for signing auth entries. if let Ok(signer_) = config .sign_with .locator diff --git a/cmd/soroban-cli/src/config/sign_with.rs b/cmd/soroban-cli/src/config/sign_with.rs index ae22465ce3..c31580eef0 100644 --- a/cmd/soroban-cli/src/config/sign_with.rs +++ b/cmd/soroban-cli/src/config/sign_with.rs @@ -31,7 +31,7 @@ pub enum Error { #[derive(Debug, clap::Args, Clone, Default)] #[group(skip)] pub struct Args { - /// Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). + /// Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path. #[arg( long, conflicts_with = "sign_with_lab", From b853484f278a07476e4dcbd12e504469ffec909c Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 27 Aug 2024 13:52:29 -0400 Subject: [PATCH 07/18] fix: docs --- FULL_HELP_DOCS.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 8fe6564832..76c60ad3e4 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -117,7 +117,7 @@ Get Id of builtin Soroban Asset Contract. Deprecated, use `stellar contract id a * `--asset ` — ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -138,7 +138,7 @@ Deploy builtin Soroban Asset Contract * `--asset ` — ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -307,7 +307,7 @@ If no keys are specified the contract itself is extended. Temporary * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -337,7 +337,7 @@ Deploy a wasm contract * `--wasm-hash ` — Hash of the already installed/deployed WASM file * `--salt ` — Custom salt 32-byte salt for the token id * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -400,7 +400,7 @@ Deploy builtin Soroban Asset Contract * `--asset ` — ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -421,7 +421,7 @@ Deploy normal Wasm Contract * `--salt ` — ID of the Soroban contract * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -621,7 +621,7 @@ Install a WASM file to the ledger without creating a contract instance ###### **Options:** * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -662,7 +662,7 @@ stellar contract invoke ... -- --help * `--id ` — Contract ID to invoke * `--is-view` — View the result simulating and do not sign and submit transaction. Deprecated use `--send=no` * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -745,7 +745,7 @@ Print the current value of a contract-data ledger entry Temporary * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -784,7 +784,7 @@ If no keys are specificed the contract itself is restored. * `--ledgers-to-extend ` — Number of ledgers to extend the entry * `--ttl-ledger-only` — Only print the new Time To Live ledger * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -1271,7 +1271,7 @@ Simulate a transaction envelope from stdin ###### **Options:** * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -1304,7 +1304,7 @@ Sign a transaction envolope appending the signature to the envelope ###### **Options:** -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint From a781e44671ea857750cd7eea87f376a8e6c1d314 Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Tue, 27 Aug 2024 14:22:33 -0700 Subject: [PATCH 08/18] Add `contract alias add`. (#1565) --- FULL_HELP_DOCS.md | 23 +++++ .../src/commands/contract/alias.rs | 12 ++- .../src/commands/contract/alias/add.rs | 84 +++++++++++++++++++ .../src/commands/contract/alias/remove.rs | 3 +- cmd/soroban-cli/src/commands/contract/mod.rs | 2 +- 5 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 cmd/soroban-cli/src/commands/contract/alias/add.rs diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index e61227444c..498260c2a9 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -161,6 +161,7 @@ Utilities to manage contract aliases ###### **Subcommands:** * `remove` — Remove contract alias +* `add` — Add contract alias @@ -184,6 +185,28 @@ Remove contract alias +## `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 bindings` Generate code client bindings for a contract diff --git a/cmd/soroban-cli/src/commands/contract/alias.rs b/cmd/soroban-cli/src/commands/contract/alias.rs index 4dedd35ffd..c35d5e418d 100644 --- a/cmd/soroban-cli/src/commands/contract/alias.rs +++ b/cmd/soroban-cli/src/commands/contract/alias.rs @@ -1,23 +1,31 @@ use crate::commands::global; +pub mod add; pub mod remove; #[derive(Debug, clap::Subcommand)] pub enum Cmd { /// Remove contract alias Remove(remove::Cmd), + + /// Add contract alias + Add(add::Cmd), } #[derive(thiserror::Error, Debug)] pub enum Error { #[error(transparent)] Remove(#[from] remove::Error), + + #[error(transparent)] + Add(#[from] add::Error), } impl Cmd { - pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { + pub fn run(&self, global_args: &global::Args) -> Result<(), Error> { match &self { - Cmd::Remove(remove) => remove.run(global_args).await?, + Cmd::Remove(remove) => remove.run(global_args)?, + Cmd::Add(add) => add.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 0000000000..d5c304452a --- /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 index 27c92e6ea9..d1166bdbf2 100644 --- a/cmd/soroban-cli/src/commands/contract/alias/remove.rs +++ b/cmd/soroban-cli/src/commands/contract/alias/remove.rs @@ -32,8 +32,7 @@ pub enum Error { } impl Cmd { - #[allow(clippy::unused_async)] - pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { + 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)?; diff --git a/cmd/soroban-cli/src/commands/contract/mod.rs b/cmd/soroban-cli/src/commands/contract/mod.rs index 45fd00b96d..3a3ea0b502 100644 --- a/cmd/soroban-cli/src/commands/contract/mod.rs +++ b/cmd/soroban-cli/src/commands/contract/mod.rs @@ -141,7 +141,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).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?, From 5b4776cd217fb033afbf24e0eadf5430b72e5030 Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Tue, 27 Aug 2024 15:58:26 -0700 Subject: [PATCH 09/18] Add `contract alias show`. (#1567) --- FULL_HELP_DOCS.md | 21 +++++++ .../src/commands/contract/alias.rs | 8 +++ .../src/commands/contract/alias/remove.rs | 8 ++- .../src/commands/contract/alias/show.rs | 62 +++++++++++++++++++ 4 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 cmd/soroban-cli/src/commands/contract/alias/show.rs diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 498260c2a9..57a4ef6926 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -162,6 +162,7 @@ Utilities to manage contract aliases * `remove` — Remove contract alias * `add` — Add contract alias +* `show` — Show the contract id associated with a given alias @@ -207,6 +208,26 @@ Add contract 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/soroban-cli/src/commands/contract/alias.rs b/cmd/soroban-cli/src/commands/contract/alias.rs index c35d5e418d..bea808cb47 100644 --- a/cmd/soroban-cli/src/commands/contract/alias.rs +++ b/cmd/soroban-cli/src/commands/contract/alias.rs @@ -2,6 +2,7 @@ use crate::commands::global; pub mod add; pub mod remove; +pub mod show; #[derive(Debug, clap::Subcommand)] pub enum Cmd { @@ -10,6 +11,9 @@ pub enum Cmd { /// Add contract alias Add(add::Cmd), + + /// Show the contract id associated with a given alias + Show(show::Cmd), } #[derive(thiserror::Error, Debug)] @@ -19,6 +23,9 @@ pub enum Error { #[error(transparent)] Add(#[from] add::Error), + + #[error(transparent)] + Show(#[from] show::Error), } impl Cmd { @@ -26,6 +33,7 @@ impl Cmd { 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/remove.rs b/cmd/soroban-cli/src/commands/contract/alias/remove.rs index d1166bdbf2..7a41905958 100644 --- a/cmd/soroban-cli/src/commands/contract/alias/remove.rs +++ b/cmd/soroban-cli/src/commands/contract/alias/remove.rs @@ -27,8 +27,11 @@ pub enum Error { #[error(transparent)] Network(#[from] network::Error), - #[error("no contract found with alias `{alias}`")] - NoContract { alias: String }, + #[error("no contract found with alias '{alias}' for network '{network_passphrase}'")] + NoContract { + alias: String, + network_passphrase: String, + }, } impl Cmd { @@ -44,6 +47,7 @@ impl Cmd { else { return Err(Error::NoContract { alias: alias.into(), + network_passphrase: network_passphrase.into(), }); }; 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 0000000000..fc233b6130 --- /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(), + }) + } + } +} From f7b007ee3b6a352feb3652fd5fec250979fb6233 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Wed, 28 Aug 2024 17:24:57 -0400 Subject: [PATCH 10/18] chore: move custom arg parsing to separate module for reuse (#1569) Technically this is a breaking change because of the Error type change, but not from CLI perspective --- cmd/crates/soroban-test/tests/it/help.rs | 12 +- .../src/commands/contract/arg_parsing.rs | 245 +++++++++++++++++ .../src/commands/contract/invoke.rs | 255 ++---------------- cmd/soroban-cli/src/commands/contract/mod.rs | 1 + 4 files changed, 270 insertions(+), 243 deletions(-) create mode 100644 cmd/soroban-cli/src/commands/contract/arg_parsing.rs diff --git a/cmd/crates/soroban-test/tests/it/help.rs b/cmd/crates/soroban-test/tests/it/help.rs index a66c449ed3..ef84a361b6 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/src/commands/contract/arg_parsing.rs b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs new file mode 100644 index 0000000000..4a8af47e29 --- /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/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 6fcfb1e315..293867c760 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 3a3ea0b502..9d574335c0 100644 --- a/cmd/soroban-cli/src/commands/contract/mod.rs +++ b/cmd/soroban-cli/src/commands/contract/mod.rs @@ -1,4 +1,5 @@ pub mod alias; +pub mod arg_parsing; pub mod asset; pub mod bindings; pub mod build; From 34e8a5d2b0154e22b5d1462576205f7fdf92382b Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 29 Aug 2024 05:43:45 -0400 Subject: [PATCH 11/18] fix: only resolve contract alias if needed (#1575) --- .../src/commands/contract/extend.rs | 6 +----- cmd/soroban-cli/src/commands/contract/read.rs | 6 +----- .../src/commands/contract/restore.rs | 6 +----- cmd/soroban-cli/src/key.rs | 20 +++++++++++++++---- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/extend.rs b/cmd/soroban-cli/src/commands/contract/extend.rs index 03c7c70cf2..a0e563e58f 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/read.rs b/cmd/soroban-cli/src/commands/contract/read.rs index bef3f37377..3cb253bb1e 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 192af3140a..e3e8e65ae2 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/key.rs b/cmd/soroban-cli/src/key.rs index 8852959363..b4fd358aa6 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() From b4f937857601f886ce25ff937ec4eebcf1302402 Mon Sep 17 00:00:00 2001 From: Chad Ostrowski <221614+chadoh@users.noreply.github.com> Date: Fri, 30 Aug 2024 01:56:12 -0400 Subject: [PATCH 12/18] chore: remove unused `enum FrontendTemplate` (#1573) * chore: remove unused `enum FrontendTemplate` It was marked as `pub` so the checker didn't tell us it was unused. But it's not used anywhere else in the codebase. --------- Co-authored-by: Willem Wyndham --- cmd/soroban-cli/src/commands/contract/init.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/init.rs b/cmd/soroban-cli/src/commands/contract/init.rs index a83504d966..e38f27d7e4 100644 --- a/cmd/soroban-cli/src/commands/contract/init.rs +++ b/cmd/soroban-cli/src/commands/contract/init.rs @@ -1,6 +1,6 @@ use clap::{ builder::{PossibleValue, PossibleValuesParser, ValueParser}, - Parser, ValueEnum, + Parser, }; use gix::{clone, create, open, progress, remote}; use rust_embed::RustEmbed; @@ -26,12 +26,6 @@ const GITHUB_URL: &str = "https://github.com"; const WITH_EXAMPLE_LONG_HELP_TEXT: &str = "An optional flag to specify Soroban example contracts to include. A hello-world contract will be included by default."; -#[derive(Clone, Debug, ValueEnum, PartialEq)] -pub enum FrontendTemplate { - Astro, - None, -} - #[derive(Parser, Debug, Clone)] #[group(skip)] pub struct Cmd { From 104089aaa9e65a1501e850ba73d632315668e25f Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Fri, 30 Aug 2024 08:12:56 -0400 Subject: [PATCH 13/18] fix: remove unneeded print statement (#1554) --- cmd/soroban-cli/src/commands/events.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/cmd/soroban-cli/src/commands/events.rs b/cmd/soroban-cli/src/commands/events.rs index a755c33d04..5e2bda66f8 100644 --- a/cmd/soroban-cli/src/commands/events.rs +++ b/cmd/soroban-cli/src/commands/events.rs @@ -176,8 +176,6 @@ impl Cmd { OutputFormat::Pretty => event.pretty_print()?, } } - println!("Latest Ledger: {}", response.latest_ledger); - Ok(()) } From d11c7e0750311d149d5f85d5e44ab6e4f1f2e6cb Mon Sep 17 00:00:00 2001 From: Chad Ostrowski <221614+chadoh@users.noreply.github.com> Date: Fri, 30 Aug 2024 08:20:59 -0400 Subject: [PATCH 14/18] fix(init): use String, not Symbol, in hello_world (#1577) Updates `hello_world` to match the version that has been in `soroban-examples` since https://github.com/stellar/soroban-examples/pull/314 --- .../contracts/hello_world/src/lib.rs | 6 +++--- .../contracts/hello_world/src/test.rs | 10 +++++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/cmd/soroban-cli/src/utils/contract-init-template/contracts/hello_world/src/lib.rs b/cmd/soroban-cli/src/utils/contract-init-template/contracts/hello_world/src/lib.rs index fb23e50556..f3eb78a360 100644 --- a/cmd/soroban-cli/src/utils/contract-init-template/contracts/hello_world/src/lib.rs +++ b/cmd/soroban-cli/src/utils/contract-init-template/contracts/hello_world/src/lib.rs @@ -1,13 +1,13 @@ #![no_std] -use soroban_sdk::{contract, contractimpl, symbol_short, vec, Env, Symbol, Vec}; +use soroban_sdk::{contract, contractimpl, vec, Env, String, Vec}; #[contract] pub struct HelloContract; #[contractimpl] impl HelloContract { - pub fn hello(env: Env, to: Symbol) -> Vec { - vec![&env, symbol_short!("Hello"), to] + pub fn hello(env: Env, to: String) -> Vec { + vec![&env, String::from_str(&env, "Hello"), to] } } diff --git a/cmd/soroban-cli/src/utils/contract-init-template/contracts/hello_world/src/test.rs b/cmd/soroban-cli/src/utils/contract-init-template/contracts/hello_world/src/test.rs index e72c6bb9fa..4b250446ae 100644 --- a/cmd/soroban-cli/src/utils/contract-init-template/contracts/hello_world/src/test.rs +++ b/cmd/soroban-cli/src/utils/contract-init-template/contracts/hello_world/src/test.rs @@ -1,7 +1,7 @@ #![cfg(test)] use super::*; -use soroban_sdk::{symbol_short, vec, Env}; +use soroban_sdk::{vec, Env, String}; #[test] fn test() { @@ -9,9 +9,13 @@ fn test() { let contract_id = env.register_contract(None, HelloContract); let client = HelloContractClient::new(&env, &contract_id); - let words = client.hello(&symbol_short!("Dev")); + let words = client.hello(&String::from_str(&env, "Dev")); assert_eq!( words, - vec![&env, symbol_short!("Hello"), symbol_short!("Dev"),] + vec![ + &env, + String::from_str(&env, "Hello"), + String::from_str(&env, "Dev"), + ] ); } From 79a3aa1e50aa0a882e4bec160c005a83595aa7de Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Fri, 30 Aug 2024 10:14:28 -0400 Subject: [PATCH 15/18] Feat/use rich printer for init (#1548) --- cmd/soroban-cli/src/commands/contract/init.rs | 813 ++++++++++-------- cmd/soroban-cli/src/commands/contract/mod.rs | 2 +- cmd/soroban-cli/src/print.rs | 1 + 3 files changed, 446 insertions(+), 370 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/init.rs b/cmd/soroban-cli/src/commands/contract/init.rs index e38f27d7e4..0d451f6fe4 100644 --- a/cmd/soroban-cli/src/commands/contract/init.rs +++ b/cmd/soroban-cli/src/commands/contract/init.rs @@ -14,13 +14,15 @@ use std::{ }, io::{self, Read, Write}, num::NonZeroU32, - path::Path, + path::{Path, PathBuf}, str, sync::atomic::AtomicBool, }; use toml_edit::{Document, TomlError}; use ureq::get; +use crate::{commands::global, print}; + const SOROBAN_EXAMPLES_URL: &str = "https://github.com/stellar/soroban-examples.git"; const GITHUB_URL: &str = "https://github.com"; const WITH_EXAMPLE_LONG_HELP_TEXT: &str = @@ -81,380 +83,415 @@ pub enum Error { impl Cmd { #[allow(clippy::unused_self)] - pub fn run(&self) -> Result<(), Error> { - println!("ℹ️ Initializing project at {}", self.project_path); - let project_path = Path::new(&self.project_path); - - init( - project_path, - &self.frontend_template, - &self.with_example, - self.overwrite, - )?; + pub fn run(&self, global_args: &global::Args) -> Result<(), Error> { + let runner = Runner { + args: self.clone(), + print: print::Print::new(global_args.quiet), + }; - Ok(()) + runner.run() } } #[derive(RustEmbed)] #[folder = "src/utils/contract-init-template"] struct TemplateFiles; +struct Runner { + args: Cmd, + print: print::Print, +} -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, 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."); - return Ok(()); - } - - if !frontend_template.is_empty() { - // create a temp dir for the template repo - let fe_template_dir = tempfile::tempdir().map_err(|e| { - eprintln!("Error creating temp dir for frontend template"); - e - })?; - - // clone the template repo into the temp dir - 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, overwrite)?; - } +impl Runner { + fn run(&self) -> Result<(), Error> { + let project_path = PathBuf::from(&self.args.project_path); + self.print + .infoln(format!("Initializing project at {project_path:?}")); - // if there are --with-example flags, include the example contracts - if include_example_contracts(with_examples) { - // create an examples temp dir - let examples_dir = tempfile::tempdir().map_err(|e| { - eprintln!("Error creating temp dir for soroban-examples"); + // 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| { + self.print + .errorln("Error creating new project directory: {project_path:?}"); e })?; + self.copy_template_files()?; - // clone the soroban-examples repo into the temp dir - clone_repo(SOROBAN_EXAMPLES_URL, examples_dir.path())?; + if !Self::check_internet_connection() { + self.print.warnln("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."); + return Ok(()); + } - // copy the example contracts into the project - copy_example_contracts(examples_dir.path(), project_path, with_examples, overwrite)?; - } + if !self.args.frontend_template.is_empty() { + // create a temp dir for the template repo + let fe_template_dir = tempfile::tempdir().map_err(|e| { + self.print + .errorln("Error creating temp dir for frontend template"); + e + })?; - Ok(()) -} + // clone the template repo into the temp dir + self.clone_repo(&self.args.frontend_template, fe_template_dir.path())?; -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()); - let exists = file_exists(&to); - if exists && !overwrite { - println!( - "ℹ️ Skipped creating {} as it already exists", - &to.to_string_lossy() - ); - continue; + // copy the frontend template files into the project + self.copy_frontend_files(fe_template_dir.path(), &project_path)?; } - create_dir_all(to.parent().unwrap()).map_err(|e| { - eprintln!("Error creating directory path for: {to:?}"); - e - })?; - let Some(file) = TemplateFiles::get(item.as_ref()) else { - println!("⚠️ Failed to read file: {}", item.as_ref()); - continue; - }; + // if there are --with-example flags, include the example contracts + if self.include_example_contracts() { + // create an examples temp dir + let examples_dir = tempfile::tempdir().map_err(|e| { + self.print + .errorln("Error creating temp dir for soroban-examples"); + e + })?; - let file_contents = std::str::from_utf8(file.data.as_ref()).map_err(|e| { - eprintln!( - "Error converting file contents in {:?} to string", - item.as_ref() - ); - e - })?; + // clone the soroban-examples repo into the temp dir + self.clone_repo(SOROBAN_EXAMPLES_URL, examples_dir.path())?; - // We need to include the Cargo.toml file as Cargo.toml.removeextension in the template so that it will be included the package. This is making sure that the Cargo file is written as Cargo.toml in the new project. This is a workaround for this issue: https://github.com/rust-lang/cargo/issues/8597. - let item_path = Path::new(item.as_ref()); - if item_path.file_name().unwrap() == "Cargo.toml.removeextension" { - let item_parent_path = item_path.parent().unwrap(); - to = project_path.join(item_parent_path).join("Cargo.toml"); + // copy the example contracts into the project + self.copy_example_contracts( + examples_dir.path(), + &project_path, + &self.args.with_example, + )?; } - 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 - })?; + Ok(()) } - Ok(()) -} -fn copy_contents(from: &Path, to: &Path, overwrite: bool) -> Result<(), Error> { - let contents_to_exclude_from_copy = [ - ".git", - ".github", - "Makefile", - ".vscode", - "target", - "Cargo.lock", - ]; - for entry in read_dir(from).map_err(|e| { - eprintln!("Error reading directory: {from:?}"); - e - })? { - let entry = entry.map_err(|e| { - eprintln!("Error reading entry in directory: {from:?}"); - e - })?; - let path = entry.path(); - let entry_name = entry.file_name().to_string_lossy().to_string(); - let new_path = to.join(&entry_name); + fn copy_template_files(&self) -> Result<(), Error> { + let project_path = Path::new(&self.args.project_path); + for item in TemplateFiles::iter() { + let mut to = project_path.join(item.as_ref()); + let exists = Self::file_exists(&to); + if exists && !self.args.overwrite { + self.print + .infoln(format!("Skipped creating {to:?} as it already exists")); + continue; + } - if contents_to_exclude_from_copy.contains(&entry_name.as_str()) { - continue; - } + create_dir_all(to.parent().unwrap()).map_err(|e| { + self.print + .errorln(format!("Error creating directory path for: {to:?}")); + e + })?; - if path.is_dir() { - create_dir_all(&new_path).map_err(|e| { - eprintln!("Error creating directory: {new_path:?}"); + let Some(file) = TemplateFiles::get(item.as_ref()) else { + self.print + .warnln(format!("Failed to read file: {}", item.as_ref())); + continue; + }; + + let file_contents = std::str::from_utf8(file.data.as_ref()).map_err(|e| { + self.print.errorln(format!( + "Error converting file contents in {:?} to string", + item.as_ref() + )); e })?; - copy_contents(&path, &new_path, overwrite)?; - } else { - 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)?; - } - if overwrite && !append { - println!("🔄 Overwriting {new_path_str}"); - } else { - println!("ℹ️ Skipped creating {new_path_str} as it already exists"); - continue; - } + // We need to include the Cargo.toml file as Cargo.toml.removeextension in the template so that it will be included the package. This is making sure that the Cargo file is written as Cargo.toml in the new project. This is a workaround for this issue: https://github.com/rust-lang/cargo/issues/8597. + let item_path = Path::new(item.as_ref()); + if item_path.file_name().unwrap() == "Cargo.toml.removeextension" { + let item_parent_path = item_path.parent().unwrap(); + to = project_path.join(item_parent_path).join("Cargo.toml"); + } + + if exists { + self.print + .plusln(format!("Writing {to:?} (overwriting existing file)")); } else { - println!("➕ Writing {new_path_str}"); + self.print.plusln(format!("Writing {to:?}")); } - copy(&path, &new_path).map_err(|e| { - eprintln!( - "Error copying from {:?} to {:?}", - path.to_string_lossy(), - new_path - ); + write(&to, file_contents).map_err(|e| { + self.print.errorln(format!("Error writing file: {to:?}")); e })?; } + Ok(()) } - Ok(()) -} - -fn file_exists(file_path: &Path) -> bool { - metadata(file_path) - .as_ref() - .map(Metadata::is_file) - .unwrap_or(false) -} + fn copy_contents(&self, from: &Path, to: &Path) -> Result<(), Error> { + let contents_to_exclude_from_copy = [ + ".git", + ".github", + "Makefile", + ".vscode", + "target", + "Cargo.lock", + ]; + for entry in read_dir(from).map_err(|e| { + self.print + .errorln(format!("Error reading directory: {from:?}")); + e + })? { + let entry = entry.map_err(|e| { + self.print + .errorln(format!("Error reading entry in directory: {from:?}")); + e + })?; + let path = entry.path(); + let entry_name = entry.file_name().to_string_lossy().to_string(); + let new_path = to.join(&entry_name); -fn include_example_contracts(contracts: &[String]) -> bool { - !contracts.is_empty() -} + if contents_to_exclude_from_copy.contains(&entry_name.as_str()) { + continue; + } -fn clone_repo(from_url: &str, to_path: &Path) -> Result<(), Error> { - let mut prepare = clone::PrepareFetch::new( - from_url, - to_path, - create::Kind::WithWorktree, - create::Options { - destination_must_be_empty: false, - fs_capabilities: None, - }, - open::Options::isolated(), - ) - .map_err(|e| { - eprintln!("Error preparing fetch for {from_url:?}"); - Box::new(e) - })? - .with_shallow(remote::fetch::Shallow::DepthAtRemote( - NonZeroU32::new(1).unwrap(), - )); - - let (mut checkout, _outcome) = prepare - .fetch_then_checkout(progress::Discard, &AtomicBool::new(false)) - .map_err(|e| { - eprintln!("Error calling fetch_then_checkout with {from_url:?}"); - Box::new(e) - })?; + if path.is_dir() { + create_dir_all(&new_path).map_err(|e| { + self.print + .errorln(format!("Error creating directory: {new_path:?}")); + e + })?; + self.copy_contents(&path, &new_path)?; + } else { + let exists = Self::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 { + self.append_contents(&path, &new_path)?; + } + + if self.args.overwrite && !append { + self.print.plusln(format!( + "Writing {new_path_str} (overwriting existing file)" + )); + } else { + self.print.infoln(format!( + "Skipped creating {new_path_str} as it already exists" + )); + continue; + } + } else { + self.print.plus(format!("Writing {new_path_str}")); + } + copy(&path, &new_path).map_err(|e| { + self.print.errorln(format!( + "Error copying from {:?} to {:?}", + path.to_string_lossy(), + new_path + )); + e + })?; + } + } - let (_repo, _outcome) = checkout - .main_worktree(progress::Discard, &AtomicBool::new(false)) - .map_err(|e| { - eprintln!("Error calling main_worktree for {from_url:?}"); - e - })?; + Ok(()) + } - Ok(()) -} + fn file_exists(file_path: &Path) -> bool { + metadata(file_path) + .as_ref() + .map(Metadata::is_file) + .unwrap_or(false) + } -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}"); - let contract_as_string = contract.to_string(); - let contract_path = Path::new(&contract_as_string); - let from_contract_path = from.join(contract_path); - let to_contract_path = project_contracts_path.join(contract_path); - create_dir_all(&to_contract_path).map_err(|e| { - eprintln!("Error creating directory: {contract_path:?}"); - e - })?; + fn check_internet_connection() -> bool { + if let Ok(_req) = get(GITHUB_URL).call() { + return true; + } - copy_contents(&from_contract_path, &to_contract_path, overwrite)?; - edit_contract_cargo_file(&to_contract_path)?; + false } - Ok(()) -} + fn include_example_contracts(&self) -> bool { + !self.args.with_example.is_empty() + } -fn edit_contract_cargo_file(contract_path: &Path) -> Result<(), Error> { - let cargo_path = contract_path.join("Cargo.toml"); - let cargo_toml_str = read_to_string(&cargo_path).map_err(|e| { - eprint!("Error reading Cargo.toml file in: {contract_path:?}"); - e - })?; - - let cargo_toml_str = regex::Regex::new(r#"soroban-sdk = "[^\"]+""#) - .unwrap() - .replace_all( - cargo_toml_str.as_str(), - "soroban-sdk = { workspace = true }", - ); + fn clone_repo(&self, from_url: &str, to_path: &Path) -> Result<(), Error> { + let mut prepare = clone::PrepareFetch::new( + from_url, + to_path, + create::Kind::WithWorktree, + create::Options { + destination_must_be_empty: false, + fs_capabilities: None, + }, + open::Options::isolated(), + ) + .map_err(|e| { + self.print + .errorln(format!("Error preparing fetch for {from_url:?}")); + Box::new(e) + })? + .with_shallow(remote::fetch::Shallow::DepthAtRemote( + NonZeroU32::new(1).unwrap(), + )); + + let (mut checkout, _outcome) = prepare + .fetch_then_checkout(progress::Discard, &AtomicBool::new(false)) + .map_err(|e| { + self.print.errorln(format!( + "Error calling fetch_then_checkout with {from_url:?}" + )); + Box::new(e) + })?; - let cargo_toml_str = regex::Regex::new(r#"soroban-sdk = \{(.*) version = "[^"]+"(.+)}"#) - .unwrap() - .replace_all(&cargo_toml_str, "soroban-sdk = {$1 workspace = true$2}"); + let (_repo, _outcome) = checkout + .main_worktree(progress::Discard, &AtomicBool::new(false)) + .map_err(|e| { + self.print + .errorln(format!("Error calling main_worktree for {from_url:?}")); + e + })?; - let mut doc = cargo_toml_str.parse::().map_err(|e| { - eprintln!("Error parsing Cargo.toml file in: {contract_path:?}"); - e - })?; - doc.remove("profile"); + Ok(()) + } - write(&cargo_path, doc.to_string()).map_err(|e| { - eprintln!("Error writing to Cargo.toml file in: {contract_path:?}"); - e - })?; + fn copy_example_contracts( + &self, + from: &Path, + to: &Path, + contracts: &[String], + ) -> Result<(), Error> { + let project_contracts_path = to.join("contracts"); + for contract in contracts { + self.print + .infoln(format!("Initializing example contract: {contract}")); + let contract_as_string = contract.to_string(); + let contract_path = Path::new(&contract_as_string); + let from_contract_path = from.join(contract_path); + let to_contract_path = project_contracts_path.join(contract_path); + create_dir_all(&to_contract_path).map_err(|e| { + self.print + .errorln(format!("Error creating directory: {contract_path:?}")); + e + })?; - Ok(()) -} + self.copy_contents(&from_contract_path, &to_contract_path)?; + self.edit_contract_cargo_file(&to_contract_path)?; + } -fn copy_frontend_files(from: &Path, to: &Path, overwrite: bool) -> Result<(), Error> { - println!("ℹ️ Initializing with frontend template"); - copy_contents(from, to, overwrite)?; - edit_package_json_files(to) -} + Ok(()) + } -fn edit_package_json_files(project_path: &Path) -> Result<(), Error> { - let package_name = if let Some(name) = project_path.file_name() { - name.to_owned() - } else { - let current_dir = env::current_dir()?; - let file_name = current_dir - .file_name() - .unwrap_or(OsStr::new("soroban-astro-template")) - .to_os_string(); - file_name - }; + fn edit_contract_cargo_file(&self, contract_path: &Path) -> Result<(), Error> { + let cargo_path = contract_path.join("Cargo.toml"); + let cargo_toml_str = read_to_string(&cargo_path).map_err(|e| { + self.print.errorln(format!( + "Error reading Cargo.toml file in: {contract_path:?}" + )); + e + })?; - edit_package_name(project_path, &package_name, "package.json").map_err(|e| { - eprintln!("Error editing package.json file in: {project_path:?}"); - e - })?; - edit_package_name(project_path, &package_name, "package-lock.json") -} + let cargo_toml_str = regex::Regex::new(r#"soroban-sdk = "[^\"]+""#) + .unwrap() + .replace_all( + cargo_toml_str.as_str(), + "soroban-sdk = { workspace = true }", + ); -fn edit_package_name( - project_path: &Path, - package_name: &OsStr, - file_name: &str, -) -> Result<(), Error> { - let file_path = project_path.join(file_name); - let file_contents = read_to_string(&file_path)?; + let cargo_toml_str = regex::Regex::new(r#"soroban-sdk = \{(.*) version = "[^"]+"(.+)}"#) + .unwrap() + .replace_all(&cargo_toml_str, "soroban-sdk = {$1 workspace = true$2}"); - let mut doc: JsonValue = from_str(&file_contents).map_err(|e| { - eprintln!("Error parsing package.json file in: {project_path:?}"); - e - })?; + let mut doc = cargo_toml_str.parse::().map_err(|e| { + self.print.errorln(format!( + "Error parsing Cargo.toml file in: {contract_path:?}" + )); + e + })?; + doc.remove("profile"); - doc["name"] = json!(package_name.to_string_lossy()); + write(&cargo_path, doc.to_string()).map_err(|e| { + self.print.errorln(format!( + "Error writing to Cargo.toml file in: {contract_path:?}" + )); + e + })?; - let formatted_json = to_string_pretty(&doc)?; + Ok(()) + } - write(&file_path, formatted_json)?; + fn copy_frontend_files(&self, from: &Path, to: &Path) -> Result<(), Error> { + self.print.infoln("ℹ️ Initializing with frontend template"); + self.copy_contents(from, to)?; + self.edit_package_json_files(to) + } - Ok(()) -} + fn edit_package_json_files(&self, project_path: &Path) -> Result<(), Error> { + let package_name = if let Some(name) = project_path.file_name() { + name.to_owned() + } else { + let current_dir = env::current_dir()?; + let file_name = current_dir + .file_name() + .unwrap_or(OsStr::new("soroban-astro-template")) + .to_os_string(); + file_name + }; -fn check_internet_connection() -> bool { - if let Ok(_req) = get(GITHUB_URL).call() { - return true; + self.edit_package_name(project_path, &package_name, "package.json") + .map_err(|e| { + self.print.errorln(format!( + "Error editing package.json file in: {project_path:?}" + )); + e + })?; + self.edit_package_name(project_path, &package_name, "package-lock.json") } - false -} + fn edit_package_name( + &self, + project_path: &Path, + package_name: &OsStr, + file_name: &str, + ) -> Result<(), Error> { + let file_path = project_path.join(file_name); + let file_contents = read_to_string(&file_path)?; + + let mut doc: JsonValue = from_str(&file_contents).map_err(|e| { + self.print.errorln(format!( + "Error parsing {file_name} file in: {project_path:?}" + )); + e + })?; + + doc["name"] = json!(package_name.to_string_lossy()); -// Appends the contents of a file to another file, separated by a delimiter -fn append_contents(from: &Path, to: &Path) -> Result<(), Error> { - let mut from_file = File::open(from)?; - let mut from_content = String::new(); - from_file.read_to_string(&mut from_content)?; + let formatted_json = to_string_pretty(&doc)?; - let mut to_file = OpenOptions::new().read(true).append(true).open(to)?; - let mut to_content = String::new(); - to_file.read_to_string(&mut to_content)?; + write(&file_path, formatted_json)?; - let delimiter = get_merged_file_delimiter(to); - // if the to file already contains the delimiter, we don't need to append the contents again - if to_content.contains(&delimiter) { - return Ok(()); + Ok(()) } - to_file.write_all(delimiter.as_bytes())?; - to_file.write_all(from_content.as_bytes())?; + // Appends the contents of a file to another file, separated by a delimiter + fn append_contents(&self, from: &Path, to: &Path) -> Result<(), Error> { + let mut from_file = File::open(from)?; + let mut from_content = String::new(); + from_file.read_to_string(&mut from_content)?; - println!("ℹ️ Merging {} contents", &to.to_string_lossy()); - Ok(()) -} + let mut to_file = OpenOptions::new().read(true).append(true).open(to)?; + let mut to_content = String::new(); + to_file.read_to_string(&mut to_content)?; -fn get_merged_file_delimiter(file_path: &Path) -> String { - let comment = if file_path.to_string_lossy().contains("README.md") { - "---\n".to_string() - } else if file_path.to_string_lossy().contains("gitignore") { - "# The following is from the Frontend Template's .gitignore".to_string() - } else { - String::new() - }; + let delimiter = Self::get_merged_file_delimiter(to); + // if the to file already contains the delimiter, we don't need to append the contents again + if to_content.contains(&delimiter) { + return Ok(()); + } + + to_file.write_all(delimiter.as_bytes())?; + to_file.write_all(from_content.as_bytes())?; + + self.print.infoln(format!("Merging {to:?} contents")); + Ok(()) + } + + fn get_merged_file_delimiter(file_path: &Path) -> String { + let comment = if file_path.to_string_lossy().contains("README.md") { + "---\n".to_string() + } else if file_path.to_string_lossy().contains("gitignore") { + "# The following is from the Frontend Template's .gitignore".to_string() + } else { + String::new() + }; - format!("\n\n{comment}\n\n").to_string() + format!("\n\n{comment}\n\n").to_string() + } } #[cfg(test)] @@ -476,9 +513,16 @@ mod tests { fn test_init() { 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(), "", &with_examples, overwrite).unwrap(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: vec![], + frontend_template: String::new(), + overwrite: false, + }, + print: print::Print::new(false), + }; + runner.run().unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -495,9 +539,16 @@ mod tests { fn test_init_including_example_contract() { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); - let with_examples = ["alloc".to_owned()]; - let overwrite = false; - init(project_dir.as_path(), "", &with_examples, overwrite).unwrap(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: ["alloc".to_owned()].to_vec(), + frontend_template: String::new(), + overwrite: false, + }, + print: print::Print::new(false), + }; + runner.run().unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -519,9 +570,16 @@ mod tests { fn test_init_including_multiple_example_contracts() { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join("project"); - let with_examples = ["account".to_owned(), "atomic_swap".to_owned()]; - let overwrite = false; - init(project_dir.as_path(), "", &with_examples, overwrite).unwrap(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: ["account".to_owned(), "atomic_swap".to_owned()].to_vec(), + frontend_template: String::new(), + overwrite: false, + }, + print: print::Print::new(false), + }; + runner.run().unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -544,9 +602,16 @@ mod tests { fn test_init_with_invalid_example_contract() { 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()]; - let overwrite = false; - assert!(init(project_dir.as_path(), "", &with_examples, overwrite).is_err()); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: ["invalid_example".to_owned(), "atomic_swap".to_owned()].to_vec(), + frontend_template: String::new(), + overwrite: false, + }, + print: print::Print::new(false), + }; + assert!(runner.run().is_err()); temp_dir.close().unwrap(); } @@ -555,15 +620,16 @@ mod tests { fn test_init_with_frontend_template() { 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(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: vec![], + frontend_template: "https://github.com/stellar/soroban-astro-template".to_owned(), + overwrite: false, + }, + print: print::Print::new(false), + }; + runner.run().unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -585,28 +651,33 @@ mod tests { 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(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: vec![], + frontend_template: "https://github.com/stellar/soroban-astro-template".to_owned(), + overwrite: false, + }, + print: print::Print::new(false), + }; + runner.run().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(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: vec![], + frontend_template: "https://github.com/stellar/soroban-astro-template".to_owned(), + overwrite: true, + }, + print: print::Print::new(false), + }; + runner.run().unwrap(); // Get new modification times let new_mod_times = get_mod_times(&project_dir); @@ -641,15 +712,16 @@ mod tests { 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(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: vec![], + frontend_template: "https://github.com/stellar/soroban-astro-template".to_owned(), + overwrite: false, + }, + print: print::Print::new(false), + }; + runner.run().unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -673,24 +745,28 @@ mod tests { fn test_init_does_not_duplicate_frontend_readme_contents_when_run_more_than_once() { 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(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: vec![], + frontend_template: "https://github.com/stellar/soroban-astro-template".to_owned(), + overwrite: false, + }, + print: print::Print::new(false), + }; + runner.run().unwrap(); // call init again to make sure the README.md's contents are not duplicated - init( - project_dir.as_path(), - "https://github.com/stellar/soroban-astro-template", - &with_examples, - overwrite, - ) - .unwrap(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: vec![], + frontend_template: "https://github.com/stellar/soroban-astro-template".to_owned(), + overwrite: false, + }, + print: print::Print::new(false), + }; + runner.run().unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -737,7 +813,6 @@ mod tests { let cargo_toml_path = contract_dir.as_path().join("Cargo.toml"); let cargo_toml_str = read_to_string(cargo_toml_path.clone()).unwrap(); let doc = cargo_toml_str.parse::().unwrap(); - println!("{cargo_toml_path:?} contents:\n{cargo_toml_str}"); assert!( doc.get("dependencies") .unwrap() diff --git a/cmd/soroban-cli/src/commands/contract/mod.rs b/cmd/soroban-cli/src/commands/contract/mod.rs index 9d574335c0..761784de92 100644 --- a/cmd/soroban-cli/src/commands/contract/mod.rs +++ b/cmd/soroban-cli/src/commands/contract/mod.rs @@ -146,7 +146,7 @@ impl Cmd { Cmd::Deploy(deploy) => deploy.run(global_args).await?, Cmd::Id(id) => id.run()?, Cmd::Info(info) => info.run().await?, - Cmd::Init(init) => init.run()?, + Cmd::Init(init) => init.run(global_args)?, Cmd::Inspect(inspect) => inspect.run()?, Cmd::Install(install) => install.run(global_args).await?, Cmd::Invoke(invoke) => invoke.run(global_args).await?, diff --git a/cmd/soroban-cli/src/print.rs b/cmd/soroban-cli/src/print.rs index ca95f652c0..2afa5ec03a 100644 --- a/cmd/soroban-cli/src/print.rs +++ b/cmd/soroban-cli/src/print.rs @@ -86,6 +86,7 @@ create_print_functions!(error, errorln, "❌"); create_print_functions!(globe, globeln, "🌎"); create_print_functions!(info, infoln, "ℹ️"); create_print_functions!(link, linkln, "🔗"); +create_print_functions!(plus, plusln, "➕"); create_print_functions!(save, saveln, "💾"); create_print_functions!(search, searchln, "🔎"); create_print_functions!(warn, warnln, "⚠️"); From 6e64987461558c66fb9304ef3aac62ff9910d8b9 Mon Sep 17 00:00:00 2001 From: Chad Ostrowski <221614+chadoh@users.noreply.github.com> Date: Fri, 30 Aug 2024 14:30:06 -0400 Subject: [PATCH 16/18] fix: update snapshot fixtures (#1580) --- .../test_custom_types/dist/index.d.ts | 9 +- .../fixtures/test_custom_types/dist/index.js | 5 +- .../test_custom_types/package-lock.json | 87 +++++++------------ .../fixtures/test_custom_types/src/index.ts | 11 ++- 4 files changed, 47 insertions(+), 65 deletions(-) diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/index.d.ts b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/index.d.ts index 0b661d2c35..cd888cc046 100644 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/index.d.ts +++ b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/index.d.ts @@ -51,6 +51,9 @@ export type ComplexEnum = { values: void; }; export declare const Errors: { + /** + * Please provide an odd number + */ 1: { message: string; }; @@ -186,7 +189,7 @@ export interface Client { simulate?: boolean; }) => Promise>; /** - * Construct and simulate a strukt_hel transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * + * Construct and simulate a strukt_hel transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * Example contract method which takes a struct */ strukt_hel: ({ strukt }: { @@ -358,7 +361,7 @@ export interface Client { simulate?: boolean; }) => Promise>; /** - * Construct and simulate a not transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * + * Construct and simulate a not transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * Negates a boolean value */ not: ({ boolean }: { @@ -493,7 +496,7 @@ export interface Client { simulate?: boolean; }) => Promise>; /** - * Construct and simulate a option transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * + * Construct and simulate a option transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * Example of an optional argument */ option: ({ option }: { diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/index.js b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/index.js index 5b9e067c90..4286b2a681 100644 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/index.js +++ b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/index.js @@ -20,7 +20,10 @@ export var RoyalCard; RoyalCard[RoyalCard["King"] = 13] = "King"; })(RoyalCard || (RoyalCard = {})); export const Errors = { - 1: { message: "Please provide an odd number" } + /** + * Please provide an odd number + */ + 1: { message: "NumberMustBeOdd" } }; export class Client extends ContractClient { options; diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/package-lock.json b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/package-lock.json index 11ad043be2..dd6bb86738 100644 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/package-lock.json +++ b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/package-lock.json @@ -17,13 +17,11 @@ }, "node_modules/@stellar/js-xdr": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@stellar/js-xdr/-/js-xdr-3.1.1.tgz", - "integrity": "sha512-3gnPjAz78htgqsNEDkEsKHKosV2BF2iZkoHCNxpmZwUxiPsw+2VaXMed8RRMe0rGk3d5GZe7RrSba8zV80J3Ag==" + "license": "Apache-2.0" }, "node_modules/@stellar/stellar-base": { "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@stellar/stellar-base/-/stellar-base-12.0.1.tgz", - "integrity": "sha512-g6c27MNsDgEdUmoNQJn7zCWoCY50WHt0OIIOq3PhWaJRtUaT++qs1Jpb8+1bny2GmhtfRGOfPUFSyQBuHT9Mvg==", + "license": "Apache-2.0", "dependencies": { "@stellar/js-xdr": "^3.1.1", "base32.js": "^0.1.0", @@ -38,8 +36,7 @@ }, "node_modules/@stellar/stellar-sdk": { "version": "12.1.0", - "resolved": "https://registry.npmjs.org/@stellar/stellar-sdk/-/stellar-sdk-12.1.0.tgz", - "integrity": "sha512-Va0hu9SaPezmMbO5eMwL5D15Wrx1AGWRtxayUDRWV2Fr3ynY58mvCZS1vsgNQ4kE8MZe3nBVKv6T9Kzqwgx1PQ==", + "license": "Apache-2.0", "dependencies": { "@stellar/stellar-base": "^12.0.1", "axios": "^1.7.2", @@ -52,13 +49,11 @@ }, "node_modules/asynckit": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "license": "MIT" }, "node_modules/axios": { "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -67,16 +62,13 @@ }, "node_modules/base32.js": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/base32.js/-/base32.js-0.1.0.tgz", - "integrity": "sha512-n3TkB02ixgBOhTvANakDb4xaMXnYUVkNoRFJjQflcqMQhyEKxEHdj3E6N8t8sUQ0mjH/3/JxzlXuz3ul/J90pQ==", + "license": "MIT", "engines": { "node": ">=0.12.0" } }, "node_modules/base64-js": { "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "funding": [ { "type": "github", @@ -90,20 +82,18 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/bignumber.js": { "version": "9.1.2", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", - "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "license": "MIT", "engines": { "node": "*" } }, "node_modules/buffer": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "funding": [ { "type": "github", @@ -118,6 +108,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -125,8 +116,7 @@ }, "node_modules/combined-stream": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -136,30 +126,27 @@ }, "node_modules/delayed-stream": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", "engines": { "node": ">=0.4.0" } }, "node_modules/eventsource": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", - "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", + "license": "MIT", "engines": { "node": ">=12.0.0" } }, "node_modules/follow-redirects": { "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -171,8 +158,7 @@ }, "node_modules/form-data": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -184,8 +170,6 @@ }, "node_modules/ieee754": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "funding": [ { "type": "github", @@ -199,25 +183,23 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "BSD-3-Clause" }, "node_modules/inherits": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "license": "ISC" }, "node_modules/mime-db": { "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -227,8 +209,7 @@ }, "node_modules/node-gyp-build": { "version": "4.8.1", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz", - "integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==", + "license": "MIT", "optional": true, "bin": { "node-gyp-build": "bin.js", @@ -238,21 +219,17 @@ }, "node_modules/proxy-from-env": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "license": "MIT" }, "node_modules/randombytes": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" } }, "node_modules/safe-buffer": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "funding": [ { "type": "github", @@ -266,12 +243,12 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/sha.js": { "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "license": "(MIT AND BSD-3-Clause)", "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -282,9 +259,8 @@ }, "node_modules/sodium-native": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.1.1.tgz", - "integrity": "sha512-LXkAfRd4FHtkQS4X6g+nRcVaN7mWVNepV06phIsC6+IZFvGh1voW5TNQiQp2twVaMf05gZqQjuS+uWLM6gHhNQ==", "hasInstallScript": true, + "license": "MIT", "optional": true, "dependencies": { "node-gyp-build": "^4.8.0" @@ -292,13 +268,11 @@ }, "node_modules/toml": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", - "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==" + "license": "MIT" }, "node_modules/tweetnacl": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + "license": "Unlicense" }, "node_modules/typescript": { "version": "5.3.3", @@ -315,8 +289,7 @@ }, "node_modules/urijs": { "version": "1.19.11", - "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz", - "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==" + "license": "MIT" } } } diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/src/index.ts b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/src/index.ts index b6ee3dbbc1..7f2d5b0000 100644 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/src/index.ts +++ b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/src/index.ts @@ -59,7 +59,10 @@ export type TupleStruct = readonly [Test, SimpleEnum]; export type ComplexEnum = {tag: "Struct", values: readonly [Test]} | {tag: "Tuple", values: readonly [TupleStruct]} | {tag: "Enum", values: readonly [SimpleEnum]} | {tag: "Asset", values: readonly [string, i128]} | {tag: "Void", values: void}; export const Errors = { - 1: {message:"Please provide an odd number"} + /** + * Please provide an odd number + */ + 1: {message:"NumberMustBeOdd"} } export interface Client { @@ -204,7 +207,7 @@ export interface Client { }) => Promise> /** - * Construct and simulate a strukt_hel transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * + * Construct and simulate a strukt_hel transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * Example contract method which takes a struct */ strukt_hel: ({strukt}: {strukt: Test}, options?: { @@ -385,7 +388,7 @@ export interface Client { }) => Promise> /** - * Construct and simulate a not transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * + * Construct and simulate a not transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * Negates a boolean value */ not: ({boolean}: {boolean: boolean}, options?: { @@ -526,7 +529,7 @@ export interface Client { }) => Promise> /** - * Construct and simulate a option transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * + * Construct and simulate a option transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * Example of an optional argument */ option: ({option}: {option: Option}, options?: { From a0be0f7e82c806a6c4dd09d49fb8cdb4f8fbd29a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2024 15:01:16 -0400 Subject: [PATCH 17/18] build(deps): bump the npm_and_yarn group across 2 directories with 2 updates (#1581) --- .../test_custom_types/package-lock.json | 5 +++-- .../ts-tests/package-lock.json | 20 +++++++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/package-lock.json b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/package-lock.json index dd6bb86738..f45484ffbf 100644 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/package-lock.json +++ b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/package-lock.json @@ -52,8 +52,9 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.7.2", - "license": "MIT", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz", + "integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", diff --git a/cmd/crates/soroban-spec-typescript/ts-tests/package-lock.json b/cmd/crates/soroban-spec-typescript/ts-tests/package-lock.json index 8e620a1a31..deee8f09dd 100644 --- a/cmd/crates/soroban-spec-typescript/ts-tests/package-lock.json +++ b/cmd/crates/soroban-spec-typescript/ts-tests/package-lock.json @@ -700,9 +700,9 @@ } }, "node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz", + "integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==", "dev": true, "dependencies": { "follow-redirects": "^1.15.6", @@ -782,12 +782,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -1610,9 +1610,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" From ce1bbe7c228d30a434867949cd044ca0a16852a5 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Fri, 30 Aug 2024 18:05:32 -0400 Subject: [PATCH 18/18] Fix after merging --- .../src/commands/contract/arg_parsing.rs | 127 ++++++++++-------- .../src/commands/contract/extend.rs | 2 +- .../src/commands/contract/invoke.rs | 15 +-- cmd/soroban-cli/src/commands/contract/read.rs | 2 +- .../src/commands/contract/restore.rs | 2 +- 5 files changed, 79 insertions(+), 69 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/arg_parsing.rs b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs index 4a8af47e29..772a3e2f9e 100644 --- a/cmd/soroban-cli/src/commands/contract/arg_parsing.rs +++ b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs @@ -5,16 +5,17 @@ use std::fmt::Debug; use std::path::PathBuf; use clap::value_parser; -use ed25519_dalek::SigningKey; use heck::ToKebabCase; +use crate::commands::txn_result::TxnResult; +use crate::config::secret::StellarSigner; +use crate::config::{self}; +use crate::signer::{self, Stellar}; 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_sdk::xdr::ScSpecFunctionInputV0; use soroban_spec_tools::Spec; #[derive(thiserror::Error, Debug)] @@ -43,14 +44,16 @@ pub enum Error { MissingArgument(String), #[error("")] MissingFileArg(PathBuf), + #[error(transparent)] + Signer(#[from] signer::Error), } -pub fn build_host_function_parameters( +pub async fn build_host_function_parameters( contract_id: &stellar_strkey::Contract, slop: &[OsString], spec_entries: &[ScSpecEntry], config: &config::Args, -) -> Result<(String, Spec, InvokeContractArgs, Vec), Error> { +) -> 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) @@ -70,57 +73,15 @@ pub fn build_host_function_parameters( 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 mut signers: Vec = vec![]; + let mut parsed_args: Vec = Vec::new(); + for i in func.inputs.iter() { + let (val, signer) = parse_arg(i, matches_, config, &spec).await?; + parsed_args.push(val); + if let Some(signer) = signer { + signers.push(signer); + } + } let contract_address_arg = ScAddress::Contract(Hash(contract_id.0)); let function_symbol_arg = function @@ -145,6 +106,58 @@ pub fn build_host_function_parameters( Ok((function.clone(), spec, invoke_args, signers)) } +pub async fn parse_arg( + input: &ScSpecFunctionInputV0, + matches_: &clap::ArgMatches, + config: &config::Args, + spec: &Spec, +) -> Result<(ScVal, Option), Error> { + let mut signer: Option = None; + let name = input.name.to_utf8_string()?; + let sc_val = if let Some(mut val) = matches_.get_raw(&name) { + let mut s = val.next().unwrap().to_string_lossy().to_string(); + if matches!(input.type_, ScSpecTypeDef::Address) { + // Currently we only support local keys, same as input for --sign-with-key`, for signing auth entries. + if let Ok(signer_) = config + .sign_with + .locator + .account(&s) + .and_then(|signer| Ok(signer.signer(config.sign_with.hd_path, false)?)) + { + s = signer_.get_public_key().await?.to_string(); + signer = Some(signer_); + } + } + spec.from_string(&s, &input.type_) + .map_err(|error| Error::CannotParseArg { arg: name, error })? + } else if matches!(input.type_, ScSpecTypeDef::Option(_)) { + ScVal::Void + } else if let Some(arg_path) = matches_.get_one::(&fmt_arg_file_name(&name)) { + if matches!(input.type_, ScSpecTypeDef::Bytes | ScSpecTypeDef::BytesN(_)) { + 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{}", + input.type_, + file_contents.len() + ); + spec.from_string(&file_contents, &input.type_) + .map_err(|error| Error::CannotParseArg { arg: name, error })? + } + } else { + return Err(Error::MissingArgument(name)); + }; + Ok((sc_val, signer)) +} + fn build_custom_cmd(name: &str, spec: &Spec) -> Result { let func = spec .find_function(name) diff --git a/cmd/soroban-cli/src/commands/contract/extend.rs b/cmd/soroban-cli/src/commands/contract/extend.rs index 199d36810e..79d0d3bd98 100644 --- a/cmd/soroban-cli/src/commands/contract/extend.rs +++ b/cmd/soroban-cli/src/commands/contract/extend.rs @@ -130,7 +130,7 @@ impl NetworkRunnable for Cmd { let config = config.unwrap_or(&self.config); let network = config.get_network()?; tracing::trace!(?network); - let keys = self.key.parse_keys(&config.locator, &network)?; + let keys = self.key.parse_keys(&config.sign_with.locator, &network)?; let network = &config.get_network()?; let client = Client::new(&network.rpc_url)?; let public_key = config.source_account().await?; diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 9264d8f44f..816431c14c 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -19,28 +19,23 @@ use soroban_env_host::{ }; use soroban_rpc::{SimulateHostFunctionResult, SimulateTransactionResponse}; -use soroban_sdk::xdr::ScSpecFunctionInputV0; use soroban_spec::read::FromWasmError; use super::super::events; use super::arg_parsing; -use crate::commands::config::secret::StellarSigner; 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::config::{self, data, locator, network}; use crate::get_spec::{self, get_remote_contract_spec}; use crate::print; -use crate::signer::auth::sign_soroban_authorizations; -use crate::signer::{self, Stellar}; +use crate::signer::{self, auth::sign_soroban_authorizations}; +// use crate::signer; use crate::{ commands::global, config::{self, data, locator, network}, rpc, Pwd, }; -use crate::{commands::global, rpc, Pwd}; use soroban_spec_tools::contract; -use soroban_spec_tools::{contract, Spec}; #[derive(Parser, Debug, Default, Clone)] #[allow(clippy::struct_excessive_bools)] @@ -212,13 +207,15 @@ impl NetworkRunnable for Cmd { tracing::trace!(?network); let contract_id = self .config + .sign_with .locator .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 _ = build_host_function_parameters(&contract_id, &self.slop, spec_entries, config)?; + let _ = build_host_function_parameters(&contract_id, &self.slop, spec_entries, config) + .await?; } let client = rpc::Client::new(&network.rpc_url)?; let account_details = if self.is_view { @@ -247,7 +244,7 @@ impl NetworkRunnable for Cmd { // Get the ledger footprint let (function, spec, host_function_params, signers) = - build_host_function_parameters(&contract_id, &self.slop, &spec_entries, config)?; + build_host_function_parameters(&contract_id, &self.slop, &spec_entries, config).await?; let tx = build_invoke_contract_tx( host_function_params.clone(), sequence + 1, diff --git a/cmd/soroban-cli/src/commands/contract/read.rs b/cmd/soroban-cli/src/commands/contract/read.rs index 3cb253bb1e..8398a281b4 100644 --- a/cmd/soroban-cli/src/commands/contract/read.rs +++ b/cmd/soroban-cli/src/commands/contract/read.rs @@ -186,7 +186,7 @@ impl NetworkRunnable for Cmd { let network = config.get_network()?; tracing::trace!(?network); let client = Client::new(&network.rpc_url)?; - let keys = self.key.parse_keys(&config.locator, &network)?; + let keys = self.key.parse_keys(&config.sign_with.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 67791a24f1..d3cdad6d0f 100644 --- a/cmd/soroban-cli/src/commands/contract/restore.rs +++ b/cmd/soroban-cli/src/commands/contract/restore.rs @@ -133,7 +133,7 @@ impl NetworkRunnable for Cmd { let config = config.unwrap_or(&self.config); let network = config.get_network()?; tracing::trace!(?network); - let entry_keys = self.key.parse_keys(&config.locator, &network)?; + let entry_keys = self.key.parse_keys(&config.sign_with.locator, &network)?; let client = Client::new(&network.rpc_url)?; // Get the account sequence number