From 1746e6b8b6807cbd99d319a611e83ce5a816e177 Mon Sep 17 00:00:00 2001 From: Chad Ostrowski <221614+chadoh@users.noreply.github.com> Date: Thu, 29 Feb 2024 11:33:04 -0500 Subject: [PATCH 1/5] chore: update docs with new possible example contract (#1226) --- docs/soroban-cli-full-docs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/soroban-cli-full-docs.md b/docs/soroban-cli-full-docs.md index 4f6d8cd34..f912eecb1 100644 --- a/docs/soroban-cli-full-docs.md +++ b/docs/soroban-cli-full-docs.md @@ -807,7 +807,7 @@ Initialize a Soroban project with an example contract * `-w`, `--with-example ` - Possible values: `account`, `alloc`, `atomic_multiswap`, `atomic_swap`, `auth`, `cross_contract`, `custom_types`, `deep_contract_auth`, `deployer`, `errors`, `events`, `fuzzing`, `increment`, `liquidity_pool`, `logging`, `mint-lock`, `simple_account`, `single_offer`, `timelock`, `token`, `upgradeable_contract`, `workspace` + Possible values: `account`, `alloc`, `atomic_multiswap`, `atomic_swap`, `auth`, `cross_contract`, `custom_types`, `deep_contract_auth`, `deployer`, `errors`, `eth_abi`, `events`, `fuzzing`, `increment`, `liquidity_pool`, `logging`, `mint-lock`, `simple_account`, `single_offer`, `timelock`, `token`, `upgradeable_contract`, `workspace` * `-f`, `--frontend-template ` From 3cffbbbe4059962a563c8cb370dc854b7f049050 Mon Sep 17 00:00:00 2001 From: Chad Ostrowski <221614+chadoh@users.noreply.github.com> Date: Thu, 29 Feb 2024 12:38:17 -0500 Subject: [PATCH 2/5] feat(ts-bindings): update freighter and stellar-sdk (#1227) --- .../soroban-spec-typescript/src/project_template/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/crates/soroban-spec-typescript/src/project_template/package.json b/cmd/crates/soroban-spec-typescript/src/project_template/package.json index e32d0f5e5..40a610e0e 100644 --- a/cmd/crates/soroban-spec-typescript/src/project_template/package.json +++ b/cmd/crates/soroban-spec-typescript/src/project_template/package.json @@ -2,9 +2,9 @@ "version": "0.0.0", "name": "INSERT_CONTRACT_NAME_HERE", "dependencies": { - "@stellar/freighter-api": "1.7.1", + "@stellar/freighter-api": "2.0.0", "buffer": "6.0.3", - "@stellar/stellar-sdk": "11.2.0" + "@stellar/stellar-sdk": "11.2.2" }, "scripts": { "build": "node ./scripts/build.mjs" From 4b3fac88958594cd159a9e1ab87910e25c43eb91 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Fri, 1 Mar 2024 10:25:32 -0500 Subject: [PATCH 3/5] Feat/rpc tests (#1202) * chore: update dependencies and refactor test code The changes in this commit update the dependencies and refactor the test code to improve readability and maintainability. chore: remove unused code and options from test script * fix: docs * fix: consolidate the rpc testing with bindings * fix: set SOROBAN_FEE=10000 * fix: change port to allow for local CI testing and increase fee * fix: port 8000 important * fix: use newer version of RPC * fix: increase fee again * fix: update quickstart to latest latest * fix: deploy contract directly and remove fee args * fix: fmt and ensure stderr for deployoy * fix: don't install contract if it is already installed * fix: try fee arg * fix: fmt * fix: using quickstart:testing * fix: add sleep to dotenv tests * fix: add more sleep to dotenv tests * fix: use instruction and remove sleep * fix: use u32::max * :fix: u32::max/2 * fix: add padding to instruction from sim if one is not provided * fix: make async fn * fix: add back sleep * fix: add wait back in * fix: md-gen * fix: use async_trait --- .github/workflows/rpc-tests.yml | 42 +++ Cargo.lock | 66 ++++- Cargo.toml | 1 + cmd/crates/soroban-test/Cargo.toml | 6 +- cmd/crates/soroban-test/src/lib.rs | 181 +++++++++--- cmd/crates/soroban-test/tests/it/config.rs | 44 +-- .../soroban-test/tests/it/hello_world.rs | 22 -- .../tests/it/integration/custom_types.rs | 10 +- .../tests/it/integration/dotenv.rs | 109 ++++---- .../tests/it/integration/hello_world.rs | 258 ++++++++++-------- .../soroban-test/tests/it/integration/util.rs | 102 ++----- .../soroban-test/tests/it/integration/wrap.rs | 178 ++++++------ cmd/crates/soroban-test/tests/it/main.rs | 2 +- cmd/crates/soroban-test/tests/it/util.rs | 19 +- cmd/soroban-cli/Cargo.toml | 14 +- cmd/soroban-cli/src/bin/main.rs | 1 + .../src/commands/contract/deploy/asset.rs | 27 +- .../src/commands/contract/deploy/wasm.rs | 35 ++- .../src/commands/contract/extend.rs | 23 +- .../src/commands/contract/fetch.rs | 27 +- cmd/soroban-cli/src/commands/contract/init.rs | 2 +- .../src/commands/contract/install.rs | 41 ++- .../src/commands/contract/invoke.rs | 73 +++-- cmd/soroban-cli/src/commands/contract/read.rs | 31 ++- .../src/commands/contract/restore.rs | 21 +- cmd/soroban-cli/src/commands/events.rs | 47 +++- cmd/soroban-cli/src/commands/keys/generate.rs | 10 +- cmd/soroban-cli/src/commands/mod.rs | 13 + cmd/soroban-cli/src/commands/network/mod.rs | 30 +- cmd/soroban-cli/src/fee.rs | 16 +- 30 files changed, 884 insertions(+), 567 deletions(-) create mode 100644 .github/workflows/rpc-tests.yml delete mode 100644 cmd/crates/soroban-test/tests/it/hello_world.rs diff --git a/.github/workflows/rpc-tests.yml b/.github/workflows/rpc-tests.yml new file mode 100644 index 000000000..a388754e9 --- /dev/null +++ b/.github/workflows/rpc-tests.yml @@ -0,0 +1,42 @@ + +name: RPC Tests +on: + push: + branches: [main, release/**] + pull_request: + +jobs: + test: + name: test RPC + runs-on: ubuntu-22.04 + services: + rpc: + image: stellar/quickstart:testing@sha256:7f074dddaf081b21d273f7346325cc1017c38bbee7b839f8b633b280a663232d + ports: + - 8000:8000 + env: + ENABLE_LOGS: true + NETWORK: local + ENABLE_SOROBAN_RPC: true + options: >- + --health-cmd "curl --no-progress-meter --fail-with-body -X POST \"http://localhost:8000/soroban/rpc\" -H 'Content-Type: application/json' -d '{\"jsonrpc\":\"2.0\",\"id\":8675309,\"method\":\"getNetwork\"}' && curl --no-progress-meter \"http://localhost:8000/friendbot\" | grep '\"invalid_field\": \"addr\"'" + --health-interval 10s + --health-timeout 5s + --health-retries 50 + steps: + - uses: actions/checkout@v3 + - uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + - run: rustup update + - run: cargo build + - run: rustup target add wasm32-unknown-unknown + - run: make build-test-wasms + - run: SOROBAN_PORT=8000 cargo test --features it --package soroban-test --test it -- integration + diff --git a/Cargo.lock b/Cargo.lock index a5b1f458c..b0c5b4b40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1184,7 +1184,7 @@ dependencies = [ "gix-date", "itoa", "thiserror", - "winnow", + "winnow 0.5.40", ] [[package]] @@ -1266,7 +1266,7 @@ dependencies = [ "smallvec", "thiserror", "unicode-bom", - "winnow", + "winnow 0.5.40", ] [[package]] @@ -1515,7 +1515,7 @@ dependencies = [ "itoa", "smallvec", "thiserror", - "winnow", + "winnow 0.5.40", ] [[package]] @@ -1638,7 +1638,7 @@ dependencies = [ "gix-transport", "maybe-async", "thiserror", - "winnow", + "winnow 0.5.40", ] [[package]] @@ -1671,7 +1671,7 @@ dependencies = [ "gix-validate", "memmap2", "thiserror", - "winnow", + "winnow 0.5.40", ] [[package]] @@ -3327,6 +3327,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3509,6 +3518,7 @@ version = "20.3.1" dependencies = [ "assert_cmd", "assert_fs", + "async-trait", "base64 0.21.7", "cargo_metadata", "chrono", @@ -3563,8 +3573,8 @@ dependencies = [ "termcolor_output", "thiserror", "tokio", - "toml", - "toml_edit", + "toml 0.5.11", + "toml_edit 0.21.1", "tracing", "tracing-appender", "tracing-subscriber", @@ -3850,12 +3860,15 @@ dependencies = [ "soroban-cli", "soroban-env-host", "soroban-ledger-snapshot", + "soroban-rpc", "soroban-sdk", "soroban-spec", "soroban-spec-tools 20.3.1", "stellar-strkey 0.0.7", "thiserror", "tokio", + "toml 0.8.10", + "walkdir", "which", ] @@ -4302,11 +4315,26 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.5", +] + [[package]] name = "toml_datetime" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -4316,7 +4344,20 @@ checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ "indexmap 2.2.3", "toml_datetime", - "winnow", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e68c159e8f5ba8a28c4eb7b0c0c190d77bb479047ca713270048145a9ad28a" +dependencies = [ + "indexmap 2.2.3", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.6.0", ] [[package]] @@ -4935,6 +4976,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1dbce9e90e5404c5a52ed82b1d13fc8cfbdad85033b6f57546ffd1265f8451" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" diff --git a/Cargo.toml b/Cargo.toml index c2f0f3e6d..aad28aceb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,6 +85,7 @@ sha2 = "0.10.7" ethnum = "1.3.2" hex = "0.4.3" itertools = "0.10.0" +async-trait = "0.1.76" serde-aux = "4.1.2" serde_json = "1.0.82" diff --git a/cmd/crates/soroban-test/Cargo.toml b/cmd/crates/soroban-test/Cargo.toml index 81ad8dc04..0886a9274 100644 --- a/cmd/crates/soroban-test/Cargo.toml +++ b/cmd/crates/soroban-test/Cargo.toml @@ -25,6 +25,7 @@ stellar-strkey = { workspace = true } soroban-sdk = { workspace = true } sep5 = { workspace = true } soroban-cli = { workspace = true } +soroban-rpc = { workspace = true } thiserror = "1.0.31" sha2 = "0.10.6" @@ -32,11 +33,14 @@ assert_cmd = "2.0.4" assert_fs = "1.0.7" predicates = "2.1.5" fs_extra = "1.3.0" +toml = "0.8.10" + [dev-dependencies] serde_json = "1.0.93" which = { workspace = true } tokio = "1.28.1" +walkdir = "2.4.0" [features] -integration = [] +it = [] diff --git a/cmd/crates/soroban-test/src/lib.rs b/cmd/crates/soroban-test/src/lib.rs index bda6ec420..9ac884193 100644 --- a/cmd/crates/soroban-test/src/lib.rs +++ b/cmd/crates/soroban-test/src/lib.rs @@ -30,13 +30,17 @@ use assert_fs::{fixture::FixtureError, prelude::PathChild, TempDir}; use fs_extra::dir::CopyOptions; use soroban_cli::{ - commands::{config, contract, contract::invoke, global, keys}, - CommandParser, Pwd, + commands::{config, contract::invoke, global, keys, network, NetworkRunnable}, + CommandParser, }; mod wasm; pub use wasm::Wasm; +pub const TEST_ACCOUNT: &str = "test"; + +pub const LOCAL_NETWORK_PASSPHRASE: &str = "Standalone Network ; February 2017"; + #[derive(thiserror::Error, Debug)] pub enum Error { #[error(transparent)] @@ -53,11 +57,16 @@ pub enum Error { /// its own `TempDir` where it will save test-specific configuration. pub struct TestEnv { pub temp_dir: TempDir, + pub rpc_url: String, } impl Default for TestEnv { fn default() -> Self { - Self::new().unwrap() + let temp_dir = TempDir::new().unwrap(); + Self { + temp_dir, + rpc_url: "http://localhost:8889/soroban/rpc".to_string(), + } } } @@ -79,27 +88,67 @@ impl TestEnv { let test_env = TestEnv::default(); f(&test_env); } - pub fn new() -> Result { - let this = TempDir::new().map(|temp_dir| TestEnv { temp_dir })?; - std::env::set_var("XDG_CONFIG_HOME", this.temp_dir.as_os_str()); - this.new_assert_cmd("keys") - .arg("generate") - .arg("test") - .arg("-d") - .arg("--no-fund") - .assert(); - std::env::set_var("SOROBAN_ACCOUNT", "test"); - Ok(this) + + pub fn with_default_network(f: F) { + let test_env = TestEnv::new(); + f(&test_env); + } + + pub fn with_port(host_port: u16) -> TestEnv { + Self::with_rpc_url(&format!("http://localhost:{host_port}/soroban/rpc")) } + pub fn with_rpc_url(rpc_url: &str) -> TestEnv { + let env = TestEnv { + rpc_url: rpc_url.to_string(), + ..Default::default() + }; + env.generate_account("test", None).assert().success(); + env + } + + pub fn new() -> TestEnv { + if let Ok(rpc_url) = std::env::var("SOROBAN_RPC_URL") { + return Self::with_rpc_url(&rpc_url); + } + let host_port = std::env::var("SOROBAN_PORT") + .as_deref() + .ok() + .and_then(|n| n.parse().ok()) + .unwrap_or(8889); + Self::with_port(host_port) + } /// Create a new `assert_cmd::Command` for a given subcommand and set's the current directory /// to be the internal `temp_dir`. pub fn new_assert_cmd(&self, subcommand: &str) -> Command { - let mut this = Command::cargo_bin("soroban").unwrap_or_else(|_| Command::new("soroban")); - this.arg("-q"); - this.arg(subcommand); - this.current_dir(&self.temp_dir); - this + let mut cmd: Command = self.bin(); + cmd.arg(subcommand) + .env("SOROBAN_ACCOUNT", TEST_ACCOUNT) + .env("SOROBAN_RPC_URL", &self.rpc_url) + .env("SOROBAN_NETWORK_PASSPHRASE", LOCAL_NETWORK_PASSPHRASE) + .env("XDG_CONFIG_HOME", self.temp_dir.as_os_str()) + .current_dir(&self.temp_dir); + cmd + } + + pub fn bin(&self) -> Command { + Command::cargo_bin("soroban").unwrap_or_else(|_| Command::new("soroban")) + } + + pub fn generate_account(&self, account: &str, seed: Option) -> Command { + let mut cmd = self.new_assert_cmd("keys"); + cmd.arg("generate").arg(account); + if let Some(seed) = seed { + cmd.arg(format!("--seed={seed}")); + } + cmd + } + + pub fn fund_account(&self, account: &str) -> Assert { + self.new_assert_cmd("keys") + .arg("fund") + .arg(account) + .assert() } /// Parses a `&str` into a command and sets the pwd to be the same as the current `TestEnv`. @@ -129,32 +178,77 @@ impl TestEnv { } /// A convenience method for using the invoke command. - pub async fn invoke>(&self, command_str: &[I]) -> Result { - let cmd = contract::invoke::Cmd::parse_arg_vec( - &command_str - .iter() - .map(AsRef::as_ref) - .filter(|s| !s.is_empty()) - .collect::>(), - ) - .unwrap(); - self.invoke_cmd(cmd).await + pub async fn invoke_with_test>( + &self, + command_str: &[I], + ) -> Result { + self.invoke_with(command_str, "test").await + } + + /// A convenience method for using the invoke command. + pub async fn invoke_with>( + &self, + command_str: &[I], + source: &str, + ) -> Result { + let cmd = self.cmd_with_config::(command_str); + self.run_cmd_with(cmd, source).await + } + + /// A convenience method for using the invoke command. + pub fn cmd_with_config, T: CommandParser + NetworkRunnable>( + &self, + command_str: &[I], + ) -> T { + let mut arg = vec![ + "--network=local", + "--rpc-url=http", + "--network-passphrase=AA", + "--source-account=test", + ]; + let input = command_str + .iter() + .map(AsRef::as_ref) + .filter(|s| !s.is_empty()) + .collect::>(); + arg.extend(input); + T::parse_arg_vec(&arg).unwrap() } /// Invoke an already parsed invoke command - pub async fn invoke_cmd(&self, mut cmd: invoke::Cmd) -> Result { - cmd.set_pwd(self.dir()); - cmd.run_against_rpc_server(&global::Args { + pub async fn run_cmd_with( + &self, + cmd: T, + account: &str, + ) -> Result { + let config_dir = Some(self.dir().to_path_buf()); + let config = config::Args { + network: network::Args { + rpc_url: Some(self.rpc_url.clone()), + network_passphrase: Some(LOCAL_NETWORK_PASSPHRASE.to_string()), + network: None, + }, + source_account: account.to_string(), locator: config::locator::Args { global: false, - config_dir: None, + config_dir: config_dir.clone(), }, - filter_logs: Vec::default(), - quiet: false, - verbose: false, - very_verbose: false, - list: false, - }) + hd_path: None, + }; + cmd.run_against_rpc_server( + Some(&global::Args { + locator: config::locator::Args { + global: false, + config_dir, + }, + filter_logs: Vec::default(), + quiet: false, + verbose: false, + very_verbose: false, + list: false, + }), + Some(&config), + ) .await } @@ -181,7 +275,7 @@ impl TestEnv { /// Copy the contents of the current `TestEnv` to another `TestEnv` pub fn fork(&self) -> Result { - let this = TestEnv::new()?; + let this = TestEnv::new(); self.save(&this.temp_dir)?; Ok(this) } @@ -203,6 +297,7 @@ pub fn temp_ledger_file() -> OsString { pub trait AssertExt { fn stdout_as_str(&self) -> String; + fn stderr_as_str(&self) -> String; } impl AssertExt for Assert { @@ -212,6 +307,12 @@ impl AssertExt for Assert { .trim() .to_owned() } + fn stderr_as_str(&self) -> String { + String::from_utf8(self.get_output().stderr.clone()) + .expect("failed to make str") + .trim() + .to_owned() + } } pub trait CommandExt { fn json_arg(&mut self, j: A) -> &mut Self diff --git a/cmd/crates/soroban-test/tests/it/config.rs b/cmd/crates/soroban-test/tests/it/config.rs index 5912b2cf5..b6f1bff08 100644 --- a/cmd/crates/soroban-test/tests/it/config.rs +++ b/cmd/crates/soroban-test/tests/it/config.rs @@ -1,5 +1,5 @@ use assert_fs::TempDir; -use soroban_test::TestEnv; +use soroban_test::{AssertExt, TestEnv}; use std::{fs, path::Path}; use crate::util::{add_key, add_test_id, SecretKind, DEFAULT_SEED_PHRASE}; @@ -7,30 +7,35 @@ use soroban_cli::commands::network; const NETWORK_PASSPHRASE: &str = "Local Sandbox Stellar Network ; September 2022"; +fn ls(sandbox: &TestEnv) -> Vec { + sandbox + .new_assert_cmd("network") + .arg("ls") + .assert() + .stdout_as_str() + .split('\n') + .filter(|s| !s.is_empty()) + .map(ToString::to_string) + .collect::>() +} + #[test] fn set_and_remove_network() { TestEnv::with_default(|sandbox| { add_network(sandbox, "local"); let dir = sandbox.dir().join(".soroban").join("network"); - let read_dir = std::fs::read_dir(dir); - println!("{read_dir:#?}"); - let file = read_dir.unwrap().next().unwrap().unwrap(); + let mut read_dir = std::fs::read_dir(dir).unwrap(); + let file = read_dir.next().unwrap().unwrap(); assert_eq!(file.file_name().to_str().unwrap(), "local.toml"); + let res = ls(sandbox); + assert_eq!(res[0], "local"); + sandbox + .new_assert_cmd("network") + .arg("rm") + .arg("local") + .assert() + .success(); - let res = sandbox.cmd::(""); - let res = res.ls().unwrap(); - assert_eq!(res.len(), 1); - assert_eq!(&res[0], "local"); - - sandbox.cmd::("local").run().unwrap(); - - // sandbox - // .new_assert_cmd("config") - // .arg("network") - // .arg("rm") - // .arg("local") - // .assert() - // .stdout(""); sandbox .new_assert_cmd("network") .arg("ls") @@ -105,7 +110,7 @@ fn set_and_remove_global_network() { #[test] fn multiple_networks() { let sandbox = TestEnv::default(); - let ls = || -> Vec { sandbox.cmd::("").ls().unwrap() }; + let ls = || -> Vec { ls(&sandbox) }; add_network(&sandbox, "local"); println!("{:#?}", ls()); @@ -156,7 +161,6 @@ fn generate_key() { sandbox .new_assert_cmd("keys") .arg("generate") - .arg("--network=futurenet") .arg("--no-fund") .arg("--seed") .arg("0000000000000000") diff --git a/cmd/crates/soroban-test/tests/it/hello_world.rs b/cmd/crates/soroban-test/tests/it/hello_world.rs deleted file mode 100644 index 4c45403a1..000000000 --- a/cmd/crates/soroban-test/tests/it/hello_world.rs +++ /dev/null @@ -1,22 +0,0 @@ -use soroban_cli::commands::contract::{self, fetch}; -use soroban_test::TestEnv; -use std::path::PathBuf; - -use crate::util::{ - add_test_seed, is_rpc, network_passphrase, network_passphrase_arg, rpc_url, rpc_url_arg, - DEFAULT_PUB_KEY, DEFAULT_PUB_KEY_1, DEFAULT_SECRET_KEY, DEFAULT_SEED_PHRASE, HELLO_WORLD, - TEST_SALT, -}; - -#[tokio::test] -async fn fetch() { - if !is_rpc() { - return; - } - let e = TestEnv::default(); - let f = e.dir().join("contract.wasm"); - let id = deploy_hello(&e); - let cmd = e.cmd_arr::(&["--id", &id, "--out-file", f.to_str().unwrap()]); - cmd.run().await.unwrap(); - assert!(f.exists()); -} diff --git a/cmd/crates/soroban-test/tests/it/integration/custom_types.rs b/cmd/crates/soroban-test/tests/it/integration/custom_types.rs index fda2c1f61..6739ff3da 100644 --- a/cmd/crates/soroban-test/tests/it/integration/custom_types.rs +++ b/cmd/crates/soroban-test/tests/it/integration/custom_types.rs @@ -3,7 +3,7 @@ use serde_json::json; use soroban_cli::commands; use soroban_test::TestEnv; -use crate::integration::util::{deploy_custom, extend_contract, CUSTOM_TYPES}; +use crate::integration::util::{deploy_custom, extend_contract}; use super::util::invoke_with_roundtrip; @@ -15,9 +15,9 @@ fn invoke_custom(e: &TestEnv, id: &str, func: &str) -> assert_cmd::Command { #[tokio::test] async fn parse() { - let sandbox = &TestEnv::default(); - let id = &deploy_custom(sandbox); - extend_contract(sandbox, id, CUSTOM_TYPES).await; + let sandbox = &TestEnv::new(); + let id = &deploy_custom(sandbox).await; + extend_contract(sandbox, id).await; symbol(sandbox, id); string_with_quotes(sandbox, id).await; symbol_with_quotes(sandbox, id).await; @@ -187,7 +187,7 @@ fn number_arg_return_ok(sandbox: &TestEnv, id: &str) { async fn number_arg_return_err(sandbox: &TestEnv, id: &str) { let res = sandbox - .invoke(&["--id", id, "--", "u32_fail_on_even", "--u32_=2"]) + .invoke_with_test(&["--id", id, "--", "u32_fail_on_even", "--u32_=2"]) .await .unwrap_err(); if let commands::contract::invoke::Error::ContractInvoke(name, doc) = &res { diff --git a/cmd/crates/soroban-test/tests/it/integration/dotenv.rs b/cmd/crates/soroban-test/tests/it/integration/dotenv.rs index 7c0f25b3f..dff36dfe7 100644 --- a/cmd/crates/soroban-test/tests/it/integration/dotenv.rs +++ b/cmd/crates/soroban-test/tests/it/integration/dotenv.rs @@ -1,67 +1,68 @@ use soroban_test::TestEnv; -use super::util::{deploy_hello, TEST_CONTRACT_ID}; +use super::util::deploy_hello; fn write_env_file(e: &TestEnv, contents: &str) { let env_file = e.dir().join(".env"); - std::fs::write(&env_file, contents).unwrap(); + let contents = format!("SOROBAN_CONTRACT_ID={contents}"); + std::fs::write(&env_file, &contents).unwrap(); assert_eq!(contents, std::fs::read_to_string(env_file).unwrap()); } -fn contract_id() -> String { - format!("SOROBAN_CONTRACT_ID={TEST_CONTRACT_ID}") +#[tokio::test] +async fn can_read_file() { + let e = &TestEnv::new(); + std::thread::sleep(core::time::Duration::from_millis(1000)); + let id = deploy_hello(e).await; + println!("{id}"); + write_env_file(e, &id); + e.new_assert_cmd("contract") + .arg("invoke") + .arg("--") + .arg("hello") + .arg("--world=world") + .assert() + .stdout("[\"Hello\",\"world\"]\n") + .success(); } -#[test] -fn can_read_file() { - TestEnv::with_default(|e| { - deploy_hello(e); - write_env_file(e, &contract_id()); - e.new_assert_cmd("contract") - .arg("invoke") - .arg("--") - .arg("hello") - .arg("--world=world") - .assert() - .stdout("[\"Hello\",\"world\"]\n") - .success(); - }); +#[tokio::test] +async fn current_env_not_overwritten() { + let e = TestEnv::new(); + std::thread::sleep(core::time::Duration::from_millis(3000)); + write_env_file(&e, &deploy_hello(&e).await); + e.new_assert_cmd("contract") + .env( + "SOROBAN_CONTRACT_ID", + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + ) + .arg("invoke") + .arg("--") + .arg("hello") + .arg("--world=world") + .assert() + .stderr( + "error: Contract not found: CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4\n", + ); } -#[test] -fn current_env_not_overwritten() { - TestEnv::with_default(|e| { - deploy_hello(e); - write_env_file(e, &contract_id()); - - e.new_assert_cmd("contract") - .env("SOROBAN_CONTRACT_ID", "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4") - .arg("invoke") - .arg("--") - .arg("hello") - .arg("--world=world") - .assert() - .stderr("error: Contract not found: CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4\n"); - }); -} - -#[test] -fn cli_args_have_priority() { - TestEnv::with_default(|e| { - deploy_hello(e); - write_env_file(e, &contract_id()); - e.new_assert_cmd("contract") - .env( - "SOROBAN_CONTRACT_ID", - "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", - ) - .arg("invoke") - .arg("--id") - .arg(TEST_CONTRACT_ID) - .arg("--") - .arg("hello") - .arg("--world=world") - .assert() - .stdout("[\"Hello\",\"world\"]\n"); - }); +#[tokio::test] +async fn cli_args_have_priority() { + let e = &TestEnv::new(); + std::thread::sleep(core::time::Duration::from_millis(2000)); + let id = deploy_hello(e).await; + write_env_file(e, &id); + e.new_assert_cmd("contract") + .env( + "SOROBAN_CONTRACT_ID", + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + ) + .arg("invoke") + .arg("--id") + .arg(id) + .arg("--") + .arg("hello") + .arg("--world=world") + .assert() + .stdout("[\"Hello\",\"world\"]\n"); } diff --git a/cmd/crates/soroban-test/tests/it/integration/hello_world.rs b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs index 7bd8d596a..671d2edff 100644 --- a/cmd/crates/soroban-test/tests/it/integration/hello_world.rs +++ b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs @@ -1,50 +1,115 @@ use predicates::boolean::PredicateBooleanExt; use soroban_cli::commands::{ + config::{locator, secret}, contract::{self, fetch}, - keys, }; -use soroban_test::TestEnv; +use soroban_rpc::GetLatestLedgerResponse; +use soroban_test::{AssertExt, TestEnv, LOCAL_NETWORK_PASSPHRASE}; -use crate::{integration::util::extend_contract, util::DEFAULT_SEED_PHRASE}; +use crate::integration::util::extend_contract; -use super::util::{ - add_test_seed, deploy_hello, extend, network_passphrase, network_passphrase_arg, rpc_url, - rpc_url_arg, DEFAULT_PUB_KEY, DEFAULT_PUB_KEY_1, DEFAULT_SECRET_KEY, HELLO_WORLD, -}; +use super::util::{deploy_hello, extend, HELLO_WORLD}; #[tokio::test] -#[ignore] async fn invoke() { - let sandbox = &TestEnv::default(); - let id = &deploy_hello(sandbox); - extend_contract(sandbox, id, HELLO_WORLD).await; + let sandbox = &TestEnv::new(); + let c = soroban_rpc::Client::new(&sandbox.rpc_url).unwrap(); + let GetLatestLedgerResponse { sequence, .. } = c.get_latest_ledger().await.unwrap(); + sandbox + .new_assert_cmd("keys") + .arg("fund") + .arg("test") + .assert() + .stderr(predicates::str::contains("Account already exists")); + sandbox + .new_assert_cmd("keys") + .arg("fund") + .arg("test") + .arg("--hd-path=1") + .assert(); + let addr = sandbox + .new_assert_cmd("keys") + .arg("address") + .arg("test") + .assert() + .stdout_as_str(); + let addr_1 = sandbox + .new_assert_cmd("keys") + .arg("address") + .arg("test") + .arg("--hd-path=1") + .assert() + .stdout_as_str(); + println!("Addrs {addr}, {addr_1}"); + + let secret_key = sandbox + .new_assert_cmd("keys") + .arg("show") + .arg("test") + .assert() + .stdout_as_str(); + let secret_key_1 = sandbox + .new_assert_cmd("keys") + .arg("show") + .arg("test") + .arg("--hd-path=1") + .assert() + .stdout_as_str(); + let dir = sandbox.dir(); + let seed_phrase = std::fs::read_to_string(dir.join(".soroban/identity/test.toml")).unwrap(); + let s = toml::from_str::(&seed_phrase).unwrap(); + let secret::Secret::SeedPhrase { seed_phrase } = s else { + panic!("Expected seed phrase") + }; + let id = &deploy_hello(sandbox).await; + extend_contract(sandbox, id).await; // Note that all functions tested here have no state invoke_hello_world(sandbox, id); + sandbox .new_assert_cmd("events") - .arg("--start-ledger=20") + .arg("--start-ledger") + .arg(&sequence.to_string()) .arg("--id") .arg(id) .assert() .stdout(predicates::str::contains(id).not()) .success(); invoke_hello_world_with_lib(sandbox, id).await; - sandbox - .new_assert_cmd("events") - .arg("--start-ledger=20") - .arg("--id") - .arg(id) + let config_locator = locator::Args { + global: false, + config_dir: Some(dir.to_path_buf()), + }; + config_locator + .write_identity( + "testone", + &secret::Secret::SecretKey { + secret_key: secret_key_1.clone(), + }, + ) + .unwrap(); + let sk_from_file = std::fs::read_to_string(dir.join(".soroban/identity/testone.toml")).unwrap(); + + assert_eq!(sk_from_file, format!("secret_key = \"{secret_key_1}\"\n")); + let secret_key_1_readin = sandbox + .new_assert_cmd("keys") + .arg("show") + .arg("testone") .assert() - .stdout(predicates::str::contains(id)) - .success(); - invoke_hello_world_with_lib_two(sandbox, id).await; - invoke_auth(sandbox, id); - invoke_auth_with_identity(sandbox, id).await; - invoke_auth_with_different_test_account_fail(sandbox, id).await; + .stdout_as_str(); + assert_eq!(secret_key_1, secret_key_1_readin); + // list all files recursively from dir including in hidden folders + for entry in walkdir::WalkDir::new(dir) { + println!("{}", entry.unwrap().path().display()); + } + invoke_auth(sandbox, id, &addr); + invoke_auth_with_identity(sandbox, id, "test", &addr); + invoke_auth_with_identity(sandbox, id, "testone", &addr_1); + invoke_auth_with_different_test_account_fail(sandbox, id, &addr_1).await; // invoke_auth_with_different_test_account(sandbox, id); contract_data_read_failure(sandbox, id); - invoke_with_seed(sandbox, id).await; - invoke_with_sk(sandbox, id).await; + invoke_with_seed(sandbox, id, &seed_phrase).await; + invoke_with_sk(sandbox, id, &secret_key).await; // This does add an identity to local config invoke_with_id(sandbox, id).await; handles_kebab_case(sandbox, id).await; @@ -68,48 +133,27 @@ fn invoke_hello_world(sandbox: &TestEnv, id: &str) { } async fn invoke_hello_world_with_lib(e: &TestEnv, id: &str) { - let mut cmd = contract::invoke::Cmd { + let cmd = contract::invoke::Cmd { contract_id: id.to_string(), slop: vec!["hello".into(), "--world=world".into()], ..Default::default() }; - - cmd.config.network.rpc_url = rpc_url(); - cmd.config.network.network_passphrase = network_passphrase(); - - let res = e.invoke_cmd(cmd).await.unwrap(); + let res = e.run_cmd_with(cmd, "test").await.unwrap(); assert_eq!(res, r#"["Hello","world"]"#); } -async fn invoke_hello_world_with_lib_two(e: &TestEnv, id: &str) { - let hello_world = HELLO_WORLD.to_string(); - let mut invoke_args = vec!["--id", id, "--wasm", hello_world.as_str()]; - let args = vec!["--", "hello", "--world=world"]; - let res = - if let (Some(rpc), Some(network_passphrase)) = (rpc_url_arg(), network_passphrase_arg()) { - invoke_args.push(&rpc); - invoke_args.push(&network_passphrase); - e.invoke(&[invoke_args, args].concat()).await.unwrap() - } else { - e.invoke(&[invoke_args, args].concat()).await.unwrap() - }; - assert_eq!(res, r#"["Hello","world"]"#); -} - -fn invoke_auth(sandbox: &TestEnv, id: &str) { +fn invoke_auth(sandbox: &TestEnv, id: &str, addr: &str) { sandbox .new_assert_cmd("contract") .arg("invoke") .arg("--id") .arg(id) - .arg("--wasm") - .arg(HELLO_WORLD.path()) .arg("--") .arg("auth") - .arg(&format!("--addr={DEFAULT_PUB_KEY}")) + .arg("--addr=test") .arg("--world=world") .assert() - .stdout(format!("\"{DEFAULT_PUB_KEY}\"\n")) + .stdout(format!("\"{addr}\"\n")) .success(); // Invoke it again without providing the contract, to exercise the deployment @@ -120,65 +164,40 @@ fn invoke_auth(sandbox: &TestEnv, id: &str) { .arg(id) .arg("--") .arg("auth") - .arg(&format!("--addr={DEFAULT_PUB_KEY}")) + .arg("--addr=test") .arg("--world=world") .assert() - .stdout(format!("\"{DEFAULT_PUB_KEY}\"\n")) + .stdout(format!("\"{addr}\"\n")) .success(); } -async fn invoke_auth_with_identity(sandbox: &TestEnv, id: &str) { - sandbox - .cmd::("test -d ") - .run() - .await - .unwrap(); +fn invoke_auth_with_identity(sandbox: &TestEnv, id: &str, key: &str, addr: &str) { sandbox .new_assert_cmd("contract") .arg("invoke") + .arg("--source") + .arg(key) .arg("--id") .arg(id) - .arg("--wasm") - .arg(HELLO_WORLD.path()) .arg("--") .arg("auth") .arg("--addr") - .arg(DEFAULT_PUB_KEY) + .arg(key) .arg("--world=world") .assert() - .stdout(format!("\"{DEFAULT_PUB_KEY}\"\n")) + .stdout(format!("\"{addr}\"\n")) .success(); } -// fn invoke_auth_with_different_test_account(sandbox: &TestEnv, id: &str) { -// sandbox -// .new_assert_cmd("contract") -// .arg("invoke") -// .arg("--hd-path=1") -// .arg("--id") -// .arg(id) -// .arg("--wasm") -// .arg(HELLO_WORLD.path()) -// .arg("--") -// .arg("auth") -// .arg(&format!("--addr={DEFAULT_PUB_KEY_1}")) -// .arg("--world=world") -// .assert() -// .stdout(format!("\"{DEFAULT_PUB_KEY_1}\"\n")) -// .success(); -// } - -async fn invoke_auth_with_different_test_account_fail(sandbox: &TestEnv, id: &str) { +async fn invoke_auth_with_different_test_account_fail(sandbox: &TestEnv, id: &str, addr: &str) { let res = sandbox - .invoke(&[ + .invoke_with_test(&[ "--hd-path=0", "--id", id, - &rpc_url_arg().unwrap_or_default(), - &network_passphrase_arg().unwrap_or_default(), "--", "auth", - &format!("--addr={DEFAULT_PUB_KEY_1}"), + &format!("--addr={addr}"), "--world=world", ]) .await; @@ -207,9 +226,12 @@ fn contract_data_read_failure(sandbox: &TestEnv, id: &str) { #[tokio::test] async fn contract_data_read() { const KEY: &str = "COUNTER"; - let sandbox = &TestEnv::default(); - let id = &deploy_hello(sandbox); - let res = sandbox.invoke(&["--id", id, "--", "inc"]).await.unwrap(); + let sandbox = &TestEnv::new(); + let id = &deploy_hello(sandbox).await; + let res = sandbox + .invoke_with_test(&["--id", id, "--", "inc"]) + .await + .unwrap(); assert_eq!(res.trim(), "1"); extend(sandbox, id, Some(KEY)).await; @@ -248,30 +270,41 @@ async fn contract_data_read() { .stdout(predicates::str::starts_with("COUNTER,2")); } -async fn invoke_with_seed(sandbox: &TestEnv, id: &str) { - invoke_with_source(sandbox, DEFAULT_SEED_PHRASE, id).await; +#[tokio::test] +#[ignore] +async fn half_max_instructions() { + let sandbox = TestEnv::new(); + let wasm = HELLO_WORLD; + sandbox + .new_assert_cmd("contract") + .arg("deploy") + .arg("--fee") + .arg("1000000") + .arg("--instructions") + .arg(&(u32::MAX / 2).to_string()) + .arg("--wasm") + .arg(wasm.path()) + .arg("--ignore-checks") + .assert() + .stderr("") + .stdout_as_str(); +} + +async fn invoke_with_seed(sandbox: &TestEnv, id: &str, seed_phrase: &str) { + invoke_with_source(sandbox, seed_phrase, id).await; } -async fn invoke_with_sk(sandbox: &TestEnv, id: &str) { - invoke_with_source(sandbox, DEFAULT_SECRET_KEY, id).await; +async fn invoke_with_sk(sandbox: &TestEnv, id: &str, sk: &str) { + invoke_with_source(sandbox, sk, id).await; } async fn invoke_with_id(sandbox: &TestEnv, id: &str) { - let identity = add_test_seed(sandbox.dir()); - invoke_with_source(sandbox, &identity, id).await; + invoke_with_source(sandbox, "test", id).await; } async fn invoke_with_source(sandbox: &TestEnv, source: &str, id: &str) { let cmd = sandbox - .invoke(&[ - "--source-account", - source, - "--id", - id, - "--", - "hello", - "--world=world", - ]) + .invoke_with(&["--id", id, "--", "hello", "--world=world"], source) .await .unwrap(); assert_eq!(cmd, "[\"Hello\",\"world\"]"); @@ -279,25 +312,32 @@ async fn invoke_with_source(sandbox: &TestEnv, source: &str, id: &str) { async fn handles_kebab_case(e: &TestEnv, id: &str) { assert!(e - .invoke(&["--id", id, "--", "multi-word-cmd", "--contract-owner=world",]) + .invoke_with_test(&["--id", id, "--", "multi-word-cmd", "--contract-owner=world",]) .await .is_ok()); } async fn fetch(sandbox: &TestEnv, id: &str) { let f = sandbox.dir().join("contract.wasm"); - let cmd = sandbox.cmd_arr::(&["--id", id, "--out-file", f.to_str().unwrap()]); + let cmd = sandbox.cmd_arr::(&[ + "--rpc-url", + &sandbox.rpc_url, + "--network-passphrase", + LOCAL_NETWORK_PASSPHRASE, + "--id", + id, + "--out-file", + f.to_str().unwrap(), + ]); cmd.run().await.unwrap(); assert!(f.exists()); } async fn invoke_prng_u64_in_range_test(sandbox: &TestEnv, id: &str) { assert!(sandbox - .invoke(&[ + .invoke_with_test(&[ "--id", id, - "--wasm", - HELLO_WORLD.path().to_str().unwrap(), "--", "prng_u64_in_range", "--low=0", diff --git a/cmd/crates/soroban-test/tests/it/integration/util.rs b/cmd/crates/soroban-test/tests/it/integration/util.rs index ea27680b7..faa3534e4 100644 --- a/cmd/crates/soroban-test/tests/it/integration/util.rs +++ b/cmd/crates/soroban-test/tests/it/integration/util.rs @@ -1,23 +1,10 @@ -use soroban_cli::commands::contract; +use soroban_cli::commands; use soroban_test::{TestEnv, Wasm}; -use std::{fmt::Display, path::Path}; - -use crate::util::{add_key, SecretKind}; +use std::fmt::Display; pub const HELLO_WORLD: &Wasm = &Wasm::Custom("test-wasms", "test_hello_world"); pub const CUSTOM_TYPES: &Wasm = &Wasm::Custom("test-wasms", "test_custom_types"); -pub fn add_test_seed(dir: &Path) -> String { - let name = "test_seed"; - add_key( - dir, - name, - SecretKind::Seed, - "coral light army gather adapt blossom school alcohol coral light army giggle", - ); - name.to_owned() -} - pub async fn invoke_with_roundtrip(e: &TestEnv, id: &str, func: &str, data: D) where D: Display, @@ -25,80 +12,37 @@ where let data = data.to_string(); println!("{data}"); let res = e - .invoke(&["--id", id, "--", func, &format!("--{func}"), &data]) + .invoke_with_test(&["--id", id, "--", func, &format!("--{func}"), &data]) .await .unwrap(); assert_eq!(res, data); } -pub const DEFAULT_PUB_KEY: &str = "GDIY6AQQ75WMD4W46EYB7O6UYMHOCGQHLAQGQTKHDX4J2DYQCHVCR4W4"; -pub const DEFAULT_SECRET_KEY: &str = "SC36BWNUOCZAO7DMEJNNKFV6BOTPJP7IG5PSHLUOLT6DZFRU3D3XGIXW"; - -pub const DEFAULT_PUB_KEY_1: &str = "GCKZUJVUNEFGD4HLFBUNVYM2QY2P5WQQZMGRA3DDL4HYVT5MW5KG3ODV"; pub const TEST_SALT: &str = "f55ff16f66f43360266b95db6f8fec01d76031054306ae4a4b380598f6cfd114"; -pub const TEST_CONTRACT_ID: &str = "CBVTIVBYWAO2HNPNGKDCZW4OZYYESTKNGD7IPRTDGQSFJS4QBDQQJX3T"; - -pub fn rpc_url() -> Option { - std::env::var("SOROBAN_RPC_URL").ok() -} - -pub fn rpc_url_arg() -> Option { - rpc_url().map(|url| format!("--rpc-url={url}")) -} - -pub fn network_passphrase() -> Option { - std::env::var("SOROBAN_NETWORK_PASSPHRASE").ok() -} -pub fn network_passphrase_arg() -> Option { - network_passphrase().map(|p| format!("--network-passphrase={p}")) +pub async fn deploy_hello(sandbox: &TestEnv) -> String { + deploy_contract(sandbox, HELLO_WORLD).await } -pub fn deploy_hello(sandbox: &TestEnv) -> String { - deploy_contract(sandbox, HELLO_WORLD) +pub async fn deploy_custom(sandbox: &TestEnv) -> String { + deploy_contract(sandbox, CUSTOM_TYPES).await } -pub fn deploy_custom(sandbox: &TestEnv) -> String { - deploy_contract(sandbox, CUSTOM_TYPES) -} - -pub fn deploy_contract(sandbox: &TestEnv, wasm: &Wasm) -> String { - let hash = wasm.hash().unwrap(); - sandbox - .new_assert_cmd("contract") - .arg("install") - .arg("--wasm") - .arg(wasm.path()) - .arg("--ignore-checks") - .assert() - .success() - .stdout(format!("{hash}\n")); - - sandbox - .new_assert_cmd("contract") - .arg("deploy") - .arg("--wasm-hash") - .arg(&format!("{hash}")) - .arg("--salt") - .arg(TEST_SALT) - .arg("--ignore-checks") - .assert() - .success() - .stdout(format!("{TEST_CONTRACT_ID}\n")); - TEST_CONTRACT_ID.to_string() +pub async fn deploy_contract(sandbox: &TestEnv, wasm: &Wasm<'static>) -> String { + let cmd = sandbox.cmd_with_config::<_, commands::contract::deploy::wasm::Cmd>(&[ + "--fee", + "1000000", + "--wasm", + &wasm.path().to_string_lossy(), + "--salt", + TEST_SALT, + "--ignore-checks", + ]); + sandbox.run_cmd_with(cmd, "test").await.unwrap() } -pub async fn extend_contract(sandbox: &TestEnv, id: &str, wasm: &Wasm<'_>) { +pub async fn extend_contract(sandbox: &TestEnv, id: &str) { extend(sandbox, id, None).await; - let cmd: contract::extend::Cmd = sandbox.cmd_arr(&[ - "--wasm-hash", - wasm.hash().unwrap().to_string().as_str(), - "--durability", - "persistent", - "--ledgers-to-extend", - "100000", - ]); - cmd.run().await.unwrap(); } pub async fn extend(sandbox: &TestEnv, id: &str, value: Option<&str>) { @@ -114,6 +58,10 @@ pub async fn extend(sandbox: &TestEnv, id: &str, value: Option<&str>) { args.push("--key"); args.push(value); } - let cmd: contract::extend::Cmd = sandbox.cmd_arr(&args); - cmd.run().await.unwrap(); + sandbox + .new_assert_cmd("contract") + .arg("extend") + .args(args) + .assert() + .success(); } diff --git a/cmd/crates/soroban-test/tests/it/integration/wrap.rs b/cmd/crates/soroban-test/tests/it/integration/wrap.rs index a69e70c7c..aa356ce99 100644 --- a/cmd/crates/soroban-test/tests/it/integration/wrap.rs +++ b/cmd/crates/soroban-test/tests/it/integration/wrap.rs @@ -1,97 +1,101 @@ -use soroban_cli::CommandParser; -use soroban_cli::{ - commands::{contract::deploy::asset, keys}, - utils::contract_id_hash_from_asset, -}; -use soroban_test::TestEnv; - -use super::util::network_passphrase; +use soroban_cli::utils::contract_id_hash_from_asset; +use soroban_test::{AssertExt, TestEnv, LOCAL_NETWORK_PASSPHRASE}; #[tokio::test] #[ignore] async fn burn() { - let sandbox = &TestEnv::default(); - let network_passphrase = network_passphrase().unwrap(); - println!("NETWORK_PASSPHRASE: {network_passphrase:?}"); - let address = keys::address::Cmd::parse("test") - .unwrap() - .public_key() - .unwrap(); + let sandbox = &TestEnv::new(); + let network_passphrase = LOCAL_NETWORK_PASSPHRASE.to_string(); + let address = sandbox + .new_assert_cmd("keys") + .arg("address") + .arg("test") + .assert() + .stdout_as_str(); let asset = format!("native:{address}"); - wrap_cmd(&asset).run().await.unwrap(); + sandbox + .new_assert_cmd("contract") + .arg("asset") + .arg("deploy") + .arg("--source=test") + .arg("--asset") + .arg(&asset) + .assert() + .success(); + // wrap_cmd(&asset).run().await.unwrap(); let asset = soroban_cli::utils::parsing::parse_asset(&asset).unwrap(); let hash = contract_id_hash_from_asset(&asset, &network_passphrase).unwrap(); let id = stellar_strkey::Contract(hash.0).to_string(); - assert_eq!( - "CAMTHSPKXZJIRTUXQP5QWJIFH3XIDMKLFAWVQOFOXPTKAW5GKV37ZC4N", - id - ); - assert_eq!( - "true", - sandbox - .invoke(&[ - "--id", - &id, - "--source=test", - "--", - "authorized", - "--id", - &address.to_string() - ]) - .await - .unwrap() - ); - assert_eq!( - "\"9223372036854775807\"", - sandbox - .invoke(&[ - "--id", - &id, - "--source", - "test", - "--", - "balance", - "--id", - &address.to_string() - ]) - .await - .unwrap(), - ); - - println!( - "{}", - sandbox - .invoke(&[ - "--id", - &id, - "--source=test", - "--", - "burn", - "--id", - &address.to_string(), - "--amount=100" - ]) - .await - .unwrap() - ); - - assert_eq!( - "\"9223372036854775707\"", - sandbox - .invoke(&[ - "--id", - &id, - "--source=test", - "--", - "balance", - "--id", - &address.to_string() - ]) - .await - .unwrap(), - ); -} + println!("{id}, {address}"); + sandbox + .new_assert_cmd("contract") + .args([ + "invoke", + "--id", + &id, + "--", + "balance", + "--id", + &address.to_string(), + ]) + .assert() + .stdout("\"9223372036854775807\"\n"); + sandbox + .new_assert_cmd("contract") + .arg("invoke") + .args([ + "--id", + &id, + "--", + "authorized", + "--id", + &address.to_string(), + ]) + .assert() + .stdout("true\n"); + sandbox + .new_assert_cmd("contract") + .args([ + "invoke", + "--id", + &id, + "--", + "balance", + "--id", + &address.to_string(), + ]) + .assert() + .stdout("\"9223372036854775807\"\n"); + sandbox + .new_assert_cmd("contract") + .arg("invoke") + .env("RUST_LOGS", "trace") + .args([ + "--source=test", + "--id", + &id, + "--", + "burn", + "--from", + "test", + "--amount=100", + ]) + .assert() + .stdout("") + .stderr(""); -fn wrap_cmd(asset: &str) -> asset::Cmd { - asset::Cmd::parse_arg_vec(&["--source=test", &format!("--asset={asset}")]).unwrap() + println!("hi"); + sandbox + .new_assert_cmd("contract") + .args([ + "invoke", + "--id", + &id, + "--", + "balance", + "--id", + &address.to_string(), + ]) + .assert() + .stdout("\"9223372036854775707\"\n"); } diff --git a/cmd/crates/soroban-test/tests/it/main.rs b/cmd/crates/soroban-test/tests/it/main.rs index a6b18cb22..10aea449c 100644 --- a/cmd/crates/soroban-test/tests/it/main.rs +++ b/cmd/crates/soroban-test/tests/it/main.rs @@ -1,7 +1,7 @@ mod arg_parsing; mod config; mod help; -#[cfg(feature = "integration")] +#[cfg(feature = "it")] mod integration; mod plugin; mod util; diff --git a/cmd/crates/soroban-test/tests/it/util.rs b/cmd/crates/soroban-test/tests/it/util.rs index 112d5f841..c70091503 100644 --- a/cmd/crates/soroban-test/tests/it/util.rs +++ b/cmd/crates/soroban-test/tests/it/util.rs @@ -4,7 +4,7 @@ use soroban_cli::commands::{ config::{locator::KeyType, secret::Secret}, contract, }; -use soroban_test::{TestEnv, Wasm}; +use soroban_test::{TestEnv, Wasm, TEST_ACCOUNT}; pub const CUSTOM_TYPES: &Wasm = &Wasm::Custom("test-wasms", "test_custom_types"); @@ -44,7 +44,6 @@ pub fn add_test_id(dir: &Path) -> String { pub const DEFAULT_SEED_PHRASE: &str = "coral light army gather adapt blossom school alcohol coral light army giggle"; -#[allow(dead_code)] pub async fn invoke_custom( sandbox: &TestEnv, id: &str, @@ -52,21 +51,9 @@ pub async fn invoke_custom( arg: &str, wasm: &Path, ) -> Result { - let mut i: contract::invoke::Cmd = sandbox.cmd_arr(&[ - "--id", - id, - "--network", - "futurenet", - "--source", - "default", - "--", - func, - arg, - ]); + let mut i: contract::invoke::Cmd = sandbox.cmd_with_config(&["--id", id, "--", func, arg]); i.wasm = Some(wasm.to_path_buf()); - i.config.network.network = Some("futurenet".to_owned()); - i.invoke(&soroban_cli::commands::global::Args::default()) - .await + sandbox.run_cmd_with(i, TEST_ACCOUNT).await } pub const DEFAULT_CONTRACT_ID: &str = "CDR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OO5Z"; diff --git a/cmd/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml index e20a6458f..846b4a36a 100644 --- a/cmd/soroban-cli/Cargo.toml +++ b/cmd/soroban-cli/Cargo.toml @@ -48,13 +48,13 @@ soroban-sdk = { workspace = true } soroban-rpc = { workspace = true } clap = { workspace = true, features = [ - "derive", - "env", - "deprecated", - "string", + "derive", + "env", + "deprecated", + "string", ] } -clap_complete = {workspace = true} - +clap_complete = { workspace = true } +async-trait = { workspace = true } base64 = { workspace = true } thiserror = { workspace = true } serde = { workspace = true, features = ["derive"] } @@ -101,7 +101,7 @@ gix = { version = "0.58.0", default-features = false, features = [ "blocking-http-transport-reqwest-rust-tls", "worktree-mutation", ] } -ureq = {version = "2.9.1", features = ["json"]} +ureq = { version = "2.9.1", features = ["json"] } tempfile = "3.8.1" toml_edit = "0.21.0" diff --git a/cmd/soroban-cli/src/bin/main.rs b/cmd/soroban-cli/src/bin/main.rs index 7a87099c0..70eeb6110 100644 --- a/cmd/soroban-cli/src/bin/main.rs +++ b/cmd/soroban-cli/src/bin/main.rs @@ -37,6 +37,7 @@ async fn main() { let builder = fmt::Subscriber::builder() .with_env_filter(e_filter) + .with_ansi(false) .with_writer(std::io::stderr); let subscriber = builder.finish(); diff --git a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs index 173e9b06f..dbca1940c 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs @@ -12,7 +12,7 @@ use std::convert::Infallible; use std::{array::TryFromSliceError, fmt::Debug, num::ParseIntError}; use crate::{ - commands::config, + commands::{config, global, NetworkRunnable}, rpc::{Client, Error as SorobanRpcError}, utils::{contract_id_hash_from_asset, parsing::parse_asset}, }; @@ -58,21 +58,32 @@ pub struct Cmd { impl Cmd { pub async fn run(&self) -> Result<(), Error> { - // Parse asset - let asset = parse_asset(&self.asset)?; - - let res_str = self.run_against_rpc_server(asset).await?; + let res_str = self.run_against_rpc_server(None, None).await?; println!("{res_str}"); Ok(()) } +} + +#[async_trait::async_trait] +impl NetworkRunnable for Cmd { + type Error = Error; + type Result = String; + + async fn run_against_rpc_server( + &self, + _: Option<&global::Args>, + config: Option<&config::Args>, + ) -> Result { + let config = config.unwrap_or(&self.config); + // Parse asset + let asset = parse_asset(&self.asset)?; - async fn run_against_rpc_server(&self, asset: Asset) -> Result { - let network = self.config.get_network()?; + let network = config.get_network()?; let client = Client::new(&network.rpc_url)?; client .verify_network_passphrase(Some(&network.network_passphrase)) .await?; - let key = self.config.key_pair()?; + let key = config.key_pair()?; // Get the account sequence number let public_strkey = diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index 0797ac175..bac7fe2f0 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -14,7 +14,10 @@ use soroban_env_host::{ HostError, }; -use crate::commands::contract::{self, id::wasm::get_contract_id}; +use crate::commands::{ + contract::{self, id::wasm::get_contract_id}, + global, NetworkRunnable, +}; use crate::{ commands::{config, contract::install, HEADING_RPC}, rpc::{self, Client}, @@ -92,20 +95,31 @@ pub enum Error { impl Cmd { pub async fn run(&self) -> Result<(), Error> { - let res_str = self.run_and_get_contract_id().await?; + let res_str = self.run_against_rpc_server(None, None).await?; println!("{res_str}"); Ok(()) } +} - pub async fn run_and_get_contract_id(&self) -> Result { +#[async_trait::async_trait] +impl NetworkRunnable for Cmd { + type Error = Error; + type Result = String; + + async fn run_against_rpc_server( + &self, + global_args: Option<&global::Args>, + config: Option<&config::Args>, + ) -> Result { + let config = config.unwrap_or(&self.config); let wasm_hash = if let Some(wasm) = &self.wasm { let hash = install::Cmd { wasm: wasm::Args { wasm: wasm.clone() }, - config: self.config.clone(), + config: config.clone(), fee: self.fee.clone(), ignore_checks: self.ignore_checks, } - .run_and_get_hash() + .run_against_rpc_server(global_args, Some(config)) .await?; hex::encode(hash) } else { @@ -115,18 +129,13 @@ impl Cmd { .to_string() }; - let hash = Hash(utils::contract_id_from_str(&wasm_hash).map_err(|e| { + let wasm_hash = Hash(utils::contract_id_from_str(&wasm_hash).map_err(|e| { Error::CannotParseWasmHash { wasm_hash: wasm_hash.clone(), error: e, } })?); - - self.run_against_rpc_server(hash).await - } - - async fn run_against_rpc_server(&self, wasm_hash: Hash) -> Result { - let network = self.config.get_network()?; + let network = config.get_network()?; let salt: [u8; 32] = match &self.salt { Some(h) => soroban_spec_tools::utils::padded_hex_from_str(h, 32) .map_err(|_| Error::CannotParseSalt { salt: h.clone() })? @@ -139,7 +148,7 @@ impl Cmd { client .verify_network_passphrase(Some(&network.network_passphrase)) .await?; - let key = self.config.key_pair()?; + let key = config.key_pair()?; // Get the account sequence number let public_strkey = diff --git a/cmd/soroban-cli/src/commands/contract/extend.rs b/cmd/soroban-cli/src/commands/contract/extend.rs index 965eae4fe..bcf8a90d5 100644 --- a/cmd/soroban-cli/src/commands/contract/extend.rs +++ b/cmd/soroban-cli/src/commands/contract/extend.rs @@ -9,7 +9,7 @@ use soroban_env_host::xdr::{ }; use crate::{ - commands::config, + commands::{config, global, NetworkRunnable}, key, rpc::{self, Client}, wasm, Pwd, @@ -80,7 +80,7 @@ pub enum Error { impl Cmd { #[allow(clippy::too_many_lines)] pub async fn run(&self) -> Result<(), Error> { - let ttl_ledger = self.run_against_rpc_server().await?; + let ttl_ledger = self.run_against_rpc_server(None, None).await?; if self.ttl_ledger_only { println!("{ttl_ledger}"); } else { @@ -99,14 +99,25 @@ impl Cmd { } res } +} - async fn run_against_rpc_server(&self) -> Result { - let network = self.config.get_network()?; +#[async_trait::async_trait] +impl NetworkRunnable for Cmd { + type Error = Error; + type Result = u32; + + async fn run_against_rpc_server( + &self, + _args: Option<&global::Args>, + config: Option<&config::Args>, + ) -> Result { + let config = config.unwrap_or(&self.config); + let network = config.get_network()?; tracing::trace!(?network); let keys = self.key.parse_keys()?; - let network = &self.config.get_network()?; + let network = &config.get_network()?; let client = Client::new(&network.rpc_url)?; - let key = self.config.key_pair()?; + let key = config.key_pair()?; let extend_to = self.ledgers_to_extend(); // Get the account sequence number diff --git a/cmd/soroban-cli/src/commands/contract/fetch.rs b/cmd/soroban-cli/src/commands/contract/fetch.rs index 61a82fc47..eefb1b4b8 100644 --- a/cmd/soroban-cli/src/commands/contract/fetch.rs +++ b/cmd/soroban-cli/src/commands/contract/fetch.rs @@ -21,6 +21,7 @@ use stellar_strkey::DecodeError; use super::super::config::{self, locator}; use crate::commands::network::{self, Network}; +use crate::commands::{global, NetworkRunnable}; use crate::{ rpc::{self, Client}, utils, Pwd, @@ -115,15 +116,29 @@ impl Cmd { } pub async fn get_bytes(&self) -> Result, Error> { - self.run_against_rpc_server().await + self.run_against_rpc_server(None, None).await } pub fn network(&self) -> Result { Ok(self.network.get(&self.locator)?) } - pub async fn run_against_rpc_server(&self) -> Result, Error> { - let network = self.network()?; + fn contract_id(&self) -> Result<[u8; 32], Error> { + utils::contract_id_from_str(&self.contract_id) + .map_err(|e| Error::CannotParseContractId(self.contract_id.clone(), e)) + } +} + +#[async_trait::async_trait] +impl NetworkRunnable for Cmd { + type Error = Error; + type Result = Vec; + async fn run_against_rpc_server( + &self, + _args: Option<&global::Args>, + config: Option<&config::Args>, + ) -> Result, Error> { + let network = config.map_or_else(|| self.network(), |c| Ok(c.get_network()?))?; tracing::trace!(?network); let contract_id = self.contract_id()?; let client = Client::new(&network.rpc_url)?; @@ -133,13 +148,7 @@ impl Cmd { // async closures are not yet stable Ok(client.get_remote_wasm(&contract_id).await?) } - - fn contract_id(&self) -> Result<[u8; 32], Error> { - utils::contract_id_from_str(&self.contract_id) - .map_err(|e| Error::CannotParseContractId(self.contract_id.clone(), e)) - } } - pub fn get_contract_wasm_from_storage( storage: &mut Storage, contract_id: [u8; 32], diff --git a/cmd/soroban-cli/src/commands/contract/init.rs b/cmd/soroban-cli/src/commands/contract/init.rs index ef25c08ad..b5a4ce2c0 100644 --- a/cmd/soroban-cli/src/commands/contract/init.rs +++ b/cmd/soroban-cli/src/commands/contract/init.rs @@ -82,7 +82,7 @@ fn get_valid_examples() -> Result, Error> { let body: ReqBody = get(GITHUB_API_URL) .call() .map_err(|e| { - eprintln!("Error fetching example contracts from soroban-examples repo"); + tracing::warn!("Error fetching example contracts from soroban-examples repo"); Box::new(e) })? .into_json()?; diff --git a/cmd/soroban-cli/src/commands/contract/install.rs b/cmd/soroban-cli/src/commands/contract/install.rs index 21743a6ee..47a47e1d2 100644 --- a/cmd/soroban-cli/src/commands/contract/install.rs +++ b/cmd/soroban-cli/src/commands/contract/install.rs @@ -4,12 +4,13 @@ use std::num::ParseIntError; use clap::{command, Parser}; use soroban_env_host::xdr::{ - Error as XdrError, Hash, HostFunction, InvokeHostFunctionOp, Memo, MuxedAccount, Operation, - OperationBody, Preconditions, ScMetaEntry, ScMetaV0, SequenceNumber, Transaction, + self, Error as XdrError, Hash, HostFunction, InvokeHostFunctionOp, Memo, MuxedAccount, + Operation, OperationBody, Preconditions, ScMetaEntry, ScMetaV0, SequenceNumber, Transaction, TransactionExt, TransactionResult, TransactionResultResult, Uint256, VecM, }; use super::restore; +use crate::commands::{global, NetworkRunnable}; use crate::key; use crate::rpc::{self, Client}; use crate::{commands::config, utils, wasm}; @@ -65,17 +66,24 @@ pub enum Error { impl Cmd { pub async fn run(&self) -> Result<(), Error> { - let res_str = hex::encode(self.run_and_get_hash().await?); + let res_str = hex::encode(self.run_against_rpc_server(None, None).await?); println!("{res_str}"); Ok(()) } +} - pub async fn run_and_get_hash(&self) -> Result { - self.run_against_rpc_server(&self.wasm.read()?).await - } - - async fn run_against_rpc_server(&self, contract: &[u8]) -> Result { - let network = self.config.get_network()?; +#[async_trait::async_trait] +impl NetworkRunnable for Cmd { + type Error = Error; + type Result = Hash; + async fn run_against_rpc_server( + &self, + args: Option<&global::Args>, + config: Option<&config::Args>, + ) -> Result { + let config = config.unwrap_or(&self.config); + let contract = self.wasm.read()?; + let network = config.get_network()?; let client = Client::new(&network.rpc_url)?; client .verify_network_passphrase(Some(&network.network_passphrase)) @@ -100,7 +108,7 @@ impl Cmd { tracing::warn!("the deployed smart contract {path} was built with Soroban Rust SDK v{rs_sdk_ver}, a release candidate version not intended for use with the Stellar Public Network", path = self.wasm.wasm.display()); } } - let key = self.config.key_pair()?; + let key = config.key_pair()?; // Get the account sequence number let public_strkey = @@ -109,7 +117,14 @@ impl Cmd { let sequence: i64 = account_details.seq_num.into(); let (tx_without_preflight, hash) = - build_install_contract_code_tx(contract, sequence + 1, self.fee.fee, &key)?; + build_install_contract_code_tx(&contract, sequence + 1, self.fee.fee, &key)?; + + let code_key = + xdr::LedgerKey::ContractCode(xdr::LedgerKeyContractCode { hash: hash.clone() }); + let contract_data = client.get_ledger_entries(&[code_key]).await?; + if !contract_data.entries.unwrap_or_default().is_empty() { + return Ok(hash); + } let txn = client .create_assembled_transaction(&tx_without_preflight) @@ -136,12 +151,12 @@ impl Cmd { wasm_hash: None, durability: super::Durability::Persistent, }, - config: self.config.clone(), + config: config.clone(), fee: self.fee.clone(), ledgers_to_extend: None, ttl_ledger_only: true, } - .run_against_rpc_server() + .run_against_rpc_server(args, None) .await?; } diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index c8692eca0..29637dcbd 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -28,6 +28,7 @@ use super::super::{ config::{self, locator}, events, }; +use crate::commands::NetworkRunnable; use crate::{commands::global, rpc, Pwd}; use soroban_spec_tools::{contract, Spec}; @@ -255,14 +256,45 @@ impl Cmd { } pub async fn invoke(&self, global_args: &global::Args) -> Result { - self.run_against_rpc_server(global_args).await + self.run_against_rpc_server(Some(global_args), None).await } - pub async fn run_against_rpc_server( + pub fn read_wasm(&self) -> Result>, Error> { + Ok(if let Some(wasm) = self.wasm.as_ref() { + Some(fs::read(wasm).map_err(|e| Error::CannotReadContractFile(wasm.clone(), e))?) + } else { + None + }) + } + + pub fn spec_entries(&self) -> Result>, Error> { + self.read_wasm()? + .map(|wasm| { + soroban_spec::read::from_wasm(&wasm).map_err(Error::CannotParseContractSpec) + }) + .transpose() + } +} + +impl Cmd { + fn contract_id(&self) -> Result<[u8; 32], Error> { + soroban_spec_tools::utils::contract_id_from_str(&self.contract_id) + .map_err(|e| Error::CannotParseContractId(self.contract_id.clone(), e)) + } +} + +#[async_trait::async_trait] +impl NetworkRunnable for Cmd { + type Error = Error; + type Result = String; + + async fn run_against_rpc_server( &self, - global_args: &global::Args, + global_args: Option<&global::Args>, + config: Option<&config::Args>, ) -> Result { - let network = self.config.get_network()?; + let config = config.unwrap_or(&self.config); + let network = config.get_network()?; tracing::trace!(?network); let contract_id = self.contract_id()?; let spec_entries = self.spec_entries()?; @@ -274,7 +306,7 @@ impl Cmd { client .verify_network_passphrase(Some(&network.network_passphrase)) .await?; - let key = self.config.key_pair()?; + let key = config.key_pair()?; // Get the account sequence number let public_strkey = @@ -302,6 +334,11 @@ impl Cmd { txn.sim_response().events()?, ) } else { + let global::Args { + verbose, + very_verbose, + .. + } = global_args.map(Clone::clone).unwrap_or_default(); let res = client .send_assembled_transaction( txn, @@ -309,8 +346,7 @@ impl Cmd { &signers, &network.network_passphrase, Some(log_events), - (global_args.verbose || global_args.very_verbose || self.fee.cost) - .then_some(log_resources), + (verbose || very_verbose || self.fee.cost).then_some(log_resources), ) .await?; (res.return_value()?, res.contract_events()?) @@ -319,29 +355,6 @@ impl Cmd { crate::log::diagnostic_events(&events, tracing::Level::INFO); output_to_string(&spec, &return_value, &function) } - - pub fn read_wasm(&self) -> Result>, Error> { - Ok(if let Some(wasm) = self.wasm.as_ref() { - Some(fs::read(wasm).map_err(|e| Error::CannotReadContractFile(wasm.clone(), e))?) - } else { - None - }) - } - - pub fn spec_entries(&self) -> Result>, Error> { - self.read_wasm()? - .map(|wasm| { - soroban_spec::read::from_wasm(&wasm).map_err(Error::CannotParseContractSpec) - }) - .transpose() - } -} - -impl Cmd { - fn contract_id(&self) -> Result<[u8; 32], Error> { - soroban_spec_tools::utils::contract_id_from_str(&self.contract_id) - .map_err(|e| Error::CannotParseContractId(self.contract_id.clone(), e)) - } } fn log_events( diff --git a/cmd/soroban-cli/src/commands/contract/read.rs b/cmd/soroban-cli/src/commands/contract/read.rs index f25b6c2c0..a7b1d07a8 100644 --- a/cmd/soroban-cli/src/commands/contract/read.rs +++ b/cmd/soroban-cli/src/commands/contract/read.rs @@ -14,7 +14,7 @@ use soroban_env_host::{ use soroban_sdk::xdr::Limits; use crate::{ - commands::config, + commands::{config, global, NetworkRunnable}, key, rpc::{self, Client, FullLedgerEntries, FullLedgerEntry}, }; @@ -91,19 +91,10 @@ pub enum Error { impl Cmd { pub async fn run(&self) -> Result<(), Error> { - let entries = self.run_against_rpc_server().await?; + let entries = self.run_against_rpc_server(None, None).await?; self.output_entries(&entries) } - async fn run_against_rpc_server(&self) -> Result { - let network = self.config.get_network()?; - tracing::trace!(?network); - let network = &self.config.get_network()?; - let client = Client::new(&network.rpc_url)?; - let keys = self.key.parse_keys()?; - Ok(client.get_full_ledger_entries(&keys).await?) - } - fn output_entries(&self, entries: &FullLedgerEntries) -> Result<(), Error> { if entries.entries.is_empty() { return Err(Error::NoContractDataEntryFoundForContractID); @@ -178,3 +169,21 @@ impl Cmd { Ok(()) } } + +#[async_trait::async_trait] +impl NetworkRunnable for Cmd { + type Error = Error; + type Result = FullLedgerEntries; + async fn run_against_rpc_server( + &self, + _: Option<&global::Args>, + config: Option<&config::Args>, + ) -> Result { + let config = config.unwrap_or(&self.config); + let network = config.get_network()?; + tracing::trace!(?network); + let client = Client::new(&network.rpc_url)?; + let keys = self.key.parse_keys()?; + 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 1385068cc..869d26015 100644 --- a/cmd/soroban-cli/src/commands/contract/restore.rs +++ b/cmd/soroban-cli/src/commands/contract/restore.rs @@ -13,6 +13,7 @@ use crate::{ commands::{ config::{self, locator}, contract::extend, + global, NetworkRunnable, }, key, rpc::{self, Client}, @@ -87,7 +88,7 @@ pub enum Error { impl Cmd { #[allow(clippy::too_many_lines)] pub async fn run(&self) -> Result<(), Error> { - let expiration_ledger_seq = self.run_against_rpc_server().await?; + let expiration_ledger_seq = self.run_against_rpc_server(None, None).await?; if let Some(ledgers_to_extend) = self.ledgers_to_extend { extend::Cmd { @@ -105,14 +106,24 @@ impl Cmd { Ok(()) } +} - pub async fn run_against_rpc_server(&self) -> Result { - let network = self.config.get_network()?; +#[async_trait::async_trait] +impl NetworkRunnable for Cmd { + type Error = Error; + type Result = u32; + + async fn run_against_rpc_server( + &self, + _: Option<&global::Args>, + config: Option<&config::Args>, + ) -> Result { + let config = config.unwrap_or(&self.config); + let network = config.get_network()?; tracing::trace!(?network); let entry_keys = self.key.parse_keys()?; - let network = &self.config.get_network()?; let client = Client::new(&network.rpc_url)?; - let key = self.config.key_pair()?; + let key = config.key_pair()?; // Get the account sequence number let public_strkey = diff --git a/cmd/soroban-cli/src/commands/events.rs b/cmd/soroban-cli/src/commands/events.rs index aa46bbe23..42145f5bf 100644 --- a/cmd/soroban-cli/src/commands/events.rs +++ b/cmd/soroban-cli/src/commands/events.rs @@ -3,7 +3,10 @@ use std::io; use soroban_env_host::xdr::{self, Limits, ReadXdr}; -use super::{config::locator, network}; +use super::{ + config::{self, locator}, + global, network, NetworkRunnable, +}; use crate::{rpc, utils}; #[derive(Parser, Debug, Clone)] @@ -119,6 +122,8 @@ pub enum Error { Network(#[from] network::Error), #[error(transparent)] Locator(#[from] locator::Error), + #[error(transparent)] + Config(#[from] config::Error), } #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum)] @@ -162,7 +167,7 @@ impl Cmd { })?; } - let response = self.run_against_rpc_server().await?; + let response = self.run_against_rpc_server(None, None).await?; for event in &response.events { match self.output { @@ -189,9 +194,33 @@ impl Cmd { Ok(()) } - async fn run_against_rpc_server(&self) -> Result { + fn start(&self) -> Result { + let start = match (self.start_ledger, self.cursor.clone()) { + (Some(start), _) => rpc::EventStart::Ledger(start), + (_, Some(c)) => rpc::EventStart::Cursor(c), + // should never happen because of required_unless_present flags + _ => return Err(Error::MissingStartLedgerAndCursor), + }; + Ok(start) + } +} + +#[async_trait::async_trait] +impl NetworkRunnable for Cmd { + type Error = Error; + type Result = rpc::GetEventsResponse; + + async fn run_against_rpc_server( + &self, + _args: Option<&global::Args>, + config: Option<&config::Args>, + ) -> Result { let start = self.start()?; - let network = self.network.get(&self.locator)?; + let network = if let Some(config) = config { + Ok(config.get_network()?) + } else { + self.network.get(&self.locator) + }?; let client = rpc::Client::new(&network.rpc_url)?; client @@ -208,14 +237,4 @@ impl Cmd { .await .map_err(Error::Rpc) } - - fn start(&self) -> Result { - let start = match (self.start_ledger, self.cursor.clone()) { - (Some(start), _) => rpc::EventStart::Ledger(start), - (_, Some(c)) => rpc::EventStart::Cursor(c), - // should never happen because of required_unless_present flags - _ => return Err(Error::MissingStartLedgerAndCursor), - }; - Ok(start) - } } diff --git a/cmd/soroban-cli/src/commands/keys/generate.rs b/cmd/soroban-cli/src/commands/keys/generate.rs index 07782b216..159191dc3 100644 --- a/cmd/soroban-cli/src/commands/keys/generate.rs +++ b/cmd/soroban-cli/src/commands/keys/generate.rs @@ -66,9 +66,13 @@ impl Cmd { if !self.no_fund { let addr = secret.public_key(self.hd_path)?; let network = self.network.get(&self.config_locator)?; - network.fund_address(&addr).await.unwrap_or_else(|_| { - tracing::warn!("Failed to fund address: {addr} on at {}", network.rpc_url); - }); + network + .fund_address(&addr) + .await + .map_err(|e| { + tracing::warn!("fund_address failed: {e}"); + }) + .unwrap_or_default(); } Ok(()) } diff --git a/cmd/soroban-cli/src/commands/mod.rs b/cmd/soroban-cli/src/commands/mod.rs index 952869af3..ae3ac3ecb 100644 --- a/cmd/soroban-cli/src/commands/mod.rs +++ b/cmd/soroban-cli/src/commands/mod.rs @@ -1,5 +1,6 @@ use std::str::FromStr; +use async_trait::async_trait; use clap::{command, error::ErrorKind, CommandFactory, FromArgMatches, Parser}; pub mod completion; @@ -158,3 +159,15 @@ pub enum Error { #[error(transparent)] Network(#[from] network::Error), } + +#[async_trait] +pub trait NetworkRunnable { + type Error; + type Result; + + async fn run_against_rpc_server( + &self, + global_args: Option<&global::Args>, + config: Option<&config::Args>, + ) -> Result; +} diff --git a/cmd/soroban-cli/src/commands/network/mod.rs b/cmd/soroban-cli/src/commands/network/mod.rs index 22cba1904..a1c021722 100644 --- a/cmd/soroban-cli/src/commands/network/mod.rs +++ b/cmd/soroban-cli/src/commands/network/mod.rs @@ -12,6 +12,8 @@ use crate::{ use super::config::locator; +pub const LOCAL_NETWORK_PASSPHRASE: &str = "Standalone Network ; February 2017"; + pub mod add; pub mod ls; pub mod rm; @@ -43,6 +45,8 @@ pub enum Error { #[error("network arg or rpc url and network passphrase are required if using the network")] Network, #[error(transparent)] + Http(#[from] http::Error), + #[error(transparent)] Rpc(#[from] rpc::Error), #[error(transparent)] Hyper(#[from] hyper::Error), @@ -139,13 +143,27 @@ pub struct Network { impl Network { pub async fn helper_url(&self, addr: &str) -> Result { + use http::Uri; tracing::debug!("address {addr:?}"); - let client = Client::new(&self.rpc_url)?; - let helper_url_root = client.friendbot_url().await?; - let uri = http::Uri::from_str(&helper_url_root) - .map_err(|_| Error::InvalidUrl(helper_url_root.to_string()))?; - http::Uri::from_str(&format!("{uri:?}?addr={addr}")) - .map_err(|_| Error::InvalidUrl(helper_url_root.to_string())) + let rpc_uri = Uri::from_str(&self.rpc_url) + .map_err(|_| Error::InvalidUrl(self.rpc_url.to_string()))?; + if self.network_passphrase.as_str() == LOCAL_NETWORK_PASSPHRASE { + let auth = rpc_uri.authority().unwrap().clone(); + let scheme = rpc_uri.scheme_str().unwrap(); + // format!("{scheme}://{auth}/friendbot"); + Ok(Uri::builder() + .authority(auth) + .scheme(scheme) + .path_and_query(format!("/friendbot?addr={addr}")) + .build()?) + } else { + let client = Client::new(&self.rpc_url)?; + let uri = client.friendbot_url().await?; + Uri::from_str(&format!("{uri:?}?addr={addr}")).map_err(|e| { + tracing::error!("{e}"); + Error::InvalidUrl(uri.to_string()) + }) + } } #[allow(clippy::similar_names)] diff --git a/cmd/soroban-cli/src/fee.rs b/cmd/soroban-cli/src/fee.rs index 70f427bb9..353fe6e5d 100644 --- a/cmd/soroban-cli/src/fee.rs +++ b/cmd/soroban-cli/src/fee.rs @@ -1,4 +1,5 @@ use clap::arg; +use soroban_env_host::xdr; use soroban_rpc::Assembled; use crate::commands::HEADING_RPC; @@ -22,11 +23,24 @@ impl Args { if let Some(instructions) = self.instructions { txn.set_max_instructions(instructions) } else { - txn + add_padding_to_instructions(txn) } } } +pub fn add_padding_to_instructions(txn: Assembled) -> Assembled { + let xdr::TransactionExt::V1(xdr::SorobanTransactionData { + resources: xdr::SorobanResources { instructions, .. }, + .. + }) = txn.transaction().ext + else { + return txn; + }; + // Start with 150% + let instructions = (instructions.checked_mul(150 / 100)).unwrap_or(instructions); + txn.set_max_instructions(instructions) +} + impl Default for Args { fn default() -> Self { Self { From beb9deaf1fe1dc6b17d67e3c143746dcb9fb8623 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Fri, 1 Mar 2024 16:09:03 -0500 Subject: [PATCH 4/5] fix: #1229 (#1232) --- cmd/soroban-cli/src/commands/contract/init.rs | 77 ++++++++----------- 1 file changed, 30 insertions(+), 47 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/init.rs b/cmd/soroban-cli/src/commands/contract/init.rs index b5a4ce2c0..487a721a0 100644 --- a/cmd/soroban-cli/src/commands/contract/init.rs +++ b/cmd/soroban-cli/src/commands/contract/init.rs @@ -14,15 +14,12 @@ use clap::{ }; use gix::{clone, create, open, progress, remote}; use rust_embed::RustEmbed; -use serde::Deserialize; use serde_json::{from_str, json, Error as JsonError, Value as JsonValue}; use toml_edit::{Document, Formatted, InlineTable, Item, TomlError, Value as TomlValue}; use ureq::{get, Error as UreqError}; const SOROBAN_EXAMPLES_URL: &str = "https://github.com/stellar/soroban-examples.git"; const GITHUB_URL: &str = "https://github.com"; -const GITHUB_API_URL: &str = - "https://api.github.com/repos/stellar/soroban-examples/git/trees/main?recursive=1"; #[derive(Clone, Debug, ValueEnum, PartialEq)] pub enum FrontendTemplate { @@ -48,14 +45,36 @@ pub struct Cmd { } fn possible_example_values() -> ValueParser { - // If fetching the example contracts from the soroban-examples repo succeeds, return a parser with the example contracts. - if let Ok(examples) = get_valid_examples() { - let parser = PossibleValuesParser::new(examples.iter().map(PossibleValue::new)); - return parser.into(); - } - - // If fetching with example contracts fails, return a string parser that will allow for any value. It will be ignored in `init`. - ValueParser::string() + let parser = PossibleValuesParser::new( + [ + "account", + "alloc", + "atomic_multiswap", + "atomic_swap", + "auth", + "cross_contract", + "custom_types", + "deep_contract_auth", + "deployer", + "errors", + "eth_abi", + "events", + "fuzzing", + "increment", + "liquidity_pool", + "logging", + "mint-lock", + "simple_account", + "single_offer", + "timelock", + "token", + "upgradeable_contract", + "workspace", + ] + .iter() + .map(PossibleValue::new), + ); + parser.into() } fn with_example_help() -> String { @@ -66,42 +85,6 @@ fn with_example_help() -> String { } } -#[derive(Deserialize, Debug)] -struct RepoPath { - path: String, - #[serde(rename = "type")] - type_field: String, -} - -#[derive(Deserialize, Debug)] -struct ReqBody { - tree: Vec, -} - -fn get_valid_examples() -> Result, Error> { - let body: ReqBody = get(GITHUB_API_URL) - .call() - .map_err(|e| { - tracing::warn!("Error fetching example contracts from soroban-examples repo"); - Box::new(e) - })? - .into_json()?; - let mut valid_examples = Vec::new(); - for item in body.tree { - if item.type_field == "blob" - || item.path.starts_with('.') - || item.path.contains('/') - || item.path == "hello_world" - { - continue; - } - - valid_examples.push(item.path); - } - - Ok(valid_examples) -} - #[derive(thiserror::Error, Debug)] pub enum Error { #[error("Io error: {0}")] From 0ccbab3f38cc67e56006c9f8d8f2251f7d47a964 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Fri, 1 Mar 2024 16:37:26 -0500 Subject: [PATCH 5/5] fix: clippy (#1233) --- cmd/crates/soroban-spec-tools/src/lib.rs | 8 +-- .../tests/fixtures/test-wasms/swap/src/lib.rs | 11 ++-- .../fixtures/test-wasms/swap/src/test.rs | 8 +-- .../fixtures/test-wasms/token/src/admin.rs | 2 +- .../test-wasms/token/src/allowance.rs | 29 ++++----- .../fixtures/test-wasms/token/src/balance.rs | 20 +++--- .../fixtures/test-wasms/token/src/contract.rs | 65 ++++++++----------- .../fixtures/test-wasms/token/src/lib.rs | 2 +- .../fixtures/test-wasms/token/src/metadata.rs | 8 +-- cmd/soroban-cli/src/commands/contract/init.rs | 12 ++-- 10 files changed, 71 insertions(+), 94 deletions(-) diff --git a/cmd/crates/soroban-spec-tools/src/lib.rs b/cmd/crates/soroban-spec-tools/src/lib.rs index bc3c2a386..e6d496437 100644 --- a/cmd/crates/soroban-spec-tools/src/lib.rs +++ b/cmd/crates/soroban-spec-tools/src/lib.rs @@ -1429,8 +1429,7 @@ mod tests { 0xc7, 0x79, 0xe4, 0xfe, 0x66, 0xe5, 0x6a, 0x24, 0x70, 0xdc, 0x98, 0xc0, 0xec, 0x9c, 0x07, 0x3d, 0x05, 0xc7, 0xb1, 0x03, ] - .try_into() - .unwrap() + .into() )) ), Err(e) => panic!("Unexpected error: {e}"), @@ -1441,7 +1440,7 @@ mod tests { Ok(addr) => assert_eq!( addr, ScVal::Address(ScAddress::Account(AccountId( - PublicKey::PublicKeyTypeEd25519([0; 32].try_into().unwrap()) + PublicKey::PublicKeyTypeEd25519([0; 32].into()) ))) ), Err(e) => panic!("Unexpected error: {e}"), @@ -1458,8 +1457,7 @@ mod tests { 0xc7, 0x79, 0xe4, 0xfe, 0x66, 0xe5, 0x6a, 0x24, 0x70, 0xdc, 0x98, 0xc0, 0xec, 0x9c, 0x07, 0x3d, 0x05, 0xc7, 0xb1, 0x03, ] - .try_into() - .unwrap() + .into() ) ))) ), diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/swap/src/lib.rs b/cmd/crates/soroban-test/tests/fixtures/test-wasms/swap/src/lib.rs index 528b84227..6b899e5df 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/swap/src/lib.rs +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/swap/src/lib.rs @@ -14,6 +14,7 @@ impl AtomicSwapContract { // Swap token A for token B atomically. Settle for the minimum requested price // for each party (this is an arbitrary choice; both parties could have // received the full amount as well). + #[allow(clippy::too_many_arguments)] pub fn swap( env: Env, a: Address, @@ -26,12 +27,8 @@ impl AtomicSwapContract { min_a_for_b: i128, ) { // Verify preconditions on the minimum price for both parties. - if amount_b < min_b_for_a { - panic!("not enough token B for token A"); - } - if amount_a < min_a_for_b { - panic!("not enough token A for token B"); - } + assert!(amount_b >= min_b_for_a, "not enough token B for token A"); + assert!(amount_a >= min_a_for_b, "not enough token A for token B"); // Require authorization for a subset of arguments specific to a party. // Notice, that arguments are symmetric - there is no difference between // `a` and `b` in the call and hence their signatures can be used @@ -70,7 +67,7 @@ fn move_token( token.transfer( &contract_address, from, - &(&max_spend_amount - &transfer_amount), + &(max_spend_amount - transfer_amount), ); } diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/swap/src/test.rs b/cmd/crates/soroban-test/tests/fixtures/test-wasms/swap/src/test.rs index cf7929ef3..e32b45e0a 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/swap/src/test.rs +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/swap/src/test.rs @@ -32,10 +32,10 @@ fn test_atomic_swap() { let token_admin = Address::generate(&env); - let (token_a, token_a_admin) = create_token_contract(&env, &token_admin); - let (token_b, token_b_admin) = create_token_contract(&env, &token_admin); - token_a_admin.mint(&a, &1000); - token_b_admin.mint(&b, &5000); + let (token_a, a_admin) = create_token_contract(&env, &token_admin); + let (token_b, b_admin) = create_token_contract(&env, &token_admin); + a_admin.mint(&a, &1000); + b_admin.mint(&b, &5000); let contract = create_atomic_swap_contract(&env); diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/admin.rs b/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/admin.rs index a820bf040..c39b341b1 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/admin.rs +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/admin.rs @@ -2,7 +2,7 @@ use soroban_sdk::{Address, Env}; use crate::storage_types::DataKey; -pub fn has_administrator(e: &Env) -> bool { +pub fn has(e: &Env) -> bool { let key = DataKey::Admin; e.storage().instance().has(&key) } diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/allowance.rs b/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/allowance.rs index ad7468716..a414fc2e3 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/allowance.rs +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/allowance.rs @@ -1,7 +1,7 @@ use crate::storage_types::{AllowanceDataKey, AllowanceValue, DataKey}; use soroban_sdk::{Address, Env}; -pub fn read_allowance(e: &Env, from: Address, spender: Address) -> AllowanceValue { +pub fn read(e: &Env, from: Address, spender: Address) -> AllowanceValue { let key = DataKey::Allowance(AllowanceDataKey { from, spender }); if let Some(allowance) = e.storage().temporary().get::<_, AllowanceValue>(&key) { if allowance.expiration_ledger < e.ledger().sequence() { @@ -20,21 +20,16 @@ pub fn read_allowance(e: &Env, from: Address, spender: Address) -> AllowanceValu } } -pub fn write_allowance( - e: &Env, - from: Address, - spender: Address, - amount: i128, - expiration_ledger: u32, -) { +pub fn write(e: &Env, from: Address, spender: Address, amount: i128, expiration_ledger: u32) { let allowance = AllowanceValue { amount, expiration_ledger, }; - if amount > 0 && expiration_ledger < e.ledger().sequence() { - panic!("expiration_ledger is less than ledger seq when amount > 0") - } + assert!( + !(amount > 0 && expiration_ledger < e.ledger().sequence()), + "expiration_ledger is less than ledger seq when amount > 0" + ); let key = DataKey::Allowance(AllowanceDataKey { from, spender }); e.storage().temporary().set(&key.clone(), &allowance); @@ -44,16 +39,14 @@ pub fn write_allowance( .checked_sub(e.ledger().sequence()) .unwrap(); - e.storage().temporary().extend_ttl(&key, live_for, live_for) + e.storage().temporary().extend_ttl(&key, live_for, live_for); } } -pub fn spend_allowance(e: &Env, from: Address, spender: Address, amount: i128) { - let allowance = read_allowance(e, from.clone(), spender.clone()); - if allowance.amount < amount { - panic!("insufficient allowance"); - } - write_allowance( +pub fn spend(e: &Env, from: Address, spender: Address, amount: i128) { + let allowance = read(e, from.clone(), spender.clone()); + assert!(allowance.amount >= amount, "insufficient allowance"); + write( e, from, spender, diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/balance.rs b/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/balance.rs index 76134e8d8..56d824c45 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/balance.rs +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/balance.rs @@ -1,7 +1,7 @@ use crate::storage_types::{DataKey, BALANCE_BUMP_AMOUNT, BALANCE_LIFETIME_THRESHOLD}; use soroban_sdk::{Address, Env}; -pub fn read_balance(e: &Env, addr: Address) -> i128 { +pub fn read(e: &Env, addr: Address) -> i128 { let key = DataKey::Balance(addr); if let Some(balance) = e.storage().persistent().get::(&key) { e.storage() @@ -13,7 +13,7 @@ pub fn read_balance(e: &Env, addr: Address) -> i128 { } } -fn write_balance(e: &Env, addr: Address, amount: i128) { +fn write(e: &Env, addr: Address, amount: i128) { let key = DataKey::Balance(addr); e.storage().persistent().set(&key, &amount); e.storage() @@ -21,15 +21,13 @@ fn write_balance(e: &Env, addr: Address, amount: i128) { .extend_ttl(&key, BALANCE_LIFETIME_THRESHOLD, BALANCE_BUMP_AMOUNT); } -pub fn receive_balance(e: &Env, addr: Address, amount: i128) { - let balance = read_balance(e, addr.clone()); - write_balance(e, addr, balance + amount); +pub fn receive(e: &Env, addr: Address, amount: i128) { + let balance = read(e, addr.clone()); + write(e, addr, balance + amount); } -pub fn spend_balance(e: &Env, addr: Address, amount: i128) { - let balance = read_balance(e, addr.clone()); - if balance < amount { - panic!("insufficient balance"); - } - write_balance(e, addr, balance - amount); +pub fn spend(e: &Env, addr: Address, amount: i128) { + let balance = read(e, addr.clone()); + assert!(balance >= amount, "insufficient balance"); + write(e, addr, balance - amount); } diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/contract.rs b/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/contract.rs index cc5690c6c..519a8fdea 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/contract.rs +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/contract.rs @@ -1,19 +1,14 @@ //! This contract demonstrates a sample implementation of the Soroban token //! interface. -use crate::admin::{has_administrator, read_administrator, write_administrator}; -use crate::allowance::{read_allowance, spend_allowance, write_allowance}; -use crate::balance::{read_balance, receive_balance, spend_balance}; -use crate::metadata::{read_decimal, read_name, read_symbol, write_metadata}; use crate::storage_types::{INSTANCE_BUMP_AMOUNT, INSTANCE_LIFETIME_THRESHOLD}; +use crate::{admin, allowance, balance, metadata}; use soroban_sdk::token::{self, Interface as _}; use soroban_sdk::{contract, contractimpl, Address, Env, String}; use soroban_token_sdk::metadata::TokenMetadata; use soroban_token_sdk::TokenUtils; fn check_nonnegative_amount(amount: i128) { - if amount < 0 { - panic!("negative amount is not allowed: {}", amount) - } + assert!(amount >= 0, "negative amount is not allowed: {amount}"); } #[contract] @@ -22,46 +17,42 @@ pub struct Token; #[contractimpl] impl Token { pub fn initialize(e: Env, admin: Address, decimal: u32, name: String, symbol: String) { - if has_administrator(&e) { - panic!("already initialized") - } - write_administrator(&e, &admin); - if decimal > u8::MAX.into() { - panic!("Decimal must fit in a u8"); - } - - write_metadata( + assert!(!admin::has(&e), "already initialized"); + admin::write_administrator(&e, &admin); + assert!(decimal <= u8::MAX.into(), "Decimal must fit in a u8"); + + metadata::write( &e, TokenMetadata { decimal, name, symbol, }, - ) + ); } pub fn mint(e: Env, to: Address, amount: i128) { check_nonnegative_amount(amount); - let admin = read_administrator(&e); + let admin = admin::read_administrator(&e); admin.require_auth(); e.storage() .instance() .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); - receive_balance(&e, to.clone(), amount); + balance::receive(&e, to.clone(), amount); TokenUtils::new(&e).events().mint(admin, to, amount); } pub fn set_admin(e: Env, new_admin: Address) { - let admin = read_administrator(&e); + let admin = admin::read_administrator(&e); admin.require_auth(); e.storage() .instance() .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); - write_administrator(&e, &new_admin); + admin::write_administrator(&e, &new_admin); TokenUtils::new(&e).events().set_admin(admin, new_admin); } } @@ -72,7 +63,7 @@ impl token::Interface for Token { e.storage() .instance() .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); - read_allowance(&e, from, spender).amount + allowance::read(&e, from, spender).amount } fn approve(e: Env, from: Address, spender: Address, amount: i128, expiration_ledger: u32) { @@ -84,7 +75,7 @@ impl token::Interface for Token { .instance() .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); - write_allowance(&e, from.clone(), spender.clone(), amount, expiration_ledger); + allowance::write(&e, from.clone(), spender.clone(), amount, expiration_ledger); TokenUtils::new(&e) .events() .approve(from, spender, amount, expiration_ledger); @@ -94,7 +85,7 @@ impl token::Interface for Token { e.storage() .instance() .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); - read_balance(&e, id) + balance::read(&e, id) } fn transfer(e: Env, from: Address, to: Address, amount: i128) { @@ -106,8 +97,8 @@ impl token::Interface for Token { .instance() .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); - spend_balance(&e, from.clone(), amount); - receive_balance(&e, to.clone(), amount); + balance::spend(&e, from.clone(), amount); + balance::receive(&e, to.clone(), amount); TokenUtils::new(&e).events().transfer(from, to, amount); } @@ -120,10 +111,10 @@ impl token::Interface for Token { .instance() .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); - spend_allowance(&e, from.clone(), spender, amount); - spend_balance(&e, from.clone(), amount); - receive_balance(&e, to.clone(), amount); - TokenUtils::new(&e).events().transfer(from, to, amount) + allowance::spend(&e, from.clone(), spender, amount); + balance::spend(&e, from.clone(), amount); + balance::receive(&e, to.clone(), amount); + TokenUtils::new(&e).events().transfer(from, to, amount); } fn burn(e: Env, from: Address, amount: i128) { @@ -135,7 +126,7 @@ impl token::Interface for Token { .instance() .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); - spend_balance(&e, from.clone(), amount); + balance::spend(&e, from.clone(), amount); TokenUtils::new(&e).events().burn(from, amount); } @@ -148,20 +139,20 @@ impl token::Interface for Token { .instance() .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); - spend_allowance(&e, from.clone(), spender, amount); - spend_balance(&e, from.clone(), amount); - TokenUtils::new(&e).events().burn(from, amount) + allowance::spend(&e, from.clone(), spender, amount); + balance::spend(&e, from.clone(), amount); + TokenUtils::new(&e).events().burn(from, amount); } fn decimals(e: Env) -> u32 { - read_decimal(&e) + metadata::decimal(&e) } fn name(e: Env) -> String { - read_name(&e) + metadata::name(&e) } fn symbol(e: Env) -> String { - read_symbol(&e) + metadata::symbol(&e) } } diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/lib.rs b/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/lib.rs index b5f04e4dc..5151c189f 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/lib.rs +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/lib.rs @@ -3,7 +3,7 @@ mod admin; mod allowance; mod balance; -mod contract; +pub mod contract; mod metadata; mod storage_types; mod test; diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/metadata.rs b/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/metadata.rs index 715feeeaa..0ae7c4076 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/metadata.rs +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/metadata.rs @@ -1,22 +1,22 @@ use soroban_sdk::{Env, String}; use soroban_token_sdk::{metadata::TokenMetadata, TokenUtils}; -pub fn read_decimal(e: &Env) -> u32 { +pub fn decimal(e: &Env) -> u32 { let util = TokenUtils::new(e); util.metadata().get_metadata().decimal } -pub fn read_name(e: &Env) -> String { +pub fn name(e: &Env) -> String { let util = TokenUtils::new(e); util.metadata().get_metadata().name } -pub fn read_symbol(e: &Env) -> String { +pub fn symbol(e: &Env) -> String { let util = TokenUtils::new(e); util.metadata().get_metadata().symbol } -pub fn write_metadata(e: &Env, metadata: TokenMetadata) { +pub fn write(e: &Env, metadata: TokenMetadata) { let util = TokenUtils::new(e); util.metadata().set_metadata(&metadata); } diff --git a/cmd/soroban-cli/src/commands/contract/init.rs b/cmd/soroban-cli/src/commands/contract/init.rs index 487a721a0..26b1535f3 100644 --- a/cmd/soroban-cli/src/commands/contract/init.rs +++ b/cmd/soroban-cli/src/commands/contract/init.rs @@ -132,7 +132,7 @@ struct TemplateFiles; fn init( project_path: &Path, - frontend_template: &String, + frontend_template: &str, with_examples: &[String], ) -> Result<(), Error> { // create a project dir, and copy the contents of the base template (contract-init-template) into it @@ -436,7 +436,7 @@ mod tests { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); let with_examples = vec![]; - init(project_dir.as_path(), &String::new(), &with_examples).unwrap(); + init(project_dir.as_path(), "", &with_examples).unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -455,7 +455,7 @@ mod tests { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); let with_examples = ["alloc".to_owned()]; - init(project_dir.as_path(), &String::new(), &with_examples).unwrap(); + init(project_dir.as_path(), "", &with_examples).unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -478,7 +478,7 @@ mod tests { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join("project"); let with_examples = ["account".to_owned(), "atomic_swap".to_owned()]; - init(project_dir.as_path(), &String::new(), &with_examples).unwrap(); + init(project_dir.as_path(), "", &with_examples).unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -502,7 +502,7 @@ mod tests { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join("project"); let with_examples = ["invalid_example".to_owned(), "atomic_swap".to_owned()]; - assert!(init(project_dir.as_path(), &String::new(), &with_examples,).is_err()); + assert!(init(project_dir.as_path(), "", &with_examples,).is_err()); temp_dir.close().unwrap(); } @@ -514,7 +514,7 @@ mod tests { let with_examples = vec![]; init( project_dir.as_path(), - &"https://github.com/AhaLabs/soroban-astro-template".to_string(), + "https://github.com/AhaLabs/soroban-astro-template", &with_examples, ) .unwrap();