From 25849dadd8d58fa434265a7631002c21606c9cec Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 2 May 2024 11:49:42 -0400 Subject: [PATCH 01/30] feat: --no-build command allows returning built transaction --sim-only for assembling transactions --- cmd/crates/soroban-test/src/lib.rs | 35 ++++--- .../tests/it/integration/hello_world.rs | 24 ++++- .../src/commands/contract/deploy/asset.rs | 15 ++- .../src/commands/contract/deploy/wasm.rs | 24 +++-- .../src/commands/contract/extend.rs | 20 ++-- .../src/commands/contract/fetch.rs | 14 ++- .../src/commands/contract/install.rs | 18 ++-- .../src/commands/contract/invoke.rs | 99 ++++++++++++------- cmd/soroban-cli/src/commands/contract/read.rs | 14 ++- .../src/commands/contract/restore.rs | 26 +++-- cmd/soroban-cli/src/commands/events.rs | 33 ++++--- cmd/soroban-cli/src/commands/mod.rs | 4 +- cmd/soroban-cli/src/commands/txn_result.rs | 56 +++++++++++ cmd/soroban-cli/src/fee.rs | 25 ++++- cmd/soroban-cli/src/lib.rs | 3 +- docs/soroban-cli-full-docs.md | 51 ++++++++++ 16 files changed, 354 insertions(+), 107 deletions(-) create mode 100644 cmd/soroban-cli/src/commands/txn_result.rs diff --git a/cmd/crates/soroban-test/src/lib.rs b/cmd/crates/soroban-test/src/lib.rs index e4d7410ce..835d2066b 100644 --- a/cmd/crates/soroban-test/src/lib.rs +++ b/cmd/crates/soroban-test/src/lib.rs @@ -236,22 +236,25 @@ impl TestEnv { }, 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, - no_cache: false, - }), - Some(&config), - ) - .await + Ok(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, + no_cache: false, + }), + Some(&config), + ) + .await? + .res() + .unwrap()) } /// Reference to current directory of the `TestEnv`. 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 be03afa8d..758c6fce0 100644 --- a/cmd/crates/soroban-test/tests/it/integration/hello_world.rs +++ b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs @@ -11,6 +11,18 @@ use crate::integration::util::extend_contract; use super::util::{deploy_hello, extend, HELLO_WORLD}; #[allow(clippy::too_many_lines)] +#[tokio::test] +async fn invoke_view_with_non_existent_source_account() { + let sandbox = &TestEnv::new(); + let id = deploy_hello(sandbox).await; + let world = "world"; + let mut cmd = hello_world_cmd(&id, world); + cmd.config.source_account = String::new(); + cmd.is_view = true; + let res = sandbox.run_cmd_with(cmd, "test").await.unwrap(); + assert_eq!(res, format!(r#"["Hello",{world:?}]"#)); +} + #[tokio::test] async fn invoke() { let sandbox = &TestEnv::new(); @@ -140,12 +152,16 @@ fn invoke_hello_world(sandbox: &TestEnv, id: &str) { .success(); } -async fn invoke_hello_world_with_lib(e: &TestEnv, id: &str) { - let cmd = contract::invoke::Cmd { +fn hello_world_cmd(id: &str, arg: &str) -> contract::invoke::Cmd { + contract::invoke::Cmd { contract_id: id.to_string(), - slop: vec!["hello".into(), "--world=world".into()], + slop: vec!["hello".into(), format!("--world={arg}").into()], ..Default::default() - }; + } +} + +async fn invoke_hello_world_with_lib(e: &TestEnv, id: &str) { + let cmd = hello_world_cmd(id, "world"); let res = e.run_cmd_with(cmd, "test").await.unwrap(); assert_eq!(res, r#"["Hello","world"]"#); } diff --git a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs index 41e7367dc..65557eda9 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs @@ -14,7 +14,9 @@ use std::{array::TryFromSliceError, fmt::Debug, num::ParseIntError}; use crate::{ commands::{ config::{self, data}, - global, network, NetworkRunnable, + global, network, + txn_result::TxnResult, + NetworkRunnable, }, rpc::{Client, Error as SorobanRpcError}, utils::{contract_id_hash_from_asset, parsing::parse_asset}, @@ -80,7 +82,7 @@ impl NetworkRunnable for Cmd { &self, args: Option<&global::Args>, config: Option<&config::Args>, - ) -> Result { + ) -> Result, Error> { let config = config.unwrap_or(&self.config); // Parse asset let asset = parse_asset(&self.asset)?; @@ -108,8 +110,11 @@ impl NetworkRunnable for Cmd { network_passphrase, &key, )?; + if self.fee.build_only { + return Ok(TxnResult::from_xdr(&tx)?); + } let txn = client.create_assembled_transaction(&tx).await?; - let txn = self.fee.apply_to_assembled_txn(txn); + let txn = self.fee.apply_to_assembled_txn(txn)?; let get_txn_resp = client .send_assembled_transaction(txn, &key, &[], network_passphrase, None, None) .await? @@ -118,7 +123,9 @@ impl NetworkRunnable for Cmd { data::write(get_txn_resp, &network.rpc_uri()?)?; } - Ok(stellar_strkey::Contract(contract_id.0).to_string()) + Ok(TxnResult::Xdr( + stellar_strkey::Contract(contract_id.0).to_string(), + )) } } diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index 40369b230..7a2b929d4 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -17,7 +17,9 @@ use soroban_env_host::{ use crate::commands::{ config::data, contract::{self, id::wasm::get_contract_id}, - global, network, NetworkRunnable, + global, network, + txn_result::{self, TxnResult}, + NetworkRunnable, }; use crate::{ commands::{config, contract::install, HEADING_RPC}, @@ -93,6 +95,8 @@ pub enum Error { #[error(transparent)] WasmId(#[from] contract::id::wasm::Error), #[error(transparent)] + TxnResult(#[from] txn_result::Error), + #[error(transparent)] Data(#[from] data::Error), #[error(transparent)] Network(#[from] network::Error), @@ -115,18 +119,20 @@ impl NetworkRunnable for Cmd { &self, global_args: Option<&global::Args>, config: Option<&config::Args>, - ) -> Result { + ) -> Result, Error> { let config = config.unwrap_or(&self.config); let wasm_hash = if let Some(wasm) = &self.wasm { + let mut fee = self.fee.clone(); + fee.build_only = false; let hash = install::Cmd { wasm: wasm::Args { wasm: wasm.clone() }, config: config.clone(), - fee: self.fee.clone(), + fee, ignore_checks: self.ignore_checks, } .run_against_rpc_server(global_args, Some(config)) .await?; - hex::encode(hash) + hex::encode(hash.try_res()?) } else { self.wasm_hash .as_ref() @@ -169,8 +175,12 @@ impl NetworkRunnable for Cmd { salt, &key, )?; + if self.fee.build_only { + return Ok(TxnResult::from_xdr(&txn)?); + } + let txn = client.create_assembled_transaction(&txn).await?; - let txn = self.fee.apply_to_assembled_txn(txn); + let txn = self.fee.apply_to_assembled_txn(txn)?; let get_txn_resp = client .send_assembled_transaction(txn, &key, &[], &network.network_passphrase, None, None) .await? @@ -178,7 +188,9 @@ impl NetworkRunnable for Cmd { if global_args.map_or(true, |a| !a.no_cache) { data::write(get_txn_resp, &network.rpc_uri()?)?; } - Ok(stellar_strkey::Contract(contract_id.0).to_string()) + Ok(TxnResult::Res( + stellar_strkey::Contract(contract_id.0).to_string(), + )) } } diff --git a/cmd/soroban-cli/src/commands/contract/extend.rs b/cmd/soroban-cli/src/commands/contract/extend.rs index ab7834959..cebddd0c0 100644 --- a/cmd/soroban-cli/src/commands/contract/extend.rs +++ b/cmd/soroban-cli/src/commands/contract/extend.rs @@ -11,7 +11,9 @@ use soroban_env_host::xdr::{ use crate::{ commands::{ config::{self, data}, - global, network, NetworkRunnable, + global, network, + txn_result::TxnResult, + NetworkRunnable, }, key, rpc::{self, Client}, @@ -87,7 +89,11 @@ 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(None, None).await?; + let res = self.run_against_rpc_server(None, None).await?; + let TxnResult::Res(ttl_ledger) = &res else { + println!("{}", res.xdr().unwrap()); + return Ok(()); + }; if self.ttl_ledger_only { println!("{ttl_ledger}"); } else { @@ -117,7 +123,7 @@ impl NetworkRunnable for Cmd { &self, args: Option<&global::Args>, config: Option<&config::Args>, - ) -> Result { + ) -> Result, Self::Error> { let config = config.unwrap_or(&self.config); let network = config.get_network()?; tracing::trace!(?network); @@ -161,7 +167,9 @@ impl NetworkRunnable for Cmd { resource_fee: 0, }), }; - + if self.fee.build_only { + return Ok(TxnResult::from_xdr(&tx)?); + } let res = client .prepare_and_send_transaction(&tx, &key, &[], &network.network_passphrase, None, None) .await?; @@ -194,7 +202,7 @@ impl NetworkRunnable for Cmd { let entry = client.get_full_ledger_entries(&keys).await?; let extension = entry.entries[0].live_until_ledger_seq; if entry.latest_ledger + i64::from(extend_to) < i64::from(extension) { - return Ok(extension); + return Ok(TxnResult::Res(extension)); } } @@ -209,7 +217,7 @@ impl NetworkRunnable for Cmd { }), .. }), - ) => Ok(*live_until_ledger_seq), + ) => Ok(TxnResult::Res(*live_until_ledger_seq)), _ => Err(Error::LedgerEntryNotFound), } } diff --git a/cmd/soroban-cli/src/commands/contract/fetch.rs b/cmd/soroban-cli/src/commands/contract/fetch.rs index eefb1b4b8..5b223e71a 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::txn_result::TxnResult; use crate::commands::{global, NetworkRunnable}; use crate::{ rpc::{self, Client}, @@ -116,7 +117,14 @@ impl Cmd { } pub async fn get_bytes(&self) -> Result, Error> { - self.run_against_rpc_server(None, None).await + // This is safe because fetch doesn't create a transaction + unsafe { + Ok(self + .run_against_rpc_server(None, None) + .await? + .res() + .unwrap_unchecked()) + } } pub fn network(&self) -> Result { @@ -137,7 +145,7 @@ impl NetworkRunnable for Cmd { &self, _args: Option<&global::Args>, config: Option<&config::Args>, - ) -> Result, Error> { + ) -> Result>, Error> { let network = config.map_or_else(|| self.network(), |c| Ok(c.get_network()?))?; tracing::trace!(?network); let contract_id = self.contract_id()?; @@ -146,7 +154,7 @@ impl NetworkRunnable for Cmd { .verify_network_passphrase(Some(&network.network_passphrase)) .await?; // async closures are not yet stable - Ok(client.get_remote_wasm(&contract_id).await?) + Ok(TxnResult::Res(client.get_remote_wasm(&contract_id).await?)) } } pub fn get_contract_wasm_from_storage( diff --git a/cmd/soroban-cli/src/commands/contract/install.rs b/cmd/soroban-cli/src/commands/contract/install.rs index 8763fd7e6..7208e1a93 100644 --- a/cmd/soroban-cli/src/commands/contract/install.rs +++ b/cmd/soroban-cli/src/commands/contract/install.rs @@ -12,6 +12,7 @@ use soroban_env_host::xdr::{ use super::restore; use crate::commands::network; +use crate::commands::txn_result::TxnResult; use crate::commands::{config::data, global, NetworkRunnable}; use crate::key; use crate::rpc::{self, Client}; @@ -72,7 +73,10 @@ pub enum Error { impl Cmd { pub async fn run(&self) -> Result<(), Error> { - let res_str = hex::encode(self.run_against_rpc_server(None, None).await?); + let res_str = match self.run_against_rpc_server(None, None).await? { + TxnResult::Xdr(xdr) => xdr, + TxnResult::Res(hash) => hex::encode(hash), + }; println!("{res_str}"); Ok(()) } @@ -86,7 +90,7 @@ impl NetworkRunnable for Cmd { &self, args: Option<&global::Args>, config: Option<&config::Args>, - ) -> Result { + ) -> Result, Error> { let config = config.unwrap_or(&self.config); let contract = self.wasm.read()?; let network = config.get_network()?; @@ -125,6 +129,9 @@ impl NetworkRunnable for Cmd { let (tx_without_preflight, hash) = build_install_contract_code_tx(&contract, sequence + 1, self.fee.fee, &key)?; + if self.fee.build_only { + return Ok(TxnResult::from_xdr(&tx_without_preflight)?); + } let code_key = xdr::LedgerKey::ContractCode(xdr::LedgerKeyContractCode { hash: hash.clone() }); let contract_data = client.get_ledger_entries(&[code_key]).await?; @@ -142,7 +149,7 @@ impl NetworkRunnable for Cmd { // Skip reupload if this isn't V0 because V1 extension already // exists. if code.ext.ne(&ContractCodeEntryExt::V0) { - return Ok(hash); + return Ok(TxnResult::Res(hash)); } } _ => { @@ -151,11 +158,10 @@ impl NetworkRunnable for Cmd { } } } - let txn = client .create_assembled_transaction(&tx_without_preflight) .await?; - let txn = self.fee.apply_to_assembled_txn(txn); + let txn = self.fee.apply_to_assembled_txn(txn)?; let txn_resp = client .send_assembled_transaction(txn, &key, &[], &network.network_passphrase, None, None) .await?; @@ -189,7 +195,7 @@ impl NetworkRunnable for Cmd { if args.map_or(true, |a| !a.no_cache) { data::write_spec(&hash.to_string(), &wasm_spec.spec)?; } - Ok(hash) + Ok(TxnResult::Res(hash)) } } diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index d40f9c46f..00d7383d7 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -12,15 +12,16 @@ use heck::ToKebabCase; use soroban_env_host::{ xdr::{ - self, ContractDataEntry, Error as XdrError, Hash, HostFunction, InvokeContractArgs, - InvokeHostFunctionOp, LedgerEntryData, LedgerFootprint, Memo, MuxedAccount, Operation, - OperationBody, Preconditions, ScAddress, ScSpecEntry, ScSpecFunctionV0, ScSpecTypeDef, - ScVal, ScVec, SequenceNumber, SorobanAuthorizationEntry, SorobanResources, Transaction, + self, Hash, HostFunction, InvokeContractArgs, InvokeHostFunctionOp, LedgerEntryData, + LedgerFootprint, Memo, MuxedAccount, Operation, OperationBody, Preconditions, PublicKey, + ScAddress, ScSpecEntry, ScSpecFunctionV0, ScSpecTypeDef, ScVal, ScVec, SequenceNumber, + SorobanAuthorizationEntry, SorobanResources, String32, StringM, Transaction, TransactionExt, Uint256, VecM, }, HostError, }; +use soroban_sdk::xdr::{AccountEntry, AccountEntryExt, AccountId, ContractDataEntry, DiagnosticEvent, Thresholds}; use soroban_spec::read::FromWasmError; use stellar_strkey::DecodeError; @@ -28,6 +29,7 @@ use super::super::{ config::{self, locator}, events, }; +use crate::commands::txn_result::TxnResult; use crate::commands::NetworkRunnable; use crate::{ commands::{config::data, global, network}, @@ -80,7 +82,7 @@ pub enum Error { error: soroban_spec_tools::Error, }, #[error("cannot add contract to ledger entries: {0}")] - CannotAddContractToLedgerEntries(XdrError), + CannotAddContractToLedgerEntries(xdr::Error), #[error(transparent)] // TODO: the Display impl of host errors is pretty user-unfriendly // (it just calls Debug). I think we can do better than that @@ -109,7 +111,7 @@ pub enum Error { error: soroban_spec_tools::Error, }, #[error(transparent)] - Xdr(#[from] XdrError), + Xdr(#[from] xdr::Error), #[error("error parsing int: {0}")] ParseIntError(#[from] ParseIntError), #[error(transparent)] @@ -169,6 +171,7 @@ impl Cmd { &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()) @@ -201,7 +204,7 @@ impl Cmd { let cmd = crate::commands::keys::address::Cmd { name: s.clone(), hd_path: Some(0), - locator: self.config.locator.clone(), + locator: config.locator.clone(), }; if let Ok(address) = cmd.public_key() { s = address.to_string(); @@ -272,7 +275,7 @@ impl Cmd { Ok(()) } - pub async fn invoke(&self, global_args: &global::Args) -> Result { + pub async fn invoke(&self, global_args: &global::Args) -> Result, Error> { self.run_against_rpc_server(Some(global_args), None).await } @@ -309,7 +312,7 @@ impl NetworkRunnable for Cmd { &self, global_args: Option<&global::Args>, config: Option<&config::Args>, - ) -> Result { + ) -> Result, Error> { let config = config.unwrap_or(&self.config); let network = config.get_network()?; tracing::trace!(?network); @@ -317,19 +320,24 @@ impl NetworkRunnable for Cmd { 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)?; + let _ = self.build_host_function_parameters(contract_id, spec_entries, config)?; } let client = rpc::Client::new(&network.rpc_url)?; - client - .verify_network_passphrase(Some(&network.network_passphrase)) - .await?; - let key = config.key_pair()?; - - // Get the account sequence number - let public_strkey = - stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); - let account_details = client.get_account(&public_strkey).await?; + let account_details = if self.is_view { + default_account_entry() + } else { + client + .verify_network_passphrase(Some(&network.network_passphrase)) + .await?; + let key = config.key_pair()?; + + // Get the account sequence number + let public_strkey = + stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); + client.get_account(&public_strkey).await? + }; let sequence: i64 = account_details.seq_num.into(); + let AccountId(PublicKey::PublicKeyTypeEd25519(account_id)) = account_details.account_id; let r = client.get_contract_data(&contract_id).await?; tracing::trace!("{r:?}"); @@ -361,15 +369,18 @@ impl NetworkRunnable for Cmd { // Get the ledger footprint let (function, spec, host_function_params, signers) = - self.build_host_function_parameters(contract_id, &spec_entries)?; + self.build_host_function_parameters(contract_id, &spec_entries, config)?; let tx = build_invoke_contract_tx( host_function_params.clone(), sequence + 1, self.fee.fee, - &key, + account_id, )?; + if self.fee.build_only { + return Ok(TxnResult::from_xdr(&tx)?); + } let txn = client.create_assembled_transaction(&tx).await?; - let txn = self.fee.apply_to_assembled_txn(txn); + let txn = self.fee.apply_to_assembled_txn(txn)?; let sim_res = txn.sim_response(); if global_args.map_or(true, |a| !a.no_cache) { data::write(sim_res.clone().into(), &network.rpc_uri()?)?; @@ -386,7 +397,7 @@ impl NetworkRunnable for Cmd { let res = client .send_assembled_transaction( txn, - &key, + &config.key_pair()?, &signers, &network.network_passphrase, Some(log_events), @@ -404,10 +415,27 @@ impl NetworkRunnable for Cmd { } } +const DEFAULT_ACCOUNT_ID: AccountId = AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))); + +fn default_account_entry() -> AccountEntry { + AccountEntry { + account_id: DEFAULT_ACCOUNT_ID, + balance: 0, + seq_num: SequenceNumber(0), + num_sub_entries: 0, + inflation_dest: None, + flags: 0, + home_domain: String32::from(unsafe { StringM::<32>::from_str("TEST").unwrap_unchecked() }), + thresholds: Thresholds([0; 4]), + signers: unsafe { [].try_into().unwrap_unchecked() }, + ext: AccountEntryExt::V0, + } +} + fn log_events( footprint: &LedgerFootprint, auth: &[VecM], - events: &[xdr::DiagnosticEvent], + events: &[DiagnosticEvent], ) { crate::log::auth(auth); crate::log::diagnostic_events(events, tracing::Level::TRACE); @@ -418,7 +446,11 @@ fn log_resources(resources: &SorobanResources) { crate::log::cost(resources); } -pub fn output_to_string(spec: &Spec, res: &ScVal, function: &str) -> Result { +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 @@ -429,14 +461,14 @@ pub fn output_to_string(spec: &Spec, res: &ScVal, function: &str) -> Result Result { let op = Operation { source_account: None, @@ -446,7 +478,7 @@ fn build_invoke_contract_tx( }), }; Ok(Transaction { - source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), + source_account: MuxedAccount::Ed25519(source_account_id), fee, seq_num: SequenceNumber(sequence), cond: Preconditions::None, @@ -506,16 +538,15 @@ fn build_custom_cmd(name: &str, spec: &Spec) -> Result { // Set up special-case arg rules arg = match type_ { - xdr::ScSpecTypeDef::Bool => arg + ScSpecTypeDef::Bool => arg .num_args(0..1) .default_missing_value("true") .default_value("false") .num_args(0..=1), - xdr::ScSpecTypeDef::Option(_val) => arg.required(false), - xdr::ScSpecTypeDef::I256 - | xdr::ScSpecTypeDef::I128 - | xdr::ScSpecTypeDef::I64 - | xdr::ScSpecTypeDef::I32 => arg.allow_hyphen_values(true), + ScSpecTypeDef::Option(_val) => arg.required(false), + ScSpecTypeDef::I256 | ScSpecTypeDef::I128 | ScSpecTypeDef::I64 | ScSpecTypeDef::I32 => { + arg.allow_hyphen_values(true) + } _ => arg, }; diff --git a/cmd/soroban-cli/src/commands/contract/read.rs b/cmd/soroban-cli/src/commands/contract/read.rs index a7b1d07a8..1d23e5c6a 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, global, NetworkRunnable}, + commands::{config, global, txn_result::TxnResult, NetworkRunnable}, key, rpc::{self, Client, FullLedgerEntries, FullLedgerEntry}, }; @@ -91,7 +91,13 @@ pub enum Error { impl Cmd { pub async fn run(&self) -> Result<(), Error> { - let entries = self.run_against_rpc_server(None, None).await?; + let entries = match self.run_against_rpc_server(None, None).await? { + TxnResult::Res(res) => res, + TxnResult::Xdr(xdr) => { + println!("{xdr}"); + return Ok(()); + } + }; self.output_entries(&entries) } @@ -178,12 +184,12 @@ impl NetworkRunnable for Cmd { &self, _: Option<&global::Args>, config: Option<&config::Args>, - ) -> Result { + ) -> Result, Error> { 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?) + Ok(TxnResult::Res(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 8b5921a1a..6fc8eb17f 100644 --- a/cmd/soroban-cli/src/commands/contract/restore.rs +++ b/cmd/soroban-cli/src/commands/contract/restore.rs @@ -13,7 +13,9 @@ use crate::{ commands::{ config::{self, data, locator}, contract::extend, - global, network, NetworkRunnable, + global, network, + txn_result::{self, TxnResult}, + NetworkRunnable, }, key, rpc::{self, Client}, @@ -87,13 +89,21 @@ pub enum Error { Data(#[from] data::Error), #[error(transparent)] Network(#[from] network::Error), + + #[error(transparent)] + TxnResult(#[from] txn_result::Error), } impl Cmd { #[allow(clippy::too_many_lines)] pub async fn run(&self) -> Result<(), Error> { - let expiration_ledger_seq = self.run_against_rpc_server(None, None).await?; - + let expiration_ledger_seq = match self.run_against_rpc_server(None, None).await? { + TxnResult::Res(res) => res, + TxnResult::Xdr(xdr) => { + println!("{xdr}"); + return Ok(()); + } + }; if let Some(ledgers_to_extend) = self.ledgers_to_extend { extend::Cmd { key: self.key.clone(), @@ -121,7 +131,7 @@ impl NetworkRunnable for Cmd { &self, args: Option<&global::Args>, config: Option<&config::Args>, - ) -> Result { + ) -> Result, Error> { let config = config.unwrap_or(&self.config); let network = config.get_network()?; tracing::trace!(?network); @@ -162,7 +172,9 @@ impl NetworkRunnable for Cmd { resource_fee: 0, }), }; - + if self.fee.build_only { + return Ok(TxnResult::from_xdr(&tx)?); + } let res = client .prepare_and_send_transaction(&tx, &key, &[], &network.network_passphrase, None, None) .await?; @@ -198,7 +210,9 @@ impl NetworkRunnable for Cmd { operations[0].changes.len() ); } - parse_operations(operations).ok_or(Error::MissingOperationResult) + Ok(TxnResult::Res( + parse_operations(operations).ok_or(Error::MissingOperationResult)?, + )) } } diff --git a/cmd/soroban-cli/src/commands/events.rs b/cmd/soroban-cli/src/commands/events.rs index 42145f5bf..f1707f349 100644 --- a/cmd/soroban-cli/src/commands/events.rs +++ b/cmd/soroban-cli/src/commands/events.rs @@ -5,7 +5,9 @@ use soroban_env_host::xdr::{self, Limits, ReadXdr}; use super::{ config::{self, locator}, - global, network, NetworkRunnable, + global, network, + txn_result::TxnResult, + NetworkRunnable, }; use crate::{rpc, utils}; @@ -124,6 +126,8 @@ pub enum Error { Locator(#[from] locator::Error), #[error(transparent)] Config(#[from] config::Error), + #[error(transparent)] + TxnResult(#[from] super::txn_result::Error), } #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum)] @@ -167,7 +171,8 @@ impl Cmd { })?; } - let response = self.run_against_rpc_server(None, None).await?; + let txn_res = self.run_against_rpc_server(None, None).await?; + let response = txn_res.try_res()?; for event in &response.events { match self.output { @@ -214,7 +219,7 @@ impl NetworkRunnable for Cmd { &self, _args: Option<&global::Args>, config: Option<&config::Args>, - ) -> Result { + ) -> Result, Error> { let start = self.start()?; let network = if let Some(config) = config { Ok(config.get_network()?) @@ -226,15 +231,17 @@ impl NetworkRunnable for Cmd { client .verify_network_passphrase(Some(&network.network_passphrase)) .await?; - client - .get_events( - start, - Some(self.event_type), - &self.contract_ids, - &self.topic_filters, - Some(self.count), - ) - .await - .map_err(Error::Rpc) + Ok(TxnResult::Res( + client + .get_events( + start, + Some(self.event_type), + &self.contract_ids, + &self.topic_filters, + Some(self.count), + ) + .await + .map_err(Error::Rpc)?, + )) } } diff --git a/cmd/soroban-cli/src/commands/mod.rs b/cmd/soroban-cli/src/commands/mod.rs index 4bb5dcb53..f904c465f 100644 --- a/cmd/soroban-cli/src/commands/mod.rs +++ b/cmd/soroban-cli/src/commands/mod.rs @@ -14,6 +14,8 @@ pub mod network; pub mod plugin; pub mod version; +pub mod txn_result; + pub const HEADING_RPC: &str = "Options (RPC)"; const ABOUT: &str = "Build, deploy, & interact with contracts; set identities to sign with; configure networks; generate keys; and more. @@ -168,5 +170,5 @@ pub trait NetworkRunnable { &self, global_args: Option<&global::Args>, config: Option<&config::Args>, - ) -> Result; + ) -> Result, Self::Error>; } diff --git a/cmd/soroban-cli/src/commands/txn_result.rs b/cmd/soroban-cli/src/commands/txn_result.rs new file mode 100644 index 000000000..f69f549be --- /dev/null +++ b/cmd/soroban-cli/src/commands/txn_result.rs @@ -0,0 +1,56 @@ +use std::fmt::{Display, Formatter}; + +use soroban_sdk::xdr::{self, Limits, WriteXdr}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Expect xdr string")] + XdrStringExpected, + #[error("Expect result")] + ResultExpected, +} + +pub enum TxnResult { + Xdr(String), + Res(T), +} + +impl TxnResult { + pub fn from_xdr(res: &impl WriteXdr) -> Result { + Ok(TxnResult::Xdr(res.to_xdr_base64(Limits::none())?)) + } + + pub fn xdr(&self) -> Option<&str> { + match self { + TxnResult::Xdr(xdr) => Some(xdr), + TxnResult::Res(_) => None, + } + } + + pub fn res(self) -> Option { + match self { + TxnResult::Res(res) => Some(res), + TxnResult::Xdr(_) => None, + } + } + + pub fn try_xdr(&self) -> Result<&str, Error> { + self.xdr().ok_or(Error::XdrStringExpected) + } + + pub fn try_res(self) -> Result { + self.res().ok_or(Error::ResultExpected) + } +} + +impl Display for TxnResult +where + T: Display, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + TxnResult::Xdr(xdr) => write!(f, "{xdr}"), + TxnResult::Res(res) => write!(f, "{res}"), + } + } +} diff --git a/cmd/soroban-cli/src/fee.rs b/cmd/soroban-cli/src/fee.rs index 353fe6e5d..f6ac76d58 100644 --- a/cmd/soroban-cli/src/fee.rs +++ b/cmd/soroban-cli/src/fee.rs @@ -1,5 +1,6 @@ use clap::arg; -use soroban_env_host::xdr; + +use soroban_env_host::xdr::{self, WriteXdr}; use soroban_rpc::Assembled; use crate::commands::HEADING_RPC; @@ -16,15 +17,31 @@ pub struct Args { /// Number of instructions to simulate #[arg(long, help_heading = HEADING_RPC)] pub instructions: Option, + /// Build the transaction only write the base64 xdr to stdout + #[arg(long, help_heading = HEADING_RPC)] + pub build_only: bool, + /// Simulation the transaction only write the base64 xdr to stdout + #[arg(long, help_heading = HEADING_RPC, conflicts_with = "build_only")] + pub sim_only: bool, } impl Args { - pub fn apply_to_assembled_txn(&self, txn: Assembled) -> Assembled { - if let Some(instructions) = self.instructions { + pub fn apply_to_assembled_txn(&self, txn: Assembled) -> Result { + let simulated_txn = if let Some(instructions) = self.instructions { txn.set_max_instructions(instructions) } else { add_padding_to_instructions(txn) + }; + if self.sim_only { + println!( + "{}", + simulated_txn + .transaction() + .to_xdr_base64(xdr::Limits::none())? + ); + std::process::exit(0); } + Ok(simulated_txn) } } @@ -47,6 +64,8 @@ impl Default for Args { fee: 100, cost: false, instructions: None, + build_only: false, + sim_only: false, } } } diff --git a/cmd/soroban-cli/src/lib.rs b/cmd/soroban-cli/src/lib.rs index d4118a6be..5cde45436 100644 --- a/cmd/soroban-cli/src/lib.rs +++ b/cmd/soroban-cli/src/lib.rs @@ -3,9 +3,10 @@ clippy::must_use_candidate, clippy::missing_panics_doc )] +use std::path::Path; + pub(crate) use soroban_env_host::xdr; pub(crate) use soroban_rpc as rpc; -use std::path::Path; pub mod commands; pub mod fee; diff --git a/docs/soroban-cli-full-docs.md b/docs/soroban-cli-full-docs.md index 766a7798e..9d10a653e 100644 --- a/docs/soroban-cli-full-docs.md +++ b/docs/soroban-cli-full-docs.md @@ -240,6 +240,14 @@ Deploy builtin Soroban Asset Contract Possible values: `true`, `false` * `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction only write the base64 xdr to stdout + + Possible values: `true`, `false` + +* `--sim-only` — Simulation the transaction only write the base64 xdr to stdout + + Possible values: `true`, `false` + @@ -391,6 +399,14 @@ If no keys are specified the contract itself is extended. Possible values: `true`, `false` * `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction only write the base64 xdr to stdout + + Possible values: `true`, `false` + +* `--sim-only` — Simulation the transaction only write the base64 xdr to stdout + + Possible values: `true`, `false` + @@ -423,6 +439,14 @@ Deploy a wasm contract Possible values: `true`, `false` * `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction only write the base64 xdr to stdout + + Possible values: `true`, `false` + +* `--sim-only` — Simulation the transaction only write the base64 xdr to stdout + + Possible values: `true`, `false` + * `-i`, `--ignore-checks` — Whether to ignore safety checks when deploying contracts Default value: `false` @@ -587,6 +611,14 @@ Install a WASM file to the ledger without creating a contract instance Possible values: `true`, `false` * `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction only write the base64 xdr to stdout + + Possible values: `true`, `false` + +* `--sim-only` — Simulation the transaction only write the base64 xdr to stdout + + Possible values: `true`, `false` + * `--wasm ` — Path to wasm binary * `-i`, `--ignore-checks` — Whether to ignore safety checks when deploying contracts @@ -636,6 +668,14 @@ soroban contract invoke ... -- --help Possible values: `true`, `false` * `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction only write the base64 xdr to stdout + + Possible values: `true`, `false` + +* `--sim-only` — Simulation the transaction only write the base64 xdr to stdout + + Possible values: `true`, `false` + @@ -748,6 +788,14 @@ If no keys are specificed the contract itself is restored. Possible values: `true`, `false` * `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction only write the base64 xdr to stdout + + Possible values: `true`, `false` + +* `--sim-only` — Simulation the transaction only write the base64 xdr to stdout + + Possible values: `true`, `false` + @@ -1339,6 +1387,9 @@ Read cached action * `--id ` — ID of the cache entry + Possible values: `envelope` + +
From 4ea2d25d3e9f64d0bae6f6fe4e763d738e717cfa Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Fri, 3 May 2024 11:25:02 -0400 Subject: [PATCH 02/30] fix: clippy and fmt --- cmd/crates/soroban-spec-tools/src/lib.rs | 3 +-- cmd/soroban-cli/src/commands/contract/invoke.rs | 4 +++- docs/soroban-cli-full-docs.md | 3 --- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/cmd/crates/soroban-spec-tools/src/lib.rs b/cmd/crates/soroban-spec-tools/src/lib.rs index e6d496437..c227c3478 100644 --- a/cmd/crates/soroban-spec-tools/src/lib.rs +++ b/cmd/crates/soroban-spec-tools/src/lib.rs @@ -1137,8 +1137,7 @@ impl Spec { ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { fields, .. }) if fields .first() - .map(|f| f.name.to_utf8_string_lossy() == "0") - .unwrap_or_default() => + .is_some_and(|f| f.name.to_utf8_string_lossy() == "0") => { let fields = fields .iter() diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 00d7383d7..d297f9c54 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -21,7 +21,9 @@ use soroban_env_host::{ HostError, }; -use soroban_sdk::xdr::{AccountEntry, AccountEntryExt, AccountId, ContractDataEntry, DiagnosticEvent, Thresholds}; +use soroban_sdk::xdr::{ + AccountEntry, AccountEntryExt, AccountId, ContractDataEntry, DiagnosticEvent, Thresholds, +}; use soroban_spec::read::FromWasmError; use stellar_strkey::DecodeError; diff --git a/docs/soroban-cli-full-docs.md b/docs/soroban-cli-full-docs.md index 9d10a653e..b005f1e7f 100644 --- a/docs/soroban-cli-full-docs.md +++ b/docs/soroban-cli-full-docs.md @@ -1387,9 +1387,6 @@ Read cached action * `--id ` — ID of the cache entry - Possible values: `envelope` - -
From 58daf6cc23c608293d5074dec1e050da4820acf9 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 7 May 2024 14:42:43 -0400 Subject: [PATCH 03/30] fix: address PR comments and skip install step for build only deploy --- cmd/crates/soroban-test/src/lib.rs | 2 +- .../src/commands/contract/deploy/asset.rs | 12 ++++--- .../src/commands/contract/deploy/wasm.rs | 33 ++++++++++++------- .../src/commands/contract/fetch.rs | 13 +++----- .../src/commands/contract/id/asset.rs | 9 +++-- .../src/commands/contract/install.rs | 4 +++ .../src/commands/contract/invoke.rs | 4 +++ cmd/soroban-cli/src/commands/txn_result.rs | 17 ++++++++-- cmd/soroban-cli/src/fee.rs | 20 +++++------ cmd/soroban-cli/src/wasm.rs | 7 +++- 10 files changed, 79 insertions(+), 42 deletions(-) diff --git a/cmd/crates/soroban-test/src/lib.rs b/cmd/crates/soroban-test/src/lib.rs index 835d2066b..161535cd0 100644 --- a/cmd/crates/soroban-test/src/lib.rs +++ b/cmd/crates/soroban-test/src/lib.rs @@ -253,7 +253,7 @@ impl TestEnv { Some(&config), ) .await? - .res() + .into_res() .unwrap()) } diff --git a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs index 65557eda9..e77f7015c 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs @@ -76,13 +76,13 @@ impl Cmd { #[async_trait::async_trait] impl NetworkRunnable for Cmd { type Error = Error; - type Result = String; + type Result = stellar_strkey::Contract; async fn run_against_rpc_server( &self, args: Option<&global::Args>, config: Option<&config::Args>, - ) -> Result, Error> { + ) -> Result, Error> { let config = config.unwrap_or(&self.config); // Parse asset let asset = parse_asset(&self.asset)?; @@ -115,6 +115,10 @@ impl NetworkRunnable for Cmd { } let txn = client.create_assembled_transaction(&tx).await?; let txn = self.fee.apply_to_assembled_txn(txn)?; + let txn = match txn { + TxnResult::Xdr(raw) => return Ok(TxnResult::Xdr(raw)), + TxnResult::Res(txn) => txn, + }; let get_txn_resp = client .send_assembled_transaction(txn, &key, &[], network_passphrase, None, None) .await? @@ -123,9 +127,7 @@ impl NetworkRunnable for Cmd { data::write(get_txn_resp, &network.rpc_uri()?)?; } - Ok(TxnResult::Xdr( - stellar_strkey::Contract(contract_id.0).to_string(), - )) + Ok(TxnResult::Res(stellar_strkey::Contract(contract_id.0))) } } diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index 7a2b929d4..3cb034498 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -100,6 +100,8 @@ pub enum Error { Data(#[from] data::Error), #[error(transparent)] Network(#[from] network::Error), + #[error(transparent)] + Wasm(#[from] wasm::Error), } impl Cmd { @@ -122,17 +124,22 @@ impl NetworkRunnable for Cmd { ) -> Result, Error> { let config = config.unwrap_or(&self.config); let wasm_hash = if let Some(wasm) = &self.wasm { - let mut fee = self.fee.clone(); - fee.build_only = false; - let hash = install::Cmd { - wasm: wasm::Args { wasm: wasm.clone() }, - config: config.clone(), - fee, - ignore_checks: self.ignore_checks, - } - .run_against_rpc_server(global_args, Some(config)) - .await?; - hex::encode(hash.try_res()?) + let hash = if self.fee.build_only { + wasm::Args { wasm: wasm.clone() }.hash()? + } else { + let mut fee = self.fee.clone(); + fee.build_only = false; + install::Cmd { + wasm: wasm::Args { wasm: wasm.clone() }, + config: config.clone(), + fee, + ignore_checks: self.ignore_checks, + } + .run_against_rpc_server(global_args, Some(config)) + .await? + .try_into_res()? + }; + hex::encode(hash) } else { self.wasm_hash .as_ref() @@ -181,6 +188,10 @@ impl NetworkRunnable for Cmd { let txn = client.create_assembled_transaction(&txn).await?; let txn = self.fee.apply_to_assembled_txn(txn)?; + let txn = match txn { + TxnResult::Xdr(raw) => return Ok(TxnResult::Xdr(raw)), + TxnResult::Res(txn) => txn, + }; let get_txn_resp = client .send_assembled_transaction(txn, &key, &[], &network.network_passphrase, None, None) .await? diff --git a/cmd/soroban-cli/src/commands/contract/fetch.rs b/cmd/soroban-cli/src/commands/contract/fetch.rs index 5b223e71a..d9cb8d427 100644 --- a/cmd/soroban-cli/src/commands/contract/fetch.rs +++ b/cmd/soroban-cli/src/commands/contract/fetch.rs @@ -75,6 +75,8 @@ pub enum Error { Io(#[from] std::io::Error), #[error("missing result")] MissingResult, + #[error("Unexpected XDR")] + UnexpectedXdr, #[error("unexpected contract code data type: {0:?}")] UnexpectedContractCodeDataType(LedgerEntryData), #[error("reading file {0:?}: {1}")] @@ -117,13 +119,9 @@ impl Cmd { } pub async fn get_bytes(&self) -> Result, Error> { - // This is safe because fetch doesn't create a transaction - unsafe { - Ok(self - .run_against_rpc_server(None, None) - .await? - .res() - .unwrap_unchecked()) + match self.run_against_rpc_server(None, None).await? { + TxnResult::Xdr(_) => Err(Error::UnexpectedXdr), + TxnResult::Res(v) => Ok(v), } } @@ -153,7 +151,6 @@ impl NetworkRunnable for Cmd { client .verify_network_passphrase(Some(&network.network_passphrase)) .await?; - // async closures are not yet stable Ok(TxnResult::Res(client.get_remote_wasm(&contract_id).await?)) } } diff --git a/cmd/soroban-cli/src/commands/contract/id/asset.rs b/cmd/soroban-cli/src/commands/contract/id/asset.rs index 34e5767a6..e036b7939 100644 --- a/cmd/soroban-cli/src/commands/contract/id/asset.rs +++ b/cmd/soroban-cli/src/commands/contract/id/asset.rs @@ -26,11 +26,14 @@ pub enum Error { } impl Cmd { pub fn run(&self) -> Result<(), Error> { + println!("{}", self.contract_address()?); + Ok(()) + } + + pub fn contract_address(&self) -> Result { let asset = parse_asset(&self.asset)?; let network = self.config.get_network()?; let contract_id = contract_id_hash_from_asset(&asset, &network.network_passphrase)?; - let strkey_contract_id = stellar_strkey::Contract(contract_id.0).to_string(); - println!("{strkey_contract_id}"); - Ok(()) + Ok(stellar_strkey::Contract(contract_id.0)) } } diff --git a/cmd/soroban-cli/src/commands/contract/install.rs b/cmd/soroban-cli/src/commands/contract/install.rs index 7208e1a93..646a2d53a 100644 --- a/cmd/soroban-cli/src/commands/contract/install.rs +++ b/cmd/soroban-cli/src/commands/contract/install.rs @@ -162,6 +162,10 @@ impl NetworkRunnable for Cmd { .create_assembled_transaction(&tx_without_preflight) .await?; let txn = self.fee.apply_to_assembled_txn(txn)?; + let txn = match txn { + TxnResult::Xdr(raw) => return Ok(TxnResult::Xdr(raw)), + TxnResult::Res(txn) => txn, + }; let txn_resp = client .send_assembled_transaction(txn, &key, &[], &network.network_passphrase, None, None) .await?; diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index d297f9c54..47220305d 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -383,6 +383,10 @@ impl NetworkRunnable for Cmd { } let txn = client.create_assembled_transaction(&tx).await?; let txn = self.fee.apply_to_assembled_txn(txn)?; + let txn = match txn { + TxnResult::Xdr(raw) => return Ok(TxnResult::Xdr(raw)), + TxnResult::Res(txn) => txn, + }; let sim_res = txn.sim_response(); if global_args.map_or(true, |a| !a.no_cache) { data::write(sim_res.clone().into(), &network.rpc_uri()?)?; diff --git a/cmd/soroban-cli/src/commands/txn_result.rs b/cmd/soroban-cli/src/commands/txn_result.rs index f69f549be..02cc27f4c 100644 --- a/cmd/soroban-cli/src/commands/txn_result.rs +++ b/cmd/soroban-cli/src/commands/txn_result.rs @@ -27,7 +27,14 @@ impl TxnResult { } } - pub fn res(self) -> Option { + pub fn res(&self) -> Option<&T> { + match self { + TxnResult::Res(res) => Some(res), + TxnResult::Xdr(_) => None, + } + } + + pub fn into_res(self) -> Option { match self { TxnResult::Res(res) => Some(res), TxnResult::Xdr(_) => None, @@ -38,9 +45,15 @@ impl TxnResult { self.xdr().ok_or(Error::XdrStringExpected) } - pub fn try_res(self) -> Result { + pub fn try_res(&self) -> Result<&T, Error> { self.res().ok_or(Error::ResultExpected) } + pub fn try_into_res(self) -> Result { + match self { + TxnResult::Res(res) => Ok(res), + TxnResult::Xdr(_) => Err(Error::XdrStringExpected), + } + } } impl Display for TxnResult diff --git a/cmd/soroban-cli/src/fee.rs b/cmd/soroban-cli/src/fee.rs index f6ac76d58..70ad9abd9 100644 --- a/cmd/soroban-cli/src/fee.rs +++ b/cmd/soroban-cli/src/fee.rs @@ -1,9 +1,9 @@ use clap::arg; -use soroban_env_host::xdr::{self, WriteXdr}; +use soroban_env_host::xdr; use soroban_rpc::Assembled; -use crate::commands::HEADING_RPC; +use crate::commands::{txn_result::TxnResult, HEADING_RPC}; #[derive(Debug, clap::Args, Clone)] #[group(skip)] @@ -26,22 +26,20 @@ pub struct Args { } impl Args { - pub fn apply_to_assembled_txn(&self, txn: Assembled) -> Result { + pub fn apply_to_assembled_txn( + &self, + txn: Assembled, + ) -> Result, xdr::Error> { let simulated_txn = if let Some(instructions) = self.instructions { txn.set_max_instructions(instructions) } else { add_padding_to_instructions(txn) }; if self.sim_only { - println!( - "{}", - simulated_txn - .transaction() - .to_xdr_base64(xdr::Limits::none())? - ); - std::process::exit(0); + TxnResult::from_xdr(simulated_txn.transaction()) + } else { + Ok(TxnResult::Res(simulated_txn)) } - Ok(simulated_txn) } } diff --git a/cmd/soroban-cli/src/wasm.rs b/cmd/soroban-cli/src/wasm.rs index 4b8a7f8ca..6f6daf462 100644 --- a/cmd/soroban-cli/src/wasm.rs +++ b/cmd/soroban-cli/src/wasm.rs @@ -1,5 +1,6 @@ use clap::arg; -use soroban_env_host::xdr::{self, LedgerKey, LedgerKeyContractCode}; +use sha2::{Digest, Sha256}; +use soroban_env_host::xdr::{self, Hash, LedgerKey, LedgerKeyContractCode}; use soroban_spec_tools::contract::{self, Spec}; use std::{ fs, io, @@ -65,6 +66,10 @@ impl Args { let contents = self.read()?; Ok(Spec::new(&contents)?) } + + pub fn hash(&self) -> Result { + Ok(Hash(Sha256::digest(self.read()?).into())) + } } impl From<&PathBuf> for Args { From a0e392369df8b984d9075fcbda982065da63bef5 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 2 May 2024 12:04:42 -0400 Subject: [PATCH 04/30] feat: add txn subcommand --- cmd/soroban-cli/src/commands/mod.rs | 10 +- cmd/soroban-cli/src/commands/txn/mod.rs | 48 ++++ cmd/soroban-cli/src/commands/txn/send.rs | 35 +++ cmd/soroban-cli/src/commands/txn/sign.rs | 97 +++++++ cmd/soroban-cli/src/commands/txn/simulate.rs | 38 +++ cmd/soroban-cli/src/commands/txn/xdr.rs | 67 +++++ cmd/soroban-cli/src/lib.rs | 1 + cmd/soroban-cli/src/signer.rs | 277 +++++++++++++++++++ docs/soroban-cli-full-docs.md | 246 ++++++++++++++++ 9 files changed, 817 insertions(+), 2 deletions(-) create mode 100644 cmd/soroban-cli/src/commands/txn/mod.rs create mode 100644 cmd/soroban-cli/src/commands/txn/send.rs create mode 100644 cmd/soroban-cli/src/commands/txn/sign.rs create mode 100644 cmd/soroban-cli/src/commands/txn/simulate.rs create mode 100644 cmd/soroban-cli/src/commands/txn/xdr.rs create mode 100644 cmd/soroban-cli/src/signer.rs diff --git a/cmd/soroban-cli/src/commands/mod.rs b/cmd/soroban-cli/src/commands/mod.rs index f904c465f..4eeeb53e2 100644 --- a/cmd/soroban-cli/src/commands/mod.rs +++ b/cmd/soroban-cli/src/commands/mod.rs @@ -12,9 +12,9 @@ pub mod global; pub mod keys; pub mod network; pub mod plugin; -pub mod version; - +pub mod txn; pub mod txn_result; +pub mod version; pub const HEADING_RPC: &str = "Options (RPC)"; const ABOUT: &str = "Build, deploy, & interact with contracts; set identities to sign with; configure networks; generate keys; and more. @@ -101,6 +101,7 @@ impl Root { Cmd::Network(network) => network.run().await?, Cmd::Version(version) => version.run(), Cmd::Keys(id) => id.run().await?, + Cmd::Txn(tx) => tx.run().await?, Cmd::Cache(data) => data.run()?, }; Ok(()) @@ -135,6 +136,9 @@ pub enum Cmd { Network(network::Cmd), /// Print version information Version(version::Cmd), + /// Sign, Simulate, and Send transactions + #[command(subcommand)] + Txn(txn::Cmd), /// Cache for tranasctions and contract specs #[command(subcommand)] Cache(cache::Cmd), @@ -158,6 +162,8 @@ pub enum Error { #[error(transparent)] Network(#[from] network::Error), #[error(transparent)] + Txn(#[from] txn::Error), + #[error(transparent)] Cache(#[from] cache::Error), } diff --git a/cmd/soroban-cli/src/commands/txn/mod.rs b/cmd/soroban-cli/src/commands/txn/mod.rs new file mode 100644 index 000000000..9de360fcc --- /dev/null +++ b/cmd/soroban-cli/src/commands/txn/mod.rs @@ -0,0 +1,48 @@ +use clap::Parser; + +pub mod send; +pub mod sign; +pub mod simulate; +pub mod xdr; + +use stellar_xdr::cli as xdr_cli; + +#[derive(Debug, Parser)] +pub enum Cmd { + /// Add a new identity (keypair, ledger, macOS keychain) + Inspect(xdr_cli::Root), + /// Given an identity return its address (public key) + Sign(sign::Cmd), + /// Submit a transaction to the network + Send(send::Cmd), + /// Simulate a transaction + Simulate(simulate::Cmd), +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + /// An error during the simulation + #[error(transparent)] + Simulate(#[from] simulate::Error), + /// An error during the inspect + #[error(transparent)] + Inspect(#[from] xdr_cli::Error), + /// An error during the sign + #[error(transparent)] + Sign(#[from] sign::Error), + /// An error during the send + #[error(transparent)] + Send(#[from] send::Error), +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + match self { + Cmd::Inspect(cmd) => cmd.run()?, + Cmd::Sign(cmd) => cmd.run().await?, + Cmd::Send(cmd) => cmd.run().await?, + Cmd::Simulate(cmd) => cmd.run().await?, + }; + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/txn/send.rs b/cmd/soroban-cli/src/commands/txn/send.rs new file mode 100644 index 000000000..1cf3f8478 --- /dev/null +++ b/cmd/soroban-cli/src/commands/txn/send.rs @@ -0,0 +1,35 @@ +use soroban_rpc::GetTransactionResponse; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + XdrArgs(#[from] super::xdr::Error), + #[error(transparent)] + Config(#[from] super::super::config::Error), + #[error(transparent)] + Rpc(#[from] crate::rpc::Error), +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +pub struct Cmd { + #[clap(flatten)] + pub xdr_args: super::xdr::Args, + #[clap(flatten)] + pub config: super::super::config::Args, +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + let response = self.send().await?; + println!("{response:#?}"); + Ok(()) + } + + pub async fn send(&self) -> Result { + let txn_env = self.xdr_args.txn_envelope()?; + let network = self.config.get_network()?; + let client = crate::rpc::Client::new(&network.rpc_url)?; + Ok(client.send_transaction(&txn_env).await?) + } +} diff --git a/cmd/soroban-cli/src/commands/txn/sign.rs b/cmd/soroban-cli/src/commands/txn/sign.rs new file mode 100644 index 000000000..ec4bb1155 --- /dev/null +++ b/cmd/soroban-cli/src/commands/txn/sign.rs @@ -0,0 +1,97 @@ +use std::io; + +// use crossterm::{ +// event::{self, Event, KeyCode, KeyEvent, KeyModifiers}, +// execute, +// terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}, +// }; +use soroban_sdk::xdr::{self, Limits, TransactionEnvelope, WriteXdr}; + +use crate::signer::{self, InMemory, Stellar}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + XdrArgs(#[from] super::xdr::Error), + #[error(transparent)] + Signer(#[from] signer::Error), + #[error(transparent)] + Config(#[from] super::super::config::Error), + #[error(transparent)] + StellarStrkey(#[from] stellar_strkey::DecodeError), + #[error(transparent)] + Xdr(#[from] xdr::Error), + #[error(transparent)] + Io(#[from] io::Error), + #[error("User cancelled signing, perhaps need to add -y")] + UserCancelledSigning, +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +pub struct Cmd { + /// Confirm that a signature can be signed by the given keypair automatically. + #[arg(long, short = 'y')] + yes: bool, + #[clap(flatten)] + pub xdr_args: super::xdr::Args, + #[clap(flatten)] + pub config: super::super::config::Args, +} + +impl Cmd { + #[allow(clippy::unused_async)] + pub async fn run(&self) -> Result<(), Error> { + let envelope = self.sign()?; + println!("{}", envelope.to_xdr_base64(Limits::none())?.trim()); + Ok(()) + } + + pub fn sign(&self) -> Result { + let source = &self.config.source_account; + tracing::debug!("signing transaction with source account {}", source); + let txn = self.xdr_args.txn()?; + let key = self.config.key_pair()?; + let address = + stellar_strkey::ed25519::PublicKey::from_payload(key.verifying_key().as_bytes())?; + let in_memory = InMemory { + network_passphrase: self.config.get_network()?.network_passphrase, + keypairs: vec![key], + }; + self.prompt_user()?; + Ok(in_memory.sign_txn(txn, &stellar_strkey::Strkey::PublicKeyEd25519(address))?) + } + + pub fn prompt_user(&self) -> Result<(), Error> { + if self.yes { + return Ok(()); + } + Err(Error::UserCancelledSigning) + // TODO use crossterm to prompt user for confirmation + // // Set up the terminal + // let mut stdout = io::stdout(); + // execute!(stdout, EnterAlternateScreen)?; + // terminal::enable_raw_mode()?; + + // println!("Press 'y' or 'Y' for yes, any other key for no:"); + + // loop { + // if let Event::Key(KeyEvent { + // code, + // modifiers: KeyModifiers::NONE, + // .. + // }) = event::read()? + // { + // match code { + // KeyCode::Char('y' | 'Y') => break, + // _ => return Err(Error::UserCancelledSigning), + // } + // } + // } + + // // Clean up the terminal + // terminal::disable_raw_mode()?; + // execute!(stdout, LeaveAlternateScreen)?; + // Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/txn/simulate.rs b/cmd/soroban-cli/src/commands/txn/simulate.rs new file mode 100644 index 000000000..5fbae28c0 --- /dev/null +++ b/cmd/soroban-cli/src/commands/txn/simulate.rs @@ -0,0 +1,38 @@ +use soroban_rpc::Assembled; +use soroban_sdk::xdr::{self, WriteXdr}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + XdrArgs(#[from] super::xdr::Error), + #[error(transparent)] + Config(#[from] super::super::config::Error), + #[error(transparent)] + Rpc(#[from] crate::rpc::Error), + #[error(transparent)] + Xdr(#[from] xdr::Error), +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +pub struct Cmd { + #[clap(flatten)] + pub xdr_args: super::xdr::Args, + #[clap(flatten)] + pub config: super::super::config::Args, +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + let res = self.simulate().await?; + println!("{}", res.transaction().to_xdr_base64(xdr::Limits::none())?); + Ok(()) + } + + pub async fn simulate(&self) -> Result { + let tx = self.xdr_args.txn()?; + let network = self.config.get_network()?; + let client = crate::rpc::Client::new(&network.rpc_url)?; + Ok(client.create_assembled_transaction(&tx).await?) + } +} diff --git a/cmd/soroban-cli/src/commands/txn/xdr.rs b/cmd/soroban-cli/src/commands/txn/xdr.rs new file mode 100644 index 000000000..180c12e6b --- /dev/null +++ b/cmd/soroban-cli/src/commands/txn/xdr.rs @@ -0,0 +1,67 @@ +use std::{ + io::{stdin, Read}, + path::PathBuf, +}; + +use soroban_env_host::xdr::ReadXdr; +use soroban_sdk::xdr::{Limits, Transaction, TransactionEnvelope}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("failed to decode XDR from base64")] + Base64Decode, + #[error("failed to decode XDR from file: {0}")] + FileDecode(PathBuf), + #[error("failed to decode XDR from stdin")] + StdinDecode, + #[error(transparent)] + Io(#[from] std::io::Error), +} + +/// XDR input, either base64 encoded or file path and stdin if neither is provided +#[derive(Debug, clap::Args, Clone)] +#[group(skip)] +pub struct Args { + /// Base64 encoded XDR transaction + #[arg( + long = "xdr-base64", + env = "STELLAR_TXN_XDR_BASE64", + conflicts_with = "xdr_file" + )] + pub xdr_base64: Option, + //// File containing Binary encoded data + #[arg( + long = "xdr-file", + env = "STELLAR_TXN_XDR_FILE", + conflicts_with = "xdr_base64" + )] + pub xdr_file: Option, +} + +impl Args { + pub fn xdr(&self) -> Result { + match (self.xdr_base64.as_ref(), self.xdr_file.as_ref()) { + (Some(xdr_base64), None) => { + T::from_xdr_base64(xdr_base64, Limits::none()).map_err(|_| Error::Base64Decode) + } + (_, Some(xdr_file)) => T::from_xdr(std::fs::read(xdr_file)?, Limits::none()) + .map_err(|_| Error::FileDecode(xdr_file.clone())), + + _ => { + let mut buf = String::new(); + let _ = stdin() + .read_to_string(&mut buf) + .map_err(|_| Error::StdinDecode)?; + T::from_xdr_base64(buf.trim(), Limits::none()).map_err(|_| Error::StdinDecode) + } + } + } + + pub fn txn(&self) -> Result { + self.xdr::() + } + + pub fn txn_envelope(&self) -> Result { + self.xdr::() + } +} diff --git a/cmd/soroban-cli/src/lib.rs b/cmd/soroban-cli/src/lib.rs index 5cde45436..5f44d6b8b 100644 --- a/cmd/soroban-cli/src/lib.rs +++ b/cmd/soroban-cli/src/lib.rs @@ -12,6 +12,7 @@ pub mod commands; pub mod fee; pub mod key; pub mod log; +pub mod signer; pub mod toid; pub mod utils; pub mod wasm; diff --git a/cmd/soroban-cli/src/signer.rs b/cmd/soroban-cli/src/signer.rs new file mode 100644 index 000000000..bd5605598 --- /dev/null +++ b/cmd/soroban-cli/src/signer.rs @@ -0,0 +1,277 @@ +use ed25519_dalek::Signer; +use sha2::{Digest, Sha256}; + +use soroban_env_host::xdr::{ + self, AccountId, DecoratedSignature, Hash, HashIdPreimage, HashIdPreimageSorobanAuthorization, + InvokeHostFunctionOp, Limits, Operation, OperationBody, PublicKey, ScAddress, ScMap, ScSymbol, + ScVal, Signature, SignatureHint, SorobanAddressCredentials, SorobanAuthorizationEntry, + SorobanAuthorizedFunction, SorobanCredentials, Transaction, TransactionEnvelope, + TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, + TransactionV1Envelope, Uint256, WriteXdr, +}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Xdr(#[from] xdr::Error), + #[error("Error signing transaction {address}")] + MissingSignerForAddress { address: String }, +} + +fn requires_auth(txn: &Transaction) -> Option { + let [op @ Operation { + body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { auth, .. }), + .. + }] = txn.operations.as_slice() + else { + return None; + }; + matches!( + auth.first().map(|x| &x.root_invocation.function), + Some(&SorobanAuthorizedFunction::ContractFn(_)) + ) + .then(move || op.clone()) +} + +/// A trait for signing Stellar transactions and Soroban authorization entries +pub trait Stellar { + /// The type of the options that can be passed when creating a new signer + type Init; + /// Create a new signer with the given network passphrase and options + fn new(network_passphrase: &str, options: Option) -> Self; + + /// Get the network hash + fn network_hash(&self) -> xdr::Hash; + + /// Sign a transaction hash with the given source account + /// # Errors + /// Returns an error if the source account is not found + fn sign_txn_hash( + &self, + txn: [u8; 32], + source_account: &stellar_strkey::Strkey, + ) -> Result; + + /// Sign a Soroban authorization entry with the given address + /// # Errors + /// Returns an error if the address is not found + fn sign_soroban_authorization_entry( + &self, + unsigned_entry: &SorobanAuthorizationEntry, + signature_expiration_ledger: u32, + address: &[u8; 32], + ) -> Result; + + /// Sign a Stellar transaction with the given source account + /// This is a default implementation that signs the transaction hash and returns a decorated signature + /// # Errors + /// Returns an error if the source account is not found + fn sign_txn( + &self, + txn: Transaction, + source_account: &stellar_strkey::Strkey, + ) -> Result { + let signature_payload = TransactionSignaturePayload { + network_id: self.network_hash(), + tagged_transaction: TransactionSignaturePayloadTaggedTransaction::Tx(txn.clone()), + }; + let hash = Sha256::digest(signature_payload.to_xdr(Limits::none())?).into(); + let decorated_signature = self.sign_txn_hash(hash, source_account)?; + Ok(TransactionEnvelope::Tx(TransactionV1Envelope { + tx: txn, + signatures: vec![decorated_signature].try_into()?, + })) + } + + /// Sign a Soroban authorization entries for a given transaction and set the expiration ledger + /// # Errors + /// Returns an error if the address is not found + fn sign_soroban_authorizations( + &self, + raw: &Transaction, + signature_expiration_ledger: u32, + ) -> Result, Error> { + let mut tx = raw.clone(); + let Some(mut op) = requires_auth(&tx) else { + return Ok(None); + }; + + let xdr::Operation { + body: OperationBody::InvokeHostFunction(ref mut body), + .. + } = op + else { + return Ok(None); + }; + + let signed_auths = body + .auth + .as_slice() + .iter() + .map(|raw_auth| { + self.maybe_sign_soroban_authorization_entry(raw_auth, signature_expiration_ledger) + }) + .collect::, Error>>()?; + + body.auth = signed_auths.try_into()?; + tx.operations = vec![op].try_into()?; + Ok(Some(tx)) + } + + /// Sign a Soroban authorization entry if the address is public key + /// # Errors + /// Returns an error if the address in entry is a contract + fn maybe_sign_soroban_authorization_entry( + &self, + unsigned_entry: &SorobanAuthorizationEntry, + signature_expiration_ledger: u32, + ) -> Result { + if let SorobanAuthorizationEntry { + credentials: SorobanCredentials::Address(SorobanAddressCredentials { ref address, .. }), + .. + } = unsigned_entry + { + // See if we have a signer for this authorizationEntry + // If not, then we Error + let needle = match address { + ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(ref a)))) => a, + ScAddress::Contract(Hash(c)) => { + // This address is for a contract. This means we're using a custom + // smart-contract account. Currently the CLI doesn't support that yet. + return Err(Error::MissingSignerForAddress { + address: stellar_strkey::Strkey::Contract(stellar_strkey::Contract(*c)) + .to_string(), + }); + } + }; + self.sign_soroban_authorization_entry( + unsigned_entry, + signature_expiration_ledger, + needle, + ) + } else { + Ok(unsigned_entry.clone()) + } + } +} + +use std::fmt::Debug; +#[derive(Debug)] +pub struct InMemory { + pub network_passphrase: String, + pub keypairs: Vec, +} + +impl InMemory { + pub fn get_key( + &self, + key: &stellar_strkey::Strkey, + ) -> Result<&ed25519_dalek::SigningKey, Error> { + match key { + stellar_strkey::Strkey::PublicKeyEd25519(stellar_strkey::ed25519::PublicKey(bytes)) => { + self.keypairs + .iter() + .find(|k| k.verifying_key().to_bytes() == *bytes) + } + _ => None, + } + .ok_or_else(|| Error::MissingSignerForAddress { + address: key.to_string(), + }) + } +} + +impl Stellar for InMemory { + type Init = Vec; + fn new(network_passphrase: &str, options: Option>) -> Self { + InMemory { + network_passphrase: network_passphrase.to_string(), + keypairs: options.unwrap_or_default(), + } + } + + fn sign_txn_hash( + &self, + txn: [u8; 32], + source_account: &stellar_strkey::Strkey, + ) -> Result { + let source_account = self.get_key(source_account)?; + let tx_signature = source_account.sign(&txn); + Ok(DecoratedSignature { + // TODO: remove this unwrap. It's safe because we know the length of the array + hint: SignatureHint( + source_account.verifying_key().to_bytes()[28..] + .try_into() + .unwrap(), + ), + signature: Signature(tx_signature.to_bytes().try_into()?), + }) + } + + fn sign_soroban_authorization_entry( + &self, + unsigned_entry: &SorobanAuthorizationEntry, + signature_expiration_ledger: u32, + signer: &[u8; 32], + ) -> Result { + let mut auth = unsigned_entry.clone(); + let SorobanAuthorizationEntry { + credentials: SorobanCredentials::Address(ref mut credentials), + .. + } = auth + else { + // Doesn't need special signing + return Ok(auth); + }; + let SorobanAddressCredentials { nonce, .. } = credentials; + + let preimage = HashIdPreimage::SorobanAuthorization(HashIdPreimageSorobanAuthorization { + network_id: self.network_hash(), + invocation: auth.root_invocation.clone(), + nonce: *nonce, + signature_expiration_ledger, + }) + .to_xdr(Limits::none())?; + + let strkey = stellar_strkey::ed25519::PublicKey(*signer); + let payload = Sha256::digest(preimage); + let signer = self.get_key(&stellar_strkey::Strkey::PublicKeyEd25519(strkey))?; + let signature = signer.sign(&payload); + + let map = ScMap::sorted_from(vec![ + ( + ScVal::Symbol(ScSymbol("public_key".try_into()?)), + ScVal::Bytes( + signer + .verifying_key() + .to_bytes() + .to_vec() + .try_into() + .map_err(Error::Xdr)?, + ), + ), + ( + ScVal::Symbol(ScSymbol("signature".try_into()?)), + ScVal::Bytes( + signature + .to_bytes() + .to_vec() + .try_into() + .map_err(Error::Xdr)?, + ), + ), + ]) + .map_err(Error::Xdr)?; + credentials.signature = ScVal::Vec(Some( + vec![ScVal::Map(Some(map))].try_into().map_err(Error::Xdr)?, + )); + credentials.signature_expiration_ledger = signature_expiration_ledger; + auth.credentials = SorobanCredentials::Address(credentials.clone()); + + Ok(auth) + } + + fn network_hash(&self) -> xdr::Hash { + xdr::Hash(Sha256::digest(self.network_passphrase.as_bytes()).into()) + } +} diff --git a/docs/soroban-cli-full-docs.md b/docs/soroban-cli-full-docs.md index b005f1e7f..70935103c 100644 --- a/docs/soroban-cli-full-docs.md +++ b/docs/soroban-cli-full-docs.md @@ -51,6 +51,17 @@ This document contains the help content for the `soroban` command-line program. * [`soroban network start`↴](#soroban-network-start) * [`soroban network stop`↴](#soroban-network-stop) * [`soroban version`↴](#soroban-version) +* [`soroban txn`↴](#soroban-txn) +* [`soroban txn inspect`↴](#soroban-txn-inspect) +* [`soroban txn inspect types`↴](#soroban-txn-inspect-types) +* [`soroban txn inspect types list`↴](#soroban-txn-inspect-types-list) +* [`soroban txn inspect guess`↴](#soroban-txn-inspect-guess) +* [`soroban txn inspect decode`↴](#soroban-txn-inspect-decode) +* [`soroban txn inspect encode`↴](#soroban-txn-inspect-encode) +* [`soroban txn inspect version`↴](#soroban-txn-inspect-version) +* [`soroban txn sign`↴](#soroban-txn-sign) +* [`soroban txn send`↴](#soroban-txn-send) +* [`soroban txn simulate`↴](#soroban-txn-simulate) * [`soroban cache`↴](#soroban-cache) * [`soroban cache clean`↴](#soroban-cache-clean) * [`soroban cache path`↴](#soroban-cache-path) @@ -96,6 +107,7 @@ Full CLI reference: https://github.com/stellar/soroban-tools/tree/main/docs/soro * `xdr` — Decode and encode XDR * `network` — Start and configure networks * `version` — Print version information +* `txn` — Sign, Simulate, and Send transactions * `cache` — Cache for tranasctions and contract specs ###### **Options:** @@ -1314,6 +1326,240 @@ Print version information +## `soroban txn` + +Sign, Simulate, and Send transactions + +**Usage:** `soroban txn ` + +###### **Subcommands:** + +* `inspect` — Add a new identity (keypair, ledger, macOS keychain) +* `sign` — Given an identity return its address (public key) +* `send` — Submit a transaction to the network +* `simulate` — Simulate a transaction + + + +## `soroban txn inspect` + +Add a new identity (keypair, ledger, macOS keychain) + +**Usage:** `soroban txn inspect [CHANNEL] ` + +###### **Subcommands:** + +* `types` — View information about types +* `guess` — Guess the XDR type +* `decode` — Decode XDR +* `encode` — Encode XDR +* `version` — Print version information + +###### **Arguments:** + +* `` — Channel of XDR to operate on + + Default value: `+curr` + + Possible values: `+curr`, `+next` + + + + +## `soroban txn inspect types` + +View information about types + +**Usage:** `soroban txn inspect types ` + +###### **Subcommands:** + +* `list` — + + + +## `soroban txn inspect types list` + +**Usage:** `soroban txn inspect types list [OPTIONS]` + +###### **Options:** + +* `--output ` + + Default value: `plain` + + Possible values: `plain`, `json`, `json-formatted` + + + + +## `soroban txn inspect guess` + +Guess the XDR type + +**Usage:** `soroban txn inspect guess [OPTIONS] [FILE]` + +###### **Arguments:** + +* `` — File to decode, or stdin if omitted + +###### **Options:** + +* `--input ` + + Default value: `single-base64` + + Possible values: `single`, `single-base64`, `stream`, `stream-base64`, `stream-framed` + +* `--output ` + + Default value: `list` + + Possible values: `list` + +* `--certainty ` — Certainty as an arbitrary value + + Default value: `2` + + + +## `soroban txn inspect decode` + +Decode XDR + +**Usage:** `soroban txn inspect decode [OPTIONS] --type [FILES]...` + +###### **Arguments:** + +* `` — Files to decode, or stdin if omitted + +###### **Options:** + +* `--type ` — XDR type to decode +* `--input ` + + Default value: `stream-base64` + + Possible values: `single`, `single-base64`, `stream`, `stream-base64`, `stream-framed` + +* `--output ` + + Default value: `json` + + Possible values: `json`, `json-formatted`, `rust-debug`, `rust-debug-formatted` + + + + +## `soroban txn inspect encode` + +Encode XDR + +**Usage:** `soroban txn inspect encode [OPTIONS] --type [FILES]...` + +###### **Arguments:** + +* `` — Files to encode, or stdin if omitted + +###### **Options:** + +* `--type ` — XDR type to encode +* `--input ` + + Default value: `json` + + Possible values: `json` + +* `--output ` + + Default value: `single-base64` + + Possible values: `single`, `single-base64` + + + + +## `soroban txn inspect version` + +Print version information + +**Usage:** `soroban txn inspect version` + + + +## `soroban txn sign` + +Given an identity return its address (public key) + +**Usage:** `soroban txn sign [OPTIONS] --source-account ` + +###### **Options:** + +* `-y`, `--yes` — Confirm that a signature can be signed by the given keypair automatically + + Possible values: `true`, `false` + +* `--xdr-base64 ` — Base64 encoded XDR transaction +* `--xdr-file ` +* `--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 +* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--global` — Use global config + + Possible values: `true`, `false` + +* `--config-dir ` — Location of config directory, default is "." + + + +## `soroban txn send` + +Submit a transaction to the network + +**Usage:** `soroban txn send [OPTIONS] --source-account ` + +###### **Options:** + +* `--xdr-base64 ` — Base64 encoded XDR transaction +* `--xdr-file ` +* `--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 +* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--global` — Use global config + + Possible values: `true`, `false` + +* `--config-dir ` — Location of config directory, default is "." + + + +## `soroban txn simulate` + +Simulate a transaction + +**Usage:** `soroban txn simulate [OPTIONS] --source-account ` + +###### **Options:** + +* `--xdr-base64 ` — Base64 encoded XDR transaction +* `--xdr-file ` +* `--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 +* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--global` — Use global config + + Possible values: `true`, `false` + +* `--config-dir ` — Location of config directory, default is "." + + + ## `soroban cache` Cache for tranasctions and contract specs From 10ac181018ed1408d54df3b618c11cb10532040e Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Fri, 10 May 2024 10:09:05 +1000 Subject: [PATCH 05/30] fix that should have been part of the merge --- cmd/soroban-cli/src/commands/contract/fetch.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/fetch.rs b/cmd/soroban-cli/src/commands/contract/fetch.rs index 5898a8385..73ba12c2d 100644 --- a/cmd/soroban-cli/src/commands/contract/fetch.rs +++ b/cmd/soroban-cli/src/commands/contract/fetch.rs @@ -74,8 +74,6 @@ pub enum Error { Io(#[from] std::io::Error), #[error("missing result")] MissingResult, - #[error("Unexpected XDR")] - UnexpectedXdr, #[error("unexpected contract code data type: {0:?}")] UnexpectedContractCodeDataType(LedgerEntryData), #[error("reading file {0:?}: {1}")] From d1d6e335125c2676061750471ad698526cb2a1e2 Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Fri, 10 May 2024 10:09:45 +1000 Subject: [PATCH 06/30] fix that should have been part of the merge --- cmd/soroban-cli/src/commands/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/soroban-cli/src/commands/mod.rs b/cmd/soroban-cli/src/commands/mod.rs index 7ffe1d43b..895066e38 100644 --- a/cmd/soroban-cli/src/commands/mod.rs +++ b/cmd/soroban-cli/src/commands/mod.rs @@ -13,9 +13,10 @@ pub mod keys; pub mod network; pub mod plugin; pub mod txn; -pub mod txn_result; pub mod version; +pub mod txn_result; + pub const HEADING_RPC: &str = "Options (RPC)"; const ABOUT: &str = "Build, deploy, & interact with contracts; set identities to sign with; configure networks; generate keys; and more. From 0cf656d161a2cd953c8b2acf42e20118f4332838 Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Fri, 10 May 2024 10:19:10 +1000 Subject: [PATCH 07/30] rename txn to tx --- cmd/soroban-cli/src/commands/mod.rs | 8 ++++---- cmd/soroban-cli/src/commands/{txn => tx}/mod.rs | 0 cmd/soroban-cli/src/commands/{txn => tx}/send.rs | 0 cmd/soroban-cli/src/commands/{txn => tx}/sign.rs | 0 cmd/soroban-cli/src/commands/{txn => tx}/simulate.rs | 0 cmd/soroban-cli/src/commands/{txn => tx}/xdr.rs | 0 6 files changed, 4 insertions(+), 4 deletions(-) rename cmd/soroban-cli/src/commands/{txn => tx}/mod.rs (100%) rename cmd/soroban-cli/src/commands/{txn => tx}/send.rs (100%) rename cmd/soroban-cli/src/commands/{txn => tx}/sign.rs (100%) rename cmd/soroban-cli/src/commands/{txn => tx}/simulate.rs (100%) rename cmd/soroban-cli/src/commands/{txn => tx}/xdr.rs (100%) diff --git a/cmd/soroban-cli/src/commands/mod.rs b/cmd/soroban-cli/src/commands/mod.rs index 895066e38..5ae074fc6 100644 --- a/cmd/soroban-cli/src/commands/mod.rs +++ b/cmd/soroban-cli/src/commands/mod.rs @@ -12,7 +12,7 @@ pub mod global; pub mod keys; pub mod network; pub mod plugin; -pub mod txn; +pub mod tx; pub mod version; pub mod txn_result; @@ -102,7 +102,7 @@ impl Root { Cmd::Network(network) => network.run().await?, Cmd::Version(version) => version.run(), Cmd::Keys(id) => id.run().await?, - Cmd::Txn(tx) => tx.run().await?, + Cmd::Tx(tx) => tx.run().await?, Cmd::Cache(data) => data.run()?, }; Ok(()) @@ -139,7 +139,7 @@ pub enum Cmd { Version(version::Cmd), /// Sign, Simulate, and Send transactions #[command(subcommand)] - Txn(txn::Cmd), + Tx(tx::Cmd), /// Cache for tranasctions and contract specs #[command(subcommand)] Cache(cache::Cmd), @@ -163,7 +163,7 @@ pub enum Error { #[error(transparent)] Network(#[from] network::Error), #[error(transparent)] - Txn(#[from] txn::Error), + Tx(#[from] tx::Error), #[error(transparent)] Cache(#[from] cache::Error), } diff --git a/cmd/soroban-cli/src/commands/txn/mod.rs b/cmd/soroban-cli/src/commands/tx/mod.rs similarity index 100% rename from cmd/soroban-cli/src/commands/txn/mod.rs rename to cmd/soroban-cli/src/commands/tx/mod.rs diff --git a/cmd/soroban-cli/src/commands/txn/send.rs b/cmd/soroban-cli/src/commands/tx/send.rs similarity index 100% rename from cmd/soroban-cli/src/commands/txn/send.rs rename to cmd/soroban-cli/src/commands/tx/send.rs diff --git a/cmd/soroban-cli/src/commands/txn/sign.rs b/cmd/soroban-cli/src/commands/tx/sign.rs similarity index 100% rename from cmd/soroban-cli/src/commands/txn/sign.rs rename to cmd/soroban-cli/src/commands/tx/sign.rs diff --git a/cmd/soroban-cli/src/commands/txn/simulate.rs b/cmd/soroban-cli/src/commands/tx/simulate.rs similarity index 100% rename from cmd/soroban-cli/src/commands/txn/simulate.rs rename to cmd/soroban-cli/src/commands/tx/simulate.rs diff --git a/cmd/soroban-cli/src/commands/txn/xdr.rs b/cmd/soroban-cli/src/commands/tx/xdr.rs similarity index 100% rename from cmd/soroban-cli/src/commands/txn/xdr.rs rename to cmd/soroban-cli/src/commands/tx/xdr.rs From 151026a7d720fd9f3123bcb43397bcd918656e58 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Sun, 12 May 2024 18:34:03 -0400 Subject: [PATCH 08/30] fix: remove `tx sign` --- cmd/soroban-cli/src/commands/tx/mod.rs | 8 +- cmd/soroban-cli/src/commands/tx/sign.rs | 97 ------------------------- 2 files changed, 1 insertion(+), 104 deletions(-) delete mode 100644 cmd/soroban-cli/src/commands/tx/sign.rs diff --git a/cmd/soroban-cli/src/commands/tx/mod.rs b/cmd/soroban-cli/src/commands/tx/mod.rs index 9de360fcc..1d1c8c976 100644 --- a/cmd/soroban-cli/src/commands/tx/mod.rs +++ b/cmd/soroban-cli/src/commands/tx/mod.rs @@ -1,7 +1,7 @@ use clap::Parser; pub mod send; -pub mod sign; + pub mod simulate; pub mod xdr; @@ -11,8 +11,6 @@ use stellar_xdr::cli as xdr_cli; pub enum Cmd { /// Add a new identity (keypair, ledger, macOS keychain) Inspect(xdr_cli::Root), - /// Given an identity return its address (public key) - Sign(sign::Cmd), /// Submit a transaction to the network Send(send::Cmd), /// Simulate a transaction @@ -27,9 +25,6 @@ pub enum Error { /// An error during the inspect #[error(transparent)] Inspect(#[from] xdr_cli::Error), - /// An error during the sign - #[error(transparent)] - Sign(#[from] sign::Error), /// An error during the send #[error(transparent)] Send(#[from] send::Error), @@ -39,7 +34,6 @@ impl Cmd { pub async fn run(&self) -> Result<(), Error> { match self { Cmd::Inspect(cmd) => cmd.run()?, - Cmd::Sign(cmd) => cmd.run().await?, Cmd::Send(cmd) => cmd.run().await?, Cmd::Simulate(cmd) => cmd.run().await?, }; diff --git a/cmd/soroban-cli/src/commands/tx/sign.rs b/cmd/soroban-cli/src/commands/tx/sign.rs deleted file mode 100644 index ec4bb1155..000000000 --- a/cmd/soroban-cli/src/commands/tx/sign.rs +++ /dev/null @@ -1,97 +0,0 @@ -use std::io; - -// use crossterm::{ -// event::{self, Event, KeyCode, KeyEvent, KeyModifiers}, -// execute, -// terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}, -// }; -use soroban_sdk::xdr::{self, Limits, TransactionEnvelope, WriteXdr}; - -use crate::signer::{self, InMemory, Stellar}; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - XdrArgs(#[from] super::xdr::Error), - #[error(transparent)] - Signer(#[from] signer::Error), - #[error(transparent)] - Config(#[from] super::super::config::Error), - #[error(transparent)] - StellarStrkey(#[from] stellar_strkey::DecodeError), - #[error(transparent)] - Xdr(#[from] xdr::Error), - #[error(transparent)] - Io(#[from] io::Error), - #[error("User cancelled signing, perhaps need to add -y")] - UserCancelledSigning, -} - -#[derive(Debug, clap::Parser, Clone)] -#[group(skip)] -pub struct Cmd { - /// Confirm that a signature can be signed by the given keypair automatically. - #[arg(long, short = 'y')] - yes: bool, - #[clap(flatten)] - pub xdr_args: super::xdr::Args, - #[clap(flatten)] - pub config: super::super::config::Args, -} - -impl Cmd { - #[allow(clippy::unused_async)] - pub async fn run(&self) -> Result<(), Error> { - let envelope = self.sign()?; - println!("{}", envelope.to_xdr_base64(Limits::none())?.trim()); - Ok(()) - } - - pub fn sign(&self) -> Result { - let source = &self.config.source_account; - tracing::debug!("signing transaction with source account {}", source); - let txn = self.xdr_args.txn()?; - let key = self.config.key_pair()?; - let address = - stellar_strkey::ed25519::PublicKey::from_payload(key.verifying_key().as_bytes())?; - let in_memory = InMemory { - network_passphrase: self.config.get_network()?.network_passphrase, - keypairs: vec![key], - }; - self.prompt_user()?; - Ok(in_memory.sign_txn(txn, &stellar_strkey::Strkey::PublicKeyEd25519(address))?) - } - - pub fn prompt_user(&self) -> Result<(), Error> { - if self.yes { - return Ok(()); - } - Err(Error::UserCancelledSigning) - // TODO use crossterm to prompt user for confirmation - // // Set up the terminal - // let mut stdout = io::stdout(); - // execute!(stdout, EnterAlternateScreen)?; - // terminal::enable_raw_mode()?; - - // println!("Press 'y' or 'Y' for yes, any other key for no:"); - - // loop { - // if let Event::Key(KeyEvent { - // code, - // modifiers: KeyModifiers::NONE, - // .. - // }) = event::read()? - // { - // match code { - // KeyCode::Char('y' | 'Y') => break, - // _ => return Err(Error::UserCancelledSigning), - // } - // } - // } - - // // Clean up the terminal - // terminal::disable_raw_mode()?; - // execute!(stdout, LeaveAlternateScreen)?; - // Ok(()) - } -} From a43ac7a86ca714282f5b169e7dadfcd4aed725ff Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Mon, 13 May 2024 12:53:07 -0400 Subject: [PATCH 09/30] Merge remote-tracking branch 'origin/main' into feat/txn-command --- .github/workflows/full-help-docs.yml | 4 +- Cargo.lock | 7 + Cargo.toml | 1 + ...oban-cli-full-docs.md => FULL_HELP_DOCS.md | 434 ++++++++---------- Makefile | 4 + README.md | 2 +- cmd/soroban-cli/README.md | 4 +- cmd/soroban-cli/src/bin/doc-gen.rs | 12 +- cmd/soroban-cli/src/bin/main.rs | 53 +-- cmd/soroban-cli/src/cli.rs | 76 +++ cmd/soroban-cli/src/commands/config/mod.rs | 2 +- .../src/commands/contract/deploy/asset.rs | 15 +- .../src/commands/contract/deploy/wasm.rs | 17 +- .../src/commands/contract/extend.rs | 8 +- .../src/commands/contract/fetch.rs | 2 +- .../src/commands/contract/install.rs | 10 +- .../src/commands/contract/invoke.rs | 23 +- .../src/commands/contract/restore.rs | 11 +- cmd/soroban-cli/src/commands/global.rs | 6 +- cmd/soroban-cli/src/commands/keys/ls.rs | 6 +- cmd/soroban-cli/src/commands/mod.rs | 19 +- cmd/soroban-cli/src/commands/network/mod.rs | 10 +- cmd/soroban-cli/src/commands/txn_result.rs | 31 +- cmd/soroban-cli/src/commands/version.rs | 2 +- cmd/soroban-cli/src/fee.rs | 2 +- cmd/soroban-cli/src/lib.rs | 3 + cmd/soroban-cli/src/utils.rs | 30 +- cmd/stellar-cli/Cargo.toml | 28 ++ cmd/stellar-cli/README.md | 28 ++ cmd/stellar-cli/src/bin/main.rs | 3 + docs/README.md | 3 - 31 files changed, 472 insertions(+), 384 deletions(-) rename docs/soroban-cli-full-docs.md => FULL_HELP_DOCS.md (79%) create mode 100644 cmd/soroban-cli/src/cli.rs create mode 100644 cmd/stellar-cli/Cargo.toml create mode 100644 cmd/stellar-cli/README.md create mode 100644 cmd/stellar-cli/src/bin/main.rs delete mode 100644 docs/README.md diff --git a/.github/workflows/full-help-docs.yml b/.github/workflows/full-help-docs.yml index c8fbd9e02..4893a88fd 100644 --- a/.github/workflows/full-help-docs.yml +++ b/.github/workflows/full-help-docs.yml @@ -18,8 +18,8 @@ jobs: # this looks goofy to get GITHUB_OUTPUT to work with multi-line return values; # see https://stackoverflow.com/a/74266196/249801 run: | - cargo md-gen - raw_diff=$(git diff docs/soroban-cli-full-docs.md) + make generate-full-help-doc + raw_diff=$(git diff FULL_HELP_DOCS.md) if [ "$raw_diff" != "" ]; then echo ""; echo "Unexpected docs change:"; echo "======================="; echo ""; echo "$raw_diff"; echo ""; echo "======================="; echo ""; fi echo diff=$raw_diff >> $GITHUB_OUTPUT id: doc-gen diff --git a/Cargo.lock b/Cargo.lock index 09d548fd4..46ffe86eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4024,6 +4024,13 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stellar-cli" +version = "21.0.0-preview.1" +dependencies = [ + "soroban-cli", +] + [[package]] name = "stellar-rpc-client" version = "21.0.1" diff --git a/Cargo.toml b/Cargo.toml index 0f91b7153..97450ffca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] resolver = "2" members = [ + "cmd/stellar-cli", "cmd/soroban-cli", "cmd/crates/*", "cmd/crates/soroban-test/tests/fixtures/test-wasms/*", diff --git a/docs/soroban-cli-full-docs.md b/FULL_HELP_DOCS.md similarity index 79% rename from docs/soroban-cli-full-docs.md rename to FULL_HELP_DOCS.md index 70935103c..57f27dbbe 100644 --- a/docs/soroban-cli-full-docs.md +++ b/FULL_HELP_DOCS.md @@ -1,102 +1,100 @@ -# Command-Line Help for `soroban` +# Command-Line Help for `stellar` -This document contains the help content for the `soroban` command-line program. +This document contains the help content for the `stellar` command-line program. **Command Overview:** -* [`soroban`↴](#soroban) -* [`soroban completion`↴](#soroban-completion) -* [`soroban contract`↴](#soroban-contract) -* [`soroban contract asset`↴](#soroban-contract-asset) -* [`soroban contract asset id`↴](#soroban-contract-asset-id) -* [`soroban contract asset deploy`↴](#soroban-contract-asset-deploy) -* [`soroban contract bindings`↴](#soroban-contract-bindings) -* [`soroban contract bindings json`↴](#soroban-contract-bindings-json) -* [`soroban contract bindings rust`↴](#soroban-contract-bindings-rust) -* [`soroban contract bindings typescript`↴](#soroban-contract-bindings-typescript) -* [`soroban contract build`↴](#soroban-contract-build) -* [`soroban contract extend`↴](#soroban-contract-extend) -* [`soroban contract deploy`↴](#soroban-contract-deploy) -* [`soroban contract fetch`↴](#soroban-contract-fetch) -* [`soroban contract id`↴](#soroban-contract-id) -* [`soroban contract id asset`↴](#soroban-contract-id-asset) -* [`soroban contract id wasm`↴](#soroban-contract-id-wasm) -* [`soroban contract init`↴](#soroban-contract-init) -* [`soroban contract inspect`↴](#soroban-contract-inspect) -* [`soroban contract install`↴](#soroban-contract-install) -* [`soroban contract invoke`↴](#soroban-contract-invoke) -* [`soroban contract optimize`↴](#soroban-contract-optimize) -* [`soroban contract read`↴](#soroban-contract-read) -* [`soroban contract restore`↴](#soroban-contract-restore) -* [`soroban events`↴](#soroban-events) -* [`soroban keys`↴](#soroban-keys) -* [`soroban keys add`↴](#soroban-keys-add) -* [`soroban keys address`↴](#soroban-keys-address) -* [`soroban keys fund`↴](#soroban-keys-fund) -* [`soroban keys generate`↴](#soroban-keys-generate) -* [`soroban keys ls`↴](#soroban-keys-ls) -* [`soroban keys rm`↴](#soroban-keys-rm) -* [`soroban keys show`↴](#soroban-keys-show) -* [`soroban xdr`↴](#soroban-xdr) -* [`soroban xdr types`↴](#soroban-xdr-types) -* [`soroban xdr types list`↴](#soroban-xdr-types-list) -* [`soroban xdr guess`↴](#soroban-xdr-guess) -* [`soroban xdr decode`↴](#soroban-xdr-decode) -* [`soroban xdr encode`↴](#soroban-xdr-encode) -* [`soroban xdr version`↴](#soroban-xdr-version) -* [`soroban network`↴](#soroban-network) -* [`soroban network add`↴](#soroban-network-add) -* [`soroban network rm`↴](#soroban-network-rm) -* [`soroban network ls`↴](#soroban-network-ls) -* [`soroban network start`↴](#soroban-network-start) -* [`soroban network stop`↴](#soroban-network-stop) -* [`soroban version`↴](#soroban-version) -* [`soroban txn`↴](#soroban-txn) -* [`soroban txn inspect`↴](#soroban-txn-inspect) -* [`soroban txn inspect types`↴](#soroban-txn-inspect-types) -* [`soroban txn inspect types list`↴](#soroban-txn-inspect-types-list) -* [`soroban txn inspect guess`↴](#soroban-txn-inspect-guess) -* [`soroban txn inspect decode`↴](#soroban-txn-inspect-decode) -* [`soroban txn inspect encode`↴](#soroban-txn-inspect-encode) -* [`soroban txn inspect version`↴](#soroban-txn-inspect-version) -* [`soroban txn sign`↴](#soroban-txn-sign) -* [`soroban txn send`↴](#soroban-txn-send) -* [`soroban txn simulate`↴](#soroban-txn-simulate) -* [`soroban cache`↴](#soroban-cache) -* [`soroban cache clean`↴](#soroban-cache-clean) -* [`soroban cache path`↴](#soroban-cache-path) -* [`soroban cache actionlog`↴](#soroban-cache-actionlog) -* [`soroban cache actionlog ls`↴](#soroban-cache-actionlog-ls) -* [`soroban cache actionlog read`↴](#soroban-cache-actionlog-read) - -## `soroban` +* [`stellar`↴](#stellar) +* [`stellar completion`↴](#stellar-completion) +* [`stellar contract`↴](#stellar-contract) +* [`stellar contract asset`↴](#stellar-contract-asset) +* [`stellar contract asset id`↴](#stellar-contract-asset-id) +* [`stellar contract asset deploy`↴](#stellar-contract-asset-deploy) +* [`stellar contract bindings`↴](#stellar-contract-bindings) +* [`stellar contract bindings json`↴](#stellar-contract-bindings-json) +* [`stellar contract bindings rust`↴](#stellar-contract-bindings-rust) +* [`stellar contract bindings typescript`↴](#stellar-contract-bindings-typescript) +* [`stellar contract build`↴](#stellar-contract-build) +* [`stellar contract extend`↴](#stellar-contract-extend) +* [`stellar contract deploy`↴](#stellar-contract-deploy) +* [`stellar contract fetch`↴](#stellar-contract-fetch) +* [`stellar contract id`↴](#stellar-contract-id) +* [`stellar contract id asset`↴](#stellar-contract-id-asset) +* [`stellar contract id wasm`↴](#stellar-contract-id-wasm) +* [`stellar contract init`↴](#stellar-contract-init) +* [`stellar contract inspect`↴](#stellar-contract-inspect) +* [`stellar contract install`↴](#stellar-contract-install) +* [`stellar contract invoke`↴](#stellar-contract-invoke) +* [`stellar contract optimize`↴](#stellar-contract-optimize) +* [`stellar contract read`↴](#stellar-contract-read) +* [`stellar contract restore`↴](#stellar-contract-restore) +* [`stellar events`↴](#stellar-events) +* [`stellar keys`↴](#stellar-keys) +* [`stellar keys add`↴](#stellar-keys-add) +* [`stellar keys address`↴](#stellar-keys-address) +* [`stellar keys fund`↴](#stellar-keys-fund) +* [`stellar keys generate`↴](#stellar-keys-generate) +* [`stellar keys ls`↴](#stellar-keys-ls) +* [`stellar keys rm`↴](#stellar-keys-rm) +* [`stellar keys show`↴](#stellar-keys-show) +* [`stellar xdr`↴](#stellar-xdr) +* [`stellar xdr types`↴](#stellar-xdr-types) +* [`stellar xdr types list`↴](#stellar-xdr-types-list) +* [`stellar xdr guess`↴](#stellar-xdr-guess) +* [`stellar xdr decode`↴](#stellar-xdr-decode) +* [`stellar xdr encode`↴](#stellar-xdr-encode) +* [`stellar xdr version`↴](#stellar-xdr-version) +* [`stellar network`↴](#stellar-network) +* [`stellar network add`↴](#stellar-network-add) +* [`stellar network rm`↴](#stellar-network-rm) +* [`stellar network ls`↴](#stellar-network-ls) +* [`stellar network start`↴](#stellar-network-start) +* [`stellar network stop`↴](#stellar-network-stop) +* [`stellar version`↴](#stellar-version) +* [`stellar tx`↴](#stellar-tx) +* [`stellar tx inspect`↴](#stellar-tx-inspect) +* [`stellar tx inspect types`↴](#stellar-tx-inspect-types) +* [`stellar tx inspect types list`↴](#stellar-tx-inspect-types-list) +* [`stellar tx inspect guess`↴](#stellar-tx-inspect-guess) +* [`stellar tx inspect decode`↴](#stellar-tx-inspect-decode) +* [`stellar tx inspect encode`↴](#stellar-tx-inspect-encode) +* [`stellar tx inspect version`↴](#stellar-tx-inspect-version) +* [`stellar tx send`↴](#stellar-tx-send) +* [`stellar tx simulate`↴](#stellar-tx-simulate) +* [`stellar cache`↴](#stellar-cache) +* [`stellar cache clean`↴](#stellar-cache-clean) +* [`stellar cache path`↴](#stellar-cache-path) +* [`stellar cache actionlog`↴](#stellar-cache-actionlog) +* [`stellar cache actionlog ls`↴](#stellar-cache-actionlog-ls) +* [`stellar cache actionlog read`↴](#stellar-cache-actionlog-read) + +## `stellar` Build, deploy, & interact with contracts; set identities to sign with; configure networks; generate keys; and more. -Intro: https://soroban.stellar.org/docs -CLI Reference: https://github.com/stellar/soroban-cli/tree/main/docs/soroban-cli-full-docs.md +Stellar Docs: https://developers.stellar.org +CLI Full Hep Docs: https://github.com/stellar/soroban-cli/tree/main/FULL_HELP_DOCS.md The easiest way to get started is to generate a new identity: - soroban config identity generate alice + stellar config identity generate alice You can use identities with the `--source` flag in other commands later. Commands that relate to smart contract interactions are organized under the `contract` subcommand. List them: - soroban contract --help + stellar contract --help -A Soroban contract has its interface schema types embedded in the binary that gets deployed on-chain, making it possible to dynamically generate a custom CLI for each. `soroban contract invoke` makes use of this: +A Soroban contract has its interface schema types embedded in the binary that gets deployed on-chain, making it possible to dynamically generate a custom CLI for each. The invoke subcommand makes use of this: - soroban contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- --help + stellar contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- --help Anything after the `--` double dash (the "slop") is parsed as arguments to the contract-specific CLI, generated on-the-fly from the embedded schema. For the hello world example, with a function called `hello` that takes one string argument `to`, here's how you invoke it: - soroban contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- hello --to world + stellar contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- hello --to world -Full CLI reference: https://github.com/stellar/soroban-tools/tree/main/docs/soroban-cli-full-docs.md -**Usage:** `soroban [OPTIONS] ` +**Usage:** `stellar [OPTIONS] ` ###### **Subcommands:** @@ -107,7 +105,7 @@ Full CLI reference: https://github.com/stellar/soroban-tools/tree/main/docs/soro * `xdr` — Decode and encode XDR * `network` — Start and configure networks * `version` — Print version information -* `txn` — Sign, Simulate, and Send transactions +* `tx` — Sign, Simulate, and Send transactions * `cache` — Cache for tranasctions and contract specs ###### **Options:** @@ -117,7 +115,7 @@ Full CLI reference: https://github.com/stellar/soroban-tools/tree/main/docs/soro Possible values: `true`, `false` * `--config-dir ` — Location of config directory, default is "." -* `-f`, `--filter-logs ` — Filter logs output. To turn on "soroban_cli::log::footprint=debug" or off "=off". Can also use env var `RUST_LOG` +* `-f`, `--filter-logs ` — Filter logs output. To turn on "stellar_cli::log::footprint=debug" or off "=off". Can also use env var `RUST_LOG` * `-q`, `--quiet` — Do not write logs to stderr including `INFO` Possible values: `true`, `false` @@ -130,7 +128,7 @@ Full CLI reference: https://github.com/stellar/soroban-tools/tree/main/docs/soro Possible values: `true`, `false` -* `--list` — List installed plugins. E.g. `soroban-hello` +* `--list` — List installed plugins. E.g. `stellar-hello` Possible values: `true`, `false` @@ -141,7 +139,7 @@ Full CLI reference: https://github.com/stellar/soroban-tools/tree/main/docs/soro -## `soroban completion` +## `stellar completion` Print shell completion code for the specified shell @@ -154,7 +152,7 @@ To enable autocomplete in the current bash shell, run: To enable autocomplete permanently, run: echo "source <(soroban completion --shell bash)" >> ~/.bashrc -**Usage:** `soroban completion --shell ` +**Usage:** `stellar completion --shell ` ###### **Options:** @@ -165,11 +163,11 @@ To enable autocomplete permanently, run: -## `soroban contract` +## `stellar contract` Tools for smart contract developers -**Usage:** `soroban contract ` +**Usage:** `stellar contract ` ###### **Subcommands:** @@ -190,11 +188,11 @@ Tools for smart contract developers -## `soroban contract asset` +## `stellar contract asset` Utilities to deploy a Stellar Asset Contract or get its id -**Usage:** `soroban contract asset ` +**Usage:** `stellar contract asset ` ###### **Subcommands:** @@ -203,11 +201,11 @@ Utilities to deploy a Stellar Asset Contract or get its id -## `soroban contract asset id` +## `stellar contract asset id` Get Id of builtin Soroban Asset Contract. Deprecated, use `soroban contract id asset` instead -**Usage:** `soroban contract asset id [OPTIONS] --asset --source-account ` +**Usage:** `stellar contract asset id [OPTIONS] --asset --source-account ` ###### **Options:** @@ -225,11 +223,11 @@ Get Id of builtin Soroban Asset Contract. Deprecated, use `soroban contract id a -## `soroban contract asset deploy` +## `stellar contract asset deploy` Deploy builtin Soroban Asset Contract -**Usage:** `soroban contract asset deploy [OPTIONS] --asset --source-account ` +**Usage:** `stellar contract asset deploy [OPTIONS] --asset --source-account ` ###### **Options:** @@ -263,11 +261,11 @@ Deploy builtin Soroban Asset Contract -## `soroban contract bindings` +## `stellar contract bindings` Generate code client bindings for a contract -**Usage:** `soroban contract bindings ` +**Usage:** `stellar contract bindings ` ###### **Subcommands:** @@ -277,11 +275,11 @@ Generate code client bindings for a contract -## `soroban contract bindings json` +## `stellar contract bindings json` Generate Json Bindings -**Usage:** `soroban contract bindings json --wasm ` +**Usage:** `stellar contract bindings json --wasm ` ###### **Options:** @@ -289,11 +287,11 @@ Generate Json Bindings -## `soroban contract bindings rust` +## `stellar contract bindings rust` Generate Rust bindings -**Usage:** `soroban contract bindings rust --wasm ` +**Usage:** `stellar contract bindings rust --wasm ` ###### **Options:** @@ -301,11 +299,11 @@ Generate Rust bindings -## `soroban contract bindings typescript` +## `stellar contract bindings typescript` Generate a TypeScript / JavaScript package -**Usage:** `soroban contract bindings typescript [OPTIONS] --output-dir --contract-id ` +**Usage:** `stellar contract bindings typescript [OPTIONS] --output-dir --contract-id ` ###### **Options:** @@ -327,7 +325,7 @@ Generate a TypeScript / JavaScript package -## `soroban contract build` +## `stellar contract build` Build a contract from source @@ -335,7 +333,7 @@ Builds all crates that are referenced by the cargo manifest (Cargo.toml) that ha To view the commands that will be executed, without executing them, use the --print-commands-only option. -**Usage:** `soroban contract build [OPTIONS]` +**Usage:** `stellar contract build [OPTIONS]` ###### **Options:** @@ -363,13 +361,13 @@ To view the commands that will be executed, without executing them, use the --pr -## `soroban contract extend` +## `stellar contract extend` Extend the time to live ledger of a contract-data ledger entry. If no keys are specified the contract itself is extended. -**Usage:** `soroban contract extend [OPTIONS] --ledgers-to-extend --durability --source-account ` +**Usage:** `stellar contract extend [OPTIONS] --ledgers-to-extend --durability --source-account ` ###### **Options:** @@ -422,11 +420,11 @@ If no keys are specified the contract itself is extended. -## `soroban contract deploy` +## `stellar contract deploy` Deploy a wasm contract -**Usage:** `soroban contract deploy [OPTIONS] --source-account <--wasm |--wasm-hash >` +**Usage:** `stellar contract deploy [OPTIONS] --source-account <--wasm |--wasm-hash >` ###### **Options:** @@ -468,11 +466,11 @@ Deploy a wasm contract -## `soroban contract fetch` +## `stellar contract fetch` Fetch a contract's Wasm binary -**Usage:** `soroban contract fetch [OPTIONS] --id ` +**Usage:** `stellar contract fetch [OPTIONS] --id ` ###### **Options:** @@ -489,11 +487,11 @@ Fetch a contract's Wasm binary -## `soroban contract id` +## `stellar contract id` Generate the contract id for a given contract or asset -**Usage:** `soroban contract id ` +**Usage:** `stellar contract id ` ###### **Subcommands:** @@ -502,11 +500,11 @@ Generate the contract id for a given contract or asset -## `soroban contract id asset` +## `stellar contract id asset` Deploy builtin Soroban Asset Contract -**Usage:** `soroban contract id asset [OPTIONS] --asset --source-account ` +**Usage:** `stellar contract id asset [OPTIONS] --asset --source-account ` ###### **Options:** @@ -524,11 +522,11 @@ Deploy builtin Soroban Asset Contract -## `soroban contract id wasm` +## `stellar contract id wasm` Deploy normal Wasm Contract -**Usage:** `soroban contract id wasm [OPTIONS] --salt --source-account ` +**Usage:** `stellar contract id wasm [OPTIONS] --salt --source-account ` ###### **Options:** @@ -546,11 +544,11 @@ Deploy normal Wasm Contract -## `soroban contract init` +## `stellar contract init` Initialize a Soroban project with an example contract -**Usage:** `soroban contract init [OPTIONS] ` +**Usage:** `stellar contract init [OPTIONS] ` ###### **Arguments:** @@ -568,11 +566,11 @@ Initialize a Soroban project with an example contract -## `soroban contract inspect` +## `stellar contract inspect` Inspect a WASM file listing contract functions, meta, etc -**Usage:** `soroban contract inspect [OPTIONS] --wasm ` +**Usage:** `stellar contract inspect [OPTIONS] --wasm ` ###### **Options:** @@ -597,11 +595,11 @@ Inspect a WASM file listing contract functions, meta, etc -## `soroban contract install` +## `stellar contract install` Install a WASM file to the ledger without creating a contract instance -**Usage:** `soroban contract install [OPTIONS] --source-account --wasm ` +**Usage:** `stellar contract install [OPTIONS] --source-account --wasm ` ###### **Options:** @@ -641,7 +639,7 @@ Install a WASM file to the ledger without creating a contract instance -## `soroban contract invoke` +## `stellar contract invoke` Invoke a contract function @@ -649,7 +647,7 @@ Generates an "implicit CLI" for the specified contract on-the-fly using the cont soroban contract invoke ... -- --help -**Usage:** `soroban contract invoke [OPTIONS] --id --source-account [-- ...]` +**Usage:** `stellar contract invoke [OPTIONS] --id --source-account [-- ...]` ###### **Arguments:** @@ -691,11 +689,11 @@ soroban contract invoke ... -- --help -## `soroban contract optimize` +## `stellar contract optimize` Optimize a WASM file -**Usage:** `soroban contract optimize [OPTIONS] --wasm ` +**Usage:** `stellar contract optimize [OPTIONS] --wasm ` ###### **Options:** @@ -704,11 +702,11 @@ Optimize a WASM file -## `soroban contract read` +## `stellar contract read` Print the current value of a contract-data ledger entry -**Usage:** `soroban contract read [OPTIONS] --durability --source-account ` +**Usage:** `stellar contract read [OPTIONS] --durability --source-account ` ###### **Options:** @@ -752,13 +750,13 @@ Print the current value of a contract-data ledger entry -## `soroban contract restore` +## `stellar contract restore` Restore an evicted value for a contract-data legder entry. If no keys are specificed the contract itself is restored. -**Usage:** `soroban contract restore [OPTIONS] --durability --source-account ` +**Usage:** `stellar contract restore [OPTIONS] --durability --source-account ` ###### **Options:** @@ -811,11 +809,11 @@ If no keys are specificed the contract itself is restored. -## `soroban events` +## `stellar events` Watch the network for contract events -**Usage:** `soroban events [OPTIONS]` +**Usage:** `stellar events [OPTIONS]` ###### **Options:** @@ -855,11 +853,11 @@ Watch the network for contract events -## `soroban keys` +## `stellar keys` Create and manage identities including keys and addresses -**Usage:** `soroban keys ` +**Usage:** `stellar keys ` ###### **Subcommands:** @@ -873,11 +871,11 @@ Create and manage identities including keys and addresses -## `soroban keys add` +## `stellar keys add` Add a new identity (keypair, ledger, macOS keychain) -**Usage:** `soroban keys add [OPTIONS] ` +**Usage:** `stellar keys add [OPTIONS] ` ###### **Arguments:** @@ -901,11 +899,11 @@ Add a new identity (keypair, ledger, macOS keychain) -## `soroban keys address` +## `stellar keys address` Given an identity return its address (public key) -**Usage:** `soroban keys address [OPTIONS] ` +**Usage:** `stellar keys address [OPTIONS] ` ###### **Arguments:** @@ -922,11 +920,11 @@ Given an identity return its address (public key) -## `soroban keys fund` +## `stellar keys fund` Fund an identity on a test network -**Usage:** `soroban keys fund [OPTIONS] ` +**Usage:** `stellar keys fund [OPTIONS] ` ###### **Arguments:** @@ -946,11 +944,11 @@ Fund an identity on a test network -## `soroban keys generate` +## `stellar keys generate` Generate a new identity with a seed phrase, currently 12 words -**Usage:** `soroban keys generate [OPTIONS] ` +**Usage:** `stellar keys generate [OPTIONS] ` ###### **Arguments:** @@ -983,11 +981,11 @@ Generate a new identity with a seed phrase, currently 12 words -## `soroban keys ls` +## `stellar keys ls` List identities -**Usage:** `soroban keys ls [OPTIONS]` +**Usage:** `stellar keys ls [OPTIONS]` ###### **Options:** @@ -1003,11 +1001,11 @@ List identities -## `soroban keys rm` +## `stellar keys rm` Remove an identity -**Usage:** `soroban keys rm [OPTIONS] ` +**Usage:** `stellar keys rm [OPTIONS] ` ###### **Arguments:** @@ -1023,11 +1021,11 @@ Remove an identity -## `soroban keys show` +## `stellar keys show` Given an identity return its private key -**Usage:** `soroban keys show [OPTIONS] ` +**Usage:** `stellar keys show [OPTIONS] ` ###### **Arguments:** @@ -1044,11 +1042,11 @@ Given an identity return its private key -## `soroban xdr` +## `stellar xdr` Decode and encode XDR -**Usage:** `soroban xdr [CHANNEL] ` +**Usage:** `stellar xdr [CHANNEL] ` ###### **Subcommands:** @@ -1069,11 +1067,11 @@ Decode and encode XDR -## `soroban xdr types` +## `stellar xdr types` View information about types -**Usage:** `soroban xdr types ` +**Usage:** `stellar xdr types ` ###### **Subcommands:** @@ -1081,9 +1079,9 @@ View information about types -## `soroban xdr types list` +## `stellar xdr types list` -**Usage:** `soroban xdr types list [OPTIONS]` +**Usage:** `stellar xdr types list [OPTIONS]` ###### **Options:** @@ -1096,11 +1094,11 @@ View information about types -## `soroban xdr guess` +## `stellar xdr guess` Guess the XDR type -**Usage:** `soroban xdr guess [OPTIONS] [FILE]` +**Usage:** `stellar xdr guess [OPTIONS] [FILE]` ###### **Arguments:** @@ -1126,11 +1124,11 @@ Guess the XDR type -## `soroban xdr decode` +## `stellar xdr decode` Decode XDR -**Usage:** `soroban xdr decode [OPTIONS] --type [FILES]...` +**Usage:** `stellar xdr decode [OPTIONS] --type [FILES]...` ###### **Arguments:** @@ -1154,11 +1152,11 @@ Decode XDR -## `soroban xdr encode` +## `stellar xdr encode` Encode XDR -**Usage:** `soroban xdr encode [OPTIONS] --type [FILES]...` +**Usage:** `stellar xdr encode [OPTIONS] --type [FILES]...` ###### **Arguments:** @@ -1182,19 +1180,19 @@ Encode XDR -## `soroban xdr version` +## `stellar xdr version` Print version information -**Usage:** `soroban xdr version` +**Usage:** `stellar xdr version` -## `soroban network` +## `stellar network` Start and configure networks -**Usage:** `soroban network ` +**Usage:** `stellar network ` ###### **Subcommands:** @@ -1206,11 +1204,11 @@ Start and configure networks -## `soroban network add` +## `stellar network add` Add a new network -**Usage:** `soroban network add [OPTIONS] --rpc-url --network-passphrase ` +**Usage:** `stellar network add [OPTIONS] --rpc-url --network-passphrase ` ###### **Arguments:** @@ -1228,11 +1226,11 @@ Add a new network -## `soroban network rm` +## `stellar network rm` Remove a network -**Usage:** `soroban network rm [OPTIONS] ` +**Usage:** `stellar network rm [OPTIONS] ` ###### **Arguments:** @@ -1248,11 +1246,11 @@ Remove a network -## `soroban network ls` +## `stellar network ls` List networks -**Usage:** `soroban network ls [OPTIONS]` +**Usage:** `stellar network ls [OPTIONS]` ###### **Options:** @@ -1268,7 +1266,7 @@ List networks -## `soroban network start` +## `stellar network start` Start network @@ -1278,7 +1276,7 @@ soroban network start [OPTIONS] By default, when starting a testnet container, without any optional arguments, it will run the equivalent of the following docker command: docker run --rm -p 8000:8000 --name stellar stellar/quickstart:testing --testnet --enable-soroban-rpc -**Usage:** `soroban network start [OPTIONS] ` +**Usage:** `stellar network start [OPTIONS] ` ###### **Arguments:** @@ -1299,11 +1297,11 @@ By default, when starting a testnet container, without any optional arguments, i -## `soroban network stop` +## `stellar network stop` Stop a network started with `network start`. For example, if you ran `soroban network start local`, you can use `soroban network stop local` to stop it -**Usage:** `soroban network stop [OPTIONS] ` +**Usage:** `stellar network stop [OPTIONS] ` ###### **Arguments:** @@ -1318,34 +1316,33 @@ Stop a network started with `network start`. For example, if you ran `soroban ne -## `soroban version` +## `stellar version` Print version information -**Usage:** `soroban version` +**Usage:** `stellar version` -## `soroban txn` +## `stellar tx` Sign, Simulate, and Send transactions -**Usage:** `soroban txn ` +**Usage:** `stellar tx ` ###### **Subcommands:** * `inspect` — Add a new identity (keypair, ledger, macOS keychain) -* `sign` — Given an identity return its address (public key) * `send` — Submit a transaction to the network * `simulate` — Simulate a transaction -## `soroban txn inspect` +## `stellar tx inspect` Add a new identity (keypair, ledger, macOS keychain) -**Usage:** `soroban txn inspect [CHANNEL] ` +**Usage:** `stellar tx inspect [CHANNEL] ` ###### **Subcommands:** @@ -1366,11 +1363,11 @@ Add a new identity (keypair, ledger, macOS keychain) -## `soroban txn inspect types` +## `stellar tx inspect types` View information about types -**Usage:** `soroban txn inspect types ` +**Usage:** `stellar tx inspect types ` ###### **Subcommands:** @@ -1378,9 +1375,9 @@ View information about types -## `soroban txn inspect types list` +## `stellar tx inspect types list` -**Usage:** `soroban txn inspect types list [OPTIONS]` +**Usage:** `stellar tx inspect types list [OPTIONS]` ###### **Options:** @@ -1393,11 +1390,11 @@ View information about types -## `soroban txn inspect guess` +## `stellar tx inspect guess` Guess the XDR type -**Usage:** `soroban txn inspect guess [OPTIONS] [FILE]` +**Usage:** `stellar tx inspect guess [OPTIONS] [FILE]` ###### **Arguments:** @@ -1423,11 +1420,11 @@ Guess the XDR type -## `soroban txn inspect decode` +## `stellar tx inspect decode` Decode XDR -**Usage:** `soroban txn inspect decode [OPTIONS] --type [FILES]...` +**Usage:** `stellar tx inspect decode [OPTIONS] --type [FILES]...` ###### **Arguments:** @@ -1451,11 +1448,11 @@ Decode XDR -## `soroban txn inspect encode` +## `stellar tx inspect encode` Encode XDR -**Usage:** `soroban txn inspect encode [OPTIONS] --type [FILES]...` +**Usage:** `stellar tx inspect encode [OPTIONS] --type [FILES]...` ###### **Arguments:** @@ -1479,46 +1476,19 @@ Encode XDR -## `soroban txn inspect version` +## `stellar tx inspect version` Print version information -**Usage:** `soroban txn inspect version` +**Usage:** `stellar tx inspect version` -## `soroban txn sign` - -Given an identity return its address (public key) - -**Usage:** `soroban txn sign [OPTIONS] --source-account ` - -###### **Options:** - -* `-y`, `--yes` — Confirm that a signature can be signed by the given keypair automatically - - Possible values: `true`, `false` - -* `--xdr-base64 ` — Base64 encoded XDR transaction -* `--xdr-file ` -* `--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 -* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--global` — Use global config - - Possible values: `true`, `false` - -* `--config-dir ` — Location of config directory, default is "." - - - -## `soroban txn send` +## `stellar tx send` Submit a transaction to the network -**Usage:** `soroban txn send [OPTIONS] --source-account ` +**Usage:** `stellar tx send [OPTIONS] --source-account ` ###### **Options:** @@ -1537,11 +1507,11 @@ Submit a transaction to the network -## `soroban txn simulate` +## `stellar tx simulate` Simulate a transaction -**Usage:** `soroban txn simulate [OPTIONS] --source-account ` +**Usage:** `stellar tx simulate [OPTIONS] --source-account ` ###### **Options:** @@ -1560,11 +1530,11 @@ Simulate a transaction -## `soroban cache` +## `stellar cache` Cache for tranasctions and contract specs -**Usage:** `soroban cache ` +**Usage:** `stellar cache ` ###### **Subcommands:** @@ -1574,27 +1544,27 @@ Cache for tranasctions and contract specs -## `soroban cache clean` +## `stellar cache clean` Delete the cache -**Usage:** `soroban cache clean` +**Usage:** `stellar cache clean` -## `soroban cache path` +## `stellar cache path` Show the location of the cache -**Usage:** `soroban cache path` +**Usage:** `stellar cache path` -## `soroban cache actionlog` +## `stellar cache actionlog` Access details about cached actions like transactions, and simulations. (Experimental. May see breaking changes at any time.) -**Usage:** `soroban cache actionlog ` +**Usage:** `stellar cache actionlog ` ###### **Subcommands:** @@ -1603,11 +1573,11 @@ Access details about cached actions like transactions, and simulations. (Experim -## `soroban cache actionlog ls` +## `stellar cache actionlog ls` List cached actions (transactions, simulations) -**Usage:** `soroban cache actionlog ls [OPTIONS]` +**Usage:** `stellar cache actionlog ls [OPTIONS]` ###### **Options:** @@ -1623,11 +1593,11 @@ List cached actions (transactions, simulations) -## `soroban cache actionlog read` +## `stellar cache actionlog read` Read cached action -**Usage:** `soroban cache actionlog read --id ` +**Usage:** `stellar cache actionlog read --id ` ###### **Options:** diff --git a/Makefile b/Makefile index d2faac330..7c2f8a78c 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,7 @@ install_rust: install install: cargo install --locked --path ./cmd/soroban-cli --debug + cargo install --locked --path ./cmd/stellar-cli --debug cargo install --locked --path ./cmd/crates/soroban-test/tests/fixtures/hello --root ./target --debug --quiet # regenerate the example lib in `cmd/crates/soroban-spec-typsecript/fixtures/ts` @@ -41,6 +42,9 @@ build-test-wasms: build-test: build-test-wasms install +generate-full-help-doc: + cargo run --bin doc-gen --features clap-markdown + generate-examples-list: curl -sSL https://api.github.com/repos/stellar/soroban-examples/git/trees/main \ | jq -r '.tree[] | select(.type != "blob" and .path != "hello_world" and (.path | startswith(".") | not)) | .path' \ diff --git a/README.md b/README.md index 1e40b861d..d53e90542 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This repo is home to the Soroban CLI, the command-line multi-tool for running an ## Documentation -For installation options see below, for usage instructions [see the manual](/docs/soroban-cli-full-docs.md). +For installation options see below, for usage instructions [see the full help docs](FULL_HELP_DOCS.md). ## Install Install the latest version from source: diff --git a/cmd/soroban-cli/README.md b/cmd/soroban-cli/README.md index d17261b18..c68ffd346 100644 --- a/cmd/soroban-cli/README.md +++ b/cmd/soroban-cli/README.md @@ -1,8 +1,8 @@ # soroban-cli -CLI for running Soroban contracts locally in a test VM. Executes WASM files built using the [rs-soroban-sdk](https://github.com/stellar/rs-soroban-sdk). +CLI for interacting with the Stellar network and Soroban contracts locally in a test VM. Executes WASM files built using the [rs-soroban-sdk](https://github.com/stellar/rs-soroban-sdk). -Soroban: https://soroban.stellar.org +Docs: https://developer.stellar.org ## Install diff --git a/cmd/soroban-cli/src/bin/doc-gen.rs b/cmd/soroban-cli/src/bin/doc-gen.rs index 096f9681c..96c6b09fd 100644 --- a/cmd/soroban-cli/src/bin/doc-gen.rs +++ b/cmd/soroban-cli/src/bin/doc-gen.rs @@ -1,5 +1,5 @@ use std::{ - env, fs, + env, path::{Path, PathBuf}, }; @@ -11,12 +11,10 @@ fn main() -> Result<(), DynError> { } fn doc_gen() -> std::io::Result<()> { - let out_dir = docs_dir(); - - fs::create_dir_all(out_dir.clone())?; + let out_dir = project_root(); std::fs::write( - out_dir.join("soroban-cli-full-docs.md"), + out_dir.join("FULL_HELP_DOCS.md"), clap_markdown::help_markdown::(), )?; @@ -30,7 +28,3 @@ fn project_root() -> PathBuf { .unwrap() .to_path_buf() } - -fn docs_dir() -> PathBuf { - project_root().join("docs") -} diff --git a/cmd/soroban-cli/src/bin/main.rs b/cmd/soroban-cli/src/bin/main.rs index 70eeb6110..27a46b76a 100644 --- a/cmd/soroban-cli/src/bin/main.rs +++ b/cmd/soroban-cli/src/bin/main.rs @@ -1,52 +1,3 @@ -use clap::CommandFactory; -use dotenvy::dotenv; -use tracing_subscriber::{fmt, EnvFilter}; - -use soroban_cli::{commands, Root}; - -#[tokio::main] -async fn main() { - let _ = dotenv().unwrap_or_default(); - let mut root = Root::new().unwrap_or_else(|e| match e { - commands::Error::Clap(e) => { - let mut cmd = Root::command(); - e.format(&mut cmd).exit(); - } - e => { - eprintln!("{e}"); - std::process::exit(1); - } - }); - // Now use root to setup the logger - if let Some(level) = root.global_args.log_level() { - let mut e_filter = EnvFilter::from_default_env() - .add_directive("hyper=off".parse().unwrap()) - .add_directive(format!("soroban_cli={level}").parse().unwrap()); - - for filter in &root.global_args.filter_logs { - e_filter = e_filter.add_directive( - filter - .parse() - .map_err(|e| { - eprintln!("{e}: {filter}"); - std::process::exit(1); - }) - .unwrap(), - ); - } - - let builder = fmt::Subscriber::builder() - .with_env_filter(e_filter) - .with_ansi(false) - .with_writer(std::io::stderr); - - let subscriber = builder.finish(); - tracing::subscriber::set_global_default(subscriber) - .expect("Failed to set the global tracing subscriber"); - } - - if let Err(e) = root.run().await { - eprintln!("error: {e}"); - std::process::exit(1); - } +fn main() { + soroban_cli::main(); } diff --git a/cmd/soroban-cli/src/cli.rs b/cmd/soroban-cli/src/cli.rs new file mode 100644 index 000000000..a6be9572d --- /dev/null +++ b/cmd/soroban-cli/src/cli.rs @@ -0,0 +1,76 @@ +use clap::CommandFactory; +use dotenvy::dotenv; +use tracing_subscriber::{fmt, EnvFilter}; + +use crate::{commands, Root}; + +#[tokio::main] +pub async fn main() { + let _ = dotenv().unwrap_or_default(); + + // Map SOROBAN_ env vars to STELLAR_ env vars for backwards compatibility + // with the soroban-cli prior to when the stellar-cli was released. + let vars = &[ + "FEE", + "NO_CACHE", + "ACCOUNT", + "CONTRACT_ID", + "INVOKE_VIEW", + "RPC_URL", + "NETWORK_PASSPHRASE", + "NETWORK", + "PORT", + "SECRET_KEY", + "CONFIG_HOME", + ]; + for var in vars { + let soroban_key = format!("SOROBAN_{var}"); + let stellar_key = format!("STELLAR_{var}"); + if let Ok(val) = std::env::var(soroban_key) { + std::env::set_var(stellar_key, val); + } + } + + let mut root = Root::new().unwrap_or_else(|e| match e { + commands::Error::Clap(e) => { + let mut cmd = Root::command(); + e.format(&mut cmd).exit(); + } + e => { + eprintln!("{e}"); + std::process::exit(1); + } + }); + // Now use root to setup the logger + if let Some(level) = root.global_args.log_level() { + let mut e_filter = EnvFilter::from_default_env() + .add_directive("hyper=off".parse().unwrap()) + .add_directive(format!("stellar_cli={level}").parse().unwrap()); + + for filter in &root.global_args.filter_logs { + e_filter = e_filter.add_directive( + filter + .parse() + .map_err(|e| { + eprintln!("{e}: {filter}"); + std::process::exit(1); + }) + .unwrap(), + ); + } + + let builder = fmt::Subscriber::builder() + .with_env_filter(e_filter) + .with_ansi(false) + .with_writer(std::io::stderr); + + let subscriber = builder.finish(); + tracing::subscriber::set_global_default(subscriber) + .expect("Failed to set the global tracing subscriber"); + } + + if let Err(e) = root.run().await { + eprintln!("error: {e}"); + std::process::exit(1); + } +} diff --git a/cmd/soroban-cli/src/commands/config/mod.rs b/cmd/soroban-cli/src/commands/config/mod.rs index 4d835685b..e80aea73c 100644 --- a/cmd/soroban-cli/src/commands/config/mod.rs +++ b/cmd/soroban-cli/src/commands/config/mod.rs @@ -31,7 +31,7 @@ pub struct Args { #[command(flatten)] pub network: network::Args, - #[arg(long, visible_alias = "source", env = "SOROBAN_ACCOUNT")] + #[arg(long, visible_alias = "source", env = "STELLAR_ACCOUNT")] /// Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` pub source_account: String, diff --git a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs index 1f8d4544f..2f78a0dd6 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs @@ -3,8 +3,8 @@ use soroban_env_host::{ xdr::{ Asset, ContractDataDurability, ContractExecutable, ContractIdPreimage, CreateContractArgs, Error as XdrError, Hash, HostFunction, InvokeHostFunctionOp, LedgerKey::ContractData, - LedgerKeyContractData, Memo, MuxedAccount, Operation, OperationBody, Preconditions, - ScAddress, ScVal, SequenceNumber, Transaction, TransactionExt, Uint256, VecM, + LedgerKeyContractData, Limits, Memo, MuxedAccount, Operation, OperationBody, Preconditions, + ScAddress, ScVal, SequenceNumber, Transaction, TransactionExt, Uint256, VecM, WriteXdr, }, HostError, }; @@ -15,7 +15,7 @@ use crate::{ commands::{ config::{self, data}, global, network, - txn_result::TxnResult, + txn_result::{TxnEnvelopeResult, TxnResult}, NetworkRunnable, }, rpc::{Client, Error as SorobanRpcError}, @@ -67,8 +67,13 @@ pub struct Cmd { impl Cmd { pub async fn run(&self) -> Result<(), Error> { - let res_str = self.run_against_rpc_server(None, None).await?; - println!("{res_str}"); + let res = self.run_against_rpc_server(None, None).await?.to_envelope(); + match res { + TxnEnvelopeResult::TxnEnvelope(tx) => println!("{}", tx.to_xdr_base64(Limits::none())?), + TxnEnvelopeResult::Res(contract) => { + println!("{contract}"); + } + } Ok(()) } } diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index dc4632043..0b944c78a 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -7,9 +7,9 @@ use rand::Rng; use soroban_env_host::{ xdr::{ AccountId, ContractExecutable, ContractIdPreimage, ContractIdPreimageFromAddress, - CreateContractArgs, Error as XdrError, Hash, HostFunction, InvokeHostFunctionOp, Memo, - MuxedAccount, Operation, OperationBody, Preconditions, PublicKey, ScAddress, - SequenceNumber, Transaction, TransactionExt, Uint256, VecM, + CreateContractArgs, Error as XdrError, Hash, HostFunction, InvokeHostFunctionOp, Limits, + Memo, MuxedAccount, Operation, OperationBody, Preconditions, PublicKey, ScAddress, + SequenceNumber, Transaction, TransactionExt, Uint256, VecM, WriteXdr, }, HostError, }; @@ -18,7 +18,7 @@ use crate::commands::{ config::data, contract::{self, id::wasm::get_contract_id}, global, network, - txn_result::TxnResult, + txn_result::{TxnEnvelopeResult, TxnResult}, NetworkRunnable, }; use crate::{ @@ -104,8 +104,13 @@ pub enum Error { impl Cmd { pub async fn run(&self) -> Result<(), Error> { - let res_str = self.run_against_rpc_server(None, None).await?; - println!("{res_str}"); + let res = self.run_against_rpc_server(None, None).await?.to_envelope(); + match res { + TxnEnvelopeResult::TxnEnvelope(tx) => println!("{}", tx.to_xdr_base64(Limits::none())?), + TxnEnvelopeResult::Res(contract) => { + println!("{contract}"); + } + } Ok(()) } } diff --git a/cmd/soroban-cli/src/commands/contract/extend.rs b/cmd/soroban-cli/src/commands/contract/extend.rs index 117568c20..544fdde3d 100644 --- a/cmd/soroban-cli/src/commands/contract/extend.rs +++ b/cmd/soroban-cli/src/commands/contract/extend.rs @@ -12,7 +12,7 @@ use crate::{ commands::{ config::{self, data}, global, network, - txn_result::TxnResult, + txn_result::{TxnEnvelopeResult, TxnResult}, NetworkRunnable, }, key, @@ -89,10 +89,10 @@ pub enum Error { impl Cmd { #[allow(clippy::too_many_lines)] pub async fn run(&self) -> Result<(), Error> { - let res = self.run_against_rpc_server(None, None).await?; + let res = self.run_against_rpc_server(None, None).await?.to_envelope(); match res { - TxnResult::Txn(tx) => println!("{}", tx.to_xdr_base64(Limits::none())?), - TxnResult::Res(ttl_ledger) => { + TxnEnvelopeResult::TxnEnvelope(tx) => println!("{}", tx.to_xdr_base64(Limits::none())?), + TxnEnvelopeResult::Res(ttl_ledger) => { if self.ttl_ledger_only { println!("{ttl_ledger}"); } else { diff --git a/cmd/soroban-cli/src/commands/contract/fetch.rs b/cmd/soroban-cli/src/commands/contract/fetch.rs index 73ba12c2d..e9d73db98 100644 --- a/cmd/soroban-cli/src/commands/contract/fetch.rs +++ b/cmd/soroban-cli/src/commands/contract/fetch.rs @@ -32,7 +32,7 @@ use crate::{ #[group(skip)] pub struct Cmd { /// Contract ID to fetch - #[arg(long = "id", env = "SOROBAN_CONTRACT_ID")] + #[arg(long = "id", env = "STELLAR_CONTRACT_ID")] pub contract_id: String, /// Where to write output otherwise stdout is used #[arg(long, short = 'o')] diff --git a/cmd/soroban-cli/src/commands/contract/install.rs b/cmd/soroban-cli/src/commands/contract/install.rs index 4e1b83d9a..39b039792 100644 --- a/cmd/soroban-cli/src/commands/contract/install.rs +++ b/cmd/soroban-cli/src/commands/contract/install.rs @@ -12,7 +12,7 @@ use soroban_env_host::xdr::{ use super::restore; use crate::commands::network; -use crate::commands::txn_result::TxnResult; +use crate::commands::txn_result::{TxnEnvelopeResult, TxnResult}; use crate::commands::{config::data, global, NetworkRunnable}; use crate::key; use crate::rpc::{self, Client}; @@ -73,11 +73,11 @@ pub enum Error { impl Cmd { pub async fn run(&self) -> Result<(), Error> { - let res_str = match self.run_against_rpc_server(None, None).await? { - TxnResult::Txn(tx) => tx.to_xdr_base64(Limits::none())?, - TxnResult::Res(hash) => hex::encode(hash), + let res = self.run_against_rpc_server(None, None).await?.to_envelope(); + match res { + TxnEnvelopeResult::TxnEnvelope(tx) => println!("{}", tx.to_xdr_base64(Limits::none())?), + TxnEnvelopeResult::Res(hash) => println!("{}", hex::encode(hash)), }; - println!("{res_str}"); Ok(()) } } diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index ef1fd7db1..d44a75172 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -13,10 +13,10 @@ use heck::ToKebabCase; use soroban_env_host::{ xdr::{ self, Hash, HostFunction, InvokeContractArgs, InvokeHostFunctionOp, LedgerEntryData, - LedgerFootprint, Memo, MuxedAccount, Operation, OperationBody, Preconditions, PublicKey, - ScAddress, ScSpecEntry, ScSpecFunctionV0, ScSpecTypeDef, ScVal, ScVec, SequenceNumber, - SorobanAuthorizationEntry, SorobanResources, String32, StringM, Transaction, - TransactionExt, Uint256, VecM, + LedgerFootprint, Limits, Memo, MuxedAccount, Operation, OperationBody, Preconditions, + PublicKey, ScAddress, ScSpecEntry, ScSpecFunctionV0, ScSpecTypeDef, ScVal, ScVec, + SequenceNumber, SorobanAuthorizationEntry, SorobanResources, String32, StringM, + Transaction, TransactionExt, Uint256, VecM, WriteXdr, }, HostError, }; @@ -31,7 +31,7 @@ use super::super::{ config::{self, locator}, events, }; -use crate::commands::txn_result::TxnResult; +use crate::commands::txn_result::{TxnEnvelopeResult, TxnResult}; use crate::commands::NetworkRunnable; use crate::{ commands::{config::data, global, network}, @@ -44,13 +44,13 @@ use soroban_spec_tools::{contract, Spec}; #[group(skip)] pub struct Cmd { /// Contract ID to invoke - #[arg(long = "id", env = "SOROBAN_CONTRACT_ID")] + #[arg(long = "id", env = "STELLAR_CONTRACT_ID")] pub contract_id: String, // For testing only #[arg(skip)] pub wasm: Option, /// View the result simulating and do not sign and submit transaction - #[arg(long, env = "SOROBAN_INVOKE_VIEW")] + #[arg(long, env = "STELLAR_INVOKE_VIEW")] pub is_view: bool, /// Function name as subcommand, then arguments for that function as `--arg-name value` #[arg(last = true, id = "CONTRACT_FN_AND_ARGS")] @@ -272,8 +272,13 @@ impl Cmd { } pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { - let res = self.invoke(global_args).await?; - println!("{res}"); + let res = self.invoke(global_args).await?.to_envelope(); + match res { + TxnEnvelopeResult::TxnEnvelope(tx) => println!("{}", tx.to_xdr_base64(Limits::none())?), + TxnEnvelopeResult::Res(output) => { + println!("{output}"); + } + } Ok(()) } diff --git a/cmd/soroban-cli/src/commands/contract/restore.rs b/cmd/soroban-cli/src/commands/contract/restore.rs index f78316886..ad824d34e 100644 --- a/cmd/soroban-cli/src/commands/contract/restore.rs +++ b/cmd/soroban-cli/src/commands/contract/restore.rs @@ -14,7 +14,7 @@ use crate::{ config::{self, data, locator}, contract::extend, global, network, - txn_result::TxnResult, + txn_result::{TxnEnvelopeResult, TxnResult}, NetworkRunnable, }, key, @@ -94,12 +94,13 @@ pub enum Error { impl Cmd { #[allow(clippy::too_many_lines)] pub async fn run(&self) -> Result<(), Error> { - let expiration_ledger_seq = match self.run_against_rpc_server(None, None).await? { - TxnResult::Res(res) => res, - TxnResult::Txn(xdr) => { - println!("{}", xdr.to_xdr_base64(Limits::none())?); + let res = self.run_against_rpc_server(None, None).await?.to_envelope(); + let expiration_ledger_seq = match res { + TxnEnvelopeResult::TxnEnvelope(tx) => { + println!("{}", tx.to_xdr_base64(Limits::none())?); return Ok(()); } + TxnEnvelopeResult::Res(res) => res, }; if let Some(ledgers_to_extend) = self.ledgers_to_extend { extend::Cmd { diff --git a/cmd/soroban-cli/src/commands/global.rs b/cmd/soroban-cli/src/commands/global.rs index bb530f043..6ee659fae 100644 --- a/cmd/soroban-cli/src/commands/global.rs +++ b/cmd/soroban-cli/src/commands/global.rs @@ -10,7 +10,7 @@ pub struct Args { #[clap(flatten)] pub locator: config::locator::Args, - /// Filter logs output. To turn on "soroban_cli::log::footprint=debug" or off "=off". Can also use env var `RUST_LOG`. + /// Filter logs output. To turn on "stellar_cli::log::footprint=debug" or off "=off". Can also use env var `RUST_LOG`. #[arg(long, short = 'f')] pub filter_logs: Vec, @@ -26,12 +26,12 @@ pub struct Args { #[arg(long, visible_alias = "vv")] pub very_verbose: bool, - /// List installed plugins. E.g. `soroban-hello` + /// List installed plugins. E.g. `stellar-hello` #[arg(long)] pub list: bool, /// Do not cache your simulations and transactions - #[arg(long, env = "SOROBAN_NO_CACHE")] + #[arg(long, env = "STELLAR_NO_CACHE")] pub no_cache: bool, } diff --git a/cmd/soroban-cli/src/commands/keys/ls.rs b/cmd/soroban-cli/src/commands/keys/ls.rs index bc46ffcd8..89749d245 100644 --- a/cmd/soroban-cli/src/commands/keys/ls.rs +++ b/cmd/soroban-cli/src/commands/keys/ls.rs @@ -26,11 +26,7 @@ impl Cmd { } pub fn ls(&self) -> Result, Error> { - let mut list = self.config_locator.list_identities()?; - let test = "test".to_string(); - if !list.contains(&test) { - list.push(test); - } + let list = self.config_locator.list_identities()?; Ok(list) } diff --git a/cmd/soroban-cli/src/commands/mod.rs b/cmd/soroban-cli/src/commands/mod.rs index 5ae074fc6..1536114db 100644 --- a/cmd/soroban-cli/src/commands/mod.rs +++ b/cmd/soroban-cli/src/commands/mod.rs @@ -20,37 +20,36 @@ pub mod txn_result; pub const HEADING_RPC: &str = "Options (RPC)"; const ABOUT: &str = "Build, deploy, & interact with contracts; set identities to sign with; configure networks; generate keys; and more. -Intro: https://soroban.stellar.org/docs -CLI Reference: https://github.com/stellar/soroban-cli/tree/main/docs/soroban-cli-full-docs.md"; +Stellar Docs: https://developers.stellar.org +CLI Full Hep Docs: https://github.com/stellar/soroban-cli/tree/main/FULL_HELP_DOCS.md"; // long_about is shown when someone uses `--help`; short help when using `-h` const LONG_ABOUT: &str = " The easiest way to get started is to generate a new identity: - soroban config identity generate alice + stellar config identity generate alice You can use identities with the `--source` flag in other commands later. Commands that relate to smart contract interactions are organized under the `contract` subcommand. List them: - soroban contract --help + stellar contract --help -A Soroban contract has its interface schema types embedded in the binary that gets deployed on-chain, making it possible to dynamically generate a custom CLI for each. `soroban contract invoke` makes use of this: +A Soroban contract has its interface schema types embedded in the binary that gets deployed on-chain, making it possible to dynamically generate a custom CLI for each. The invoke subcommand makes use of this: - soroban contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- \ + stellar contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- \ --help Anything after the `--` double dash (the \"slop\") is parsed as arguments to the contract-specific CLI, generated on-the-fly from the embedded schema. For the hello world example, with a function called `hello` that takes one string argument `to`, here's how you invoke it: - soroban contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- \ + stellar contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- \ hello --to world - -Full CLI reference: https://github.com/stellar/soroban-tools/tree/main/docs/soroban-cli-full-docs.md"; +"; #[derive(Parser, Debug)] #[command( - name = "soroban", + name = "stellar", about = ABOUT, version = version::long(), long_about = ABOUT.to_string() + LONG_ABOUT, diff --git a/cmd/soroban-cli/src/commands/network/mod.rs b/cmd/soroban-cli/src/commands/network/mod.rs index e56d99068..9df58cdda 100644 --- a/cmd/soroban-cli/src/commands/network/mod.rs +++ b/cmd/soroban-cli/src/commands/network/mod.rs @@ -101,7 +101,7 @@ pub struct Args { long = "rpc-url", requires = "network_passphrase", required_unless_present = "network", - env = "SOROBAN_RPC_URL", + env = "STELLAR_RPC_URL", help_heading = HEADING_RPC, )] pub rpc_url: Option, @@ -110,7 +110,7 @@ pub struct Args { long = "network-passphrase", requires = "rpc_url", required_unless_present = "network", - env = "SOROBAN_NETWORK_PASSPHRASE", + env = "STELLAR_NETWORK_PASSPHRASE", help_heading = HEADING_RPC, )] pub network_passphrase: Option, @@ -118,7 +118,7 @@ pub struct Args { #[arg( long, required_unless_present = "rpc_url", - env = "SOROBAN_NETWORK", + env = "STELLAR_NETWORK", help_heading = HEADING_RPC, )] pub network: Option, @@ -150,14 +150,14 @@ pub struct Network { /// RPC server endpoint #[arg( long = "rpc-url", - env = "SOROBAN_RPC_URL", + env = "STELLAR_RPC_URL", help_heading = HEADING_RPC, )] pub rpc_url: String, /// Network passphrase to sign the transaction sent to the rpc server #[arg( long, - env = "SOROBAN_NETWORK_PASSPHRASE", + env = "STELLAR_NETWORK_PASSPHRASE", help_heading = HEADING_RPC, )] pub network_passphrase: String, diff --git a/cmd/soroban-cli/src/commands/txn_result.rs b/cmd/soroban-cli/src/commands/txn_result.rs index 6b189f25b..08482e94f 100644 --- a/cmd/soroban-cli/src/commands/txn_result.rs +++ b/cmd/soroban-cli/src/commands/txn_result.rs @@ -1,6 +1,4 @@ -use std::fmt::{Display, Formatter}; - -use soroban_env_host::xdr::{Limits, Transaction, WriteXdr}; +use soroban_env_host::xdr::{Transaction, TransactionEnvelope, TransactionV1Envelope, VecM}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum TxnResult { @@ -15,21 +13,22 @@ impl TxnResult { TxnResult::Txn(_) => None, } } -} -impl Display for TxnResult -where - V: Display, -{ - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + pub fn to_envelope(self) -> TxnEnvelopeResult { match self { - TxnResult::Txn(tx) => write!( - f, - "{}", - tx.to_xdr_base64(Limits::none()) - .map_err(|_| std::fmt::Error)? - ), - TxnResult::Res(value) => write!(f, "{value}"), + TxnResult::Txn(tx) => { + TxnEnvelopeResult::TxnEnvelope(TransactionEnvelope::Tx(TransactionV1Envelope { + tx, + signatures: VecM::default(), + })) + } + TxnResult::Res(res) => TxnEnvelopeResult::Res(res), } } } + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum TxnEnvelopeResult { + TxnEnvelope(TransactionEnvelope), + Res(R), +} diff --git a/cmd/soroban-cli/src/commands/version.rs b/cmd/soroban-cli/src/commands/version.rs index d9fc091b2..95aae8c60 100644 --- a/cmd/soroban-cli/src/commands/version.rs +++ b/cmd/soroban-cli/src/commands/version.rs @@ -11,7 +11,7 @@ pub struct Cmd; impl Cmd { #[allow(clippy::unused_self)] pub fn run(&self) { - println!("soroban {}", long()); + println!("stellar {}", long()); } } diff --git a/cmd/soroban-cli/src/fee.rs b/cmd/soroban-cli/src/fee.rs index b7b7e0441..ab057a587 100644 --- a/cmd/soroban-cli/src/fee.rs +++ b/cmd/soroban-cli/src/fee.rs @@ -9,7 +9,7 @@ use crate::commands::HEADING_RPC; #[group(skip)] pub struct Args { /// fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm - #[arg(long, default_value = "100", env = "SOROBAN_FEE", help_heading = HEADING_RPC)] + #[arg(long, default_value = "100", env = "STELLAR_FEE", help_heading = HEADING_RPC)] pub fee: u32, /// Output the cost execution to stderr #[arg(long = "cost", help_heading = HEADING_RPC)] diff --git a/cmd/soroban-cli/src/lib.rs b/cmd/soroban-cli/src/lib.rs index 5f44d6b8b..9681a83f0 100644 --- a/cmd/soroban-cli/src/lib.rs +++ b/cmd/soroban-cli/src/lib.rs @@ -8,6 +8,9 @@ use std::path::Path; pub(crate) use soroban_env_host::xdr; pub(crate) use soroban_rpc as rpc; +mod cli; +pub use cli::main; + pub mod commands; pub mod fee; pub mod key; diff --git a/cmd/soroban-cli/src/utils.rs b/cmd/soroban-cli/src/utils.rs index 97fc9bfcc..e74222c2c 100644 --- a/cmd/soroban-cli/src/utils.rs +++ b/cmd/soroban-cli/src/utils.rs @@ -71,16 +71,32 @@ pub fn contract_id_from_str(contract_id: &str) -> Result<[u8; 32], stellar_strke /// # Errors /// May not find a config dir pub fn find_config_dir(mut pwd: std::path::PathBuf) -> std::io::Result { - let soroban_dir = |p: &std::path::Path| p.join(".soroban"); - while !soroban_dir(&pwd).exists() { + loop { + let stellar_dir = pwd.join(".stellar"); + let stellar_exists = stellar_dir.exists(); + + let soroban_dir = pwd.join(".soroban"); + let soroban_exists = soroban_dir.exists(); + + if stellar_exists && soroban_exists { + tracing::warn!("the .stellar and .soroban config directories exist at path {pwd:?}, using the .stellar"); + } + + if stellar_exists { + return Ok(stellar_dir); + } + + if soroban_exists { + return Ok(soroban_dir); + } if !pwd.pop() { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "soroban directory not found", - )); + break; } } - Ok(soroban_dir(&pwd)) + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "stellar directory not found", + )) } pub(crate) fn into_signing_key(key: &PrivateKey) -> ed25519_dalek::SigningKey { diff --git a/cmd/stellar-cli/Cargo.toml b/cmd/stellar-cli/Cargo.toml new file mode 100644 index 000000000..3b1aa53d3 --- /dev/null +++ b/cmd/stellar-cli/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "stellar-cli" +description = "Stellar CLI" +homepage = "https://github.com/stellar/soroban-cli" +repository = "https://github.com/stellar/soroban-cli" +authors = ["Stellar Development Foundation "] +license = "Apache-2.0" +readme = "README.md" +version = "21.0.0-preview.1" +edition = "2021" +rust-version.workspace = true +autobins = false +default-run = "stellar" + +[[bin]] +name = "stellar" +path = "src/bin/main.rs" + +[package.metadata.binstall] +pkg-url = "{ repo }/releases/download/v{ version }/{ name }-{ version }-{ target }{ archive-suffix }" +bin-dir = "{ bin }{ binary-ext }" + +[features] +default = [] +opt = ["soroban-cli/opt"] + +[dependencies] +soroban-cli = { workspace = true } diff --git a/cmd/stellar-cli/README.md b/cmd/stellar-cli/README.md new file mode 100644 index 000000000..e77fe623b --- /dev/null +++ b/cmd/stellar-cli/README.md @@ -0,0 +1,28 @@ +# stellar-cli + +CLI for interacting with the Stellar network and Soroban contracts locally in a test VM. Executes WASM files built using the [rs-soroban-sdk](https://github.com/stellar/rs-soroban-sdk). + +Docs: https://developer.stellar.org + +## Install + +``` +cargo install --locked stellar-cli +``` + +To install with the `opt` feature, which includes a WASM optimization feature and wasm-opt built in: + +``` +cargo install --locked stellar-cli --features opt +``` + +## Usage + +Can invoke a contract method as a subcommand with different arguments. Anything after the slop (`--`) is passed to the contract's CLI. You can use `--help` to learn about which methods are available and what their arguments are including an example of the type of the input. + +## Example + +``` +stellar invoke --id --wasm -- --help +stellar invoke --id --network futurenet -- --help +``` diff --git a/cmd/stellar-cli/src/bin/main.rs b/cmd/stellar-cli/src/bin/main.rs new file mode 100644 index 000000000..27a46b76a --- /dev/null +++ b/cmd/stellar-cli/src/bin/main.rs @@ -0,0 +1,3 @@ +fn main() { + soroban_cli::main(); +} diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index ce8ce2e15..000000000 --- a/docs/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Auto-generated docs - -The docs in this folder are auto-generated. Do not edit them manually or your changes will be lost! From a3ee1aa43205ecb3168f3be3335041294372774a Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Mon, 13 May 2024 13:00:01 -0400 Subject: [PATCH 10/30] fix: print json for `tx send` --- cmd/soroban-cli/src/commands/tx/send.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/soroban-cli/src/commands/tx/send.rs b/cmd/soroban-cli/src/commands/tx/send.rs index 1cf3f8478..ed53a7d6b 100644 --- a/cmd/soroban-cli/src/commands/tx/send.rs +++ b/cmd/soroban-cli/src/commands/tx/send.rs @@ -8,6 +8,8 @@ pub enum Error { Config(#[from] super::super::config::Error), #[error(transparent)] Rpc(#[from] crate::rpc::Error), + #[error(transparent)] + SerdeJson(#[from] serde_json::Error), } #[derive(Debug, clap::Parser, Clone)] @@ -22,7 +24,7 @@ pub struct Cmd { impl Cmd { pub async fn run(&self) -> Result<(), Error> { let response = self.send().await?; - println!("{response:#?}"); + println!("{}", serde_json::to_string_pretty(&response)?); Ok(()) } From 588afd82b48601209dede1ba5615d83c2563b887 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Mon, 13 May 2024 18:33:13 -0400 Subject: [PATCH 11/30] fix: only use stdin as input for txn simulate/send commands --- cmd/soroban-cli/src/commands/tx/mod.rs | 4 +- cmd/soroban-cli/src/commands/tx/send.rs | 5 +- cmd/soroban-cli/src/commands/tx/simulate.rs | 6 +- cmd/soroban-cli/src/commands/tx/xdr.rs | 65 ++++++--------------- 4 files changed, 26 insertions(+), 54 deletions(-) diff --git a/cmd/soroban-cli/src/commands/tx/mod.rs b/cmd/soroban-cli/src/commands/tx/mod.rs index 1d1c8c976..e2c533b03 100644 --- a/cmd/soroban-cli/src/commands/tx/mod.rs +++ b/cmd/soroban-cli/src/commands/tx/mod.rs @@ -11,9 +11,9 @@ use stellar_xdr::cli as xdr_cli; pub enum Cmd { /// Add a new identity (keypair, ledger, macOS keychain) Inspect(xdr_cli::Root), - /// Submit a transaction to the network + /// Submit a transaction envelope from stdin to the network Send(send::Cmd), - /// Simulate a transaction + /// Simulate a transaction envelope from stdin Simulate(simulate::Cmd), } diff --git a/cmd/soroban-cli/src/commands/tx/send.rs b/cmd/soroban-cli/src/commands/tx/send.rs index ed53a7d6b..c9496f08e 100644 --- a/cmd/soroban-cli/src/commands/tx/send.rs +++ b/cmd/soroban-cli/src/commands/tx/send.rs @@ -14,9 +14,8 @@ pub enum Error { #[derive(Debug, clap::Parser, Clone)] #[group(skip)] +/// Command to send a transaction envelope to the network pub struct Cmd { - #[clap(flatten)] - pub xdr_args: super::xdr::Args, #[clap(flatten)] pub config: super::super::config::Args, } @@ -29,7 +28,7 @@ impl Cmd { } pub async fn send(&self) -> Result { - let txn_env = self.xdr_args.txn_envelope()?; + let txn_env = super::xdr::txn_envelope_from_stdin()?; let network = self.config.get_network()?; let client = crate::rpc::Client::new(&network.rpc_url)?; Ok(client.send_transaction(&txn_env).await?) diff --git a/cmd/soroban-cli/src/commands/tx/simulate.rs b/cmd/soroban-cli/src/commands/tx/simulate.rs index 5fbae28c0..20abeaef3 100644 --- a/cmd/soroban-cli/src/commands/tx/simulate.rs +++ b/cmd/soroban-cli/src/commands/tx/simulate.rs @@ -13,11 +13,11 @@ pub enum Error { Xdr(#[from] xdr::Error), } +/// Command to simulate a transaction envelope via rpc +/// e.g. `cat file.txt | soroban tx simulate` #[derive(Debug, clap::Parser, Clone)] #[group(skip)] pub struct Cmd { - #[clap(flatten)] - pub xdr_args: super::xdr::Args, #[clap(flatten)] pub config: super::super::config::Args, } @@ -30,7 +30,7 @@ impl Cmd { } pub async fn simulate(&self) -> Result { - let tx = self.xdr_args.txn()?; + let tx = super::xdr::unwrap_envelope_v1()?; let network = self.config.get_network()?; let client = crate::rpc::Client::new(&network.rpc_url)?; Ok(client.create_assembled_transaction(&tx).await?) diff --git a/cmd/soroban-cli/src/commands/tx/xdr.rs b/cmd/soroban-cli/src/commands/tx/xdr.rs index 180c12e6b..4e740ca74 100644 --- a/cmd/soroban-cli/src/commands/tx/xdr.rs +++ b/cmd/soroban-cli/src/commands/tx/xdr.rs @@ -4,7 +4,8 @@ use std::{ }; use soroban_env_host::xdr::ReadXdr; -use soroban_sdk::xdr::{Limits, Transaction, TransactionEnvelope}; +use soroban_sdk::xdr::{Limits, TransactionEnvelope, Transaction, TransactionV1Envelope}; + #[derive(Debug, thiserror::Error)] pub enum Error { @@ -16,52 +17,24 @@ pub enum Error { StdinDecode, #[error(transparent)] Io(#[from] std::io::Error), + #[error("only transaction v1 is supported")] + OnlyTransactionV1Supported, } -/// XDR input, either base64 encoded or file path and stdin if neither is provided -#[derive(Debug, clap::Args, Clone)] -#[group(skip)] -pub struct Args { - /// Base64 encoded XDR transaction - #[arg( - long = "xdr-base64", - env = "STELLAR_TXN_XDR_BASE64", - conflicts_with = "xdr_file" - )] - pub xdr_base64: Option, - //// File containing Binary encoded data - #[arg( - long = "xdr-file", - env = "STELLAR_TXN_XDR_FILE", - conflicts_with = "xdr_base64" - )] - pub xdr_file: Option, +pub fn txn_envelope_from_stdin() -> Result { + from_stdin() } - -impl Args { - pub fn xdr(&self) -> Result { - match (self.xdr_base64.as_ref(), self.xdr_file.as_ref()) { - (Some(xdr_base64), None) => { - T::from_xdr_base64(xdr_base64, Limits::none()).map_err(|_| Error::Base64Decode) - } - (_, Some(xdr_file)) => T::from_xdr(std::fs::read(xdr_file)?, Limits::none()) - .map_err(|_| Error::FileDecode(xdr_file.clone())), - - _ => { - let mut buf = String::new(); - let _ = stdin() - .read_to_string(&mut buf) - .map_err(|_| Error::StdinDecode)?; - T::from_xdr_base64(buf.trim(), Limits::none()).map_err(|_| Error::StdinDecode) - } - } - } - - pub fn txn(&self) -> Result { - self.xdr::() - } - - pub fn txn_envelope(&self) -> Result { - self.xdr::() - } +pub fn from_stdin() -> Result { + let mut buf = String::new(); + let _ = stdin() + .read_to_string(&mut buf) + .map_err(|_| Error::StdinDecode)?; + T::from_xdr_base64(buf.trim(), Limits::none()).map_err(|_| Error::StdinDecode) } + +pub fn unwrap_envelope_v1() -> Result { + let TransactionEnvelope::Tx(TransactionV1Envelope { tx, .. }) = txn_envelope_from_stdin()? else { + return Err(Error::OnlyTransactionV1Supported); + }; + Ok(tx) +} \ No newline at end of file From 8e0db60ac5204003792b9e2b7b1c4e9ce900a316 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 14 May 2024 11:04:04 -0400 Subject: [PATCH 12/30] fix: remove `txn inspect` for now --- cmd/soroban-cli/src/commands/tx/mod.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/cmd/soroban-cli/src/commands/tx/mod.rs b/cmd/soroban-cli/src/commands/tx/mod.rs index e2c533b03..d06b5a544 100644 --- a/cmd/soroban-cli/src/commands/tx/mod.rs +++ b/cmd/soroban-cli/src/commands/tx/mod.rs @@ -1,16 +1,11 @@ use clap::Parser; pub mod send; - pub mod simulate; pub mod xdr; -use stellar_xdr::cli as xdr_cli; - #[derive(Debug, Parser)] pub enum Cmd { - /// Add a new identity (keypair, ledger, macOS keychain) - Inspect(xdr_cli::Root), /// Submit a transaction envelope from stdin to the network Send(send::Cmd), /// Simulate a transaction envelope from stdin @@ -22,9 +17,6 @@ pub enum Error { /// An error during the simulation #[error(transparent)] Simulate(#[from] simulate::Error), - /// An error during the inspect - #[error(transparent)] - Inspect(#[from] xdr_cli::Error), /// An error during the send #[error(transparent)] Send(#[from] send::Error), @@ -33,7 +25,6 @@ pub enum Error { impl Cmd { pub async fn run(&self) -> Result<(), Error> { match self { - Cmd::Inspect(cmd) => cmd.run()?, Cmd::Send(cmd) => cmd.run().await?, Cmd::Simulate(cmd) => cmd.run().await?, }; From b2320d3ec574567d67909170c238cabccaaa385b Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 14 May 2024 11:50:03 -0400 Subject: [PATCH 13/30] fix: add back cargo alias --- .cargo/config.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/.cargo/config.toml b/.cargo/config.toml index e8a8edce5..da0b5d5ff 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,6 +1,7 @@ # paths = ["/path/to/override"] # path dependency overrides [alias] # command aliases +md-gen = "run --bin doc-gen --features clap-markdown" f = "fmt" # b = "build" # c = "check" From c8247b7a6b642f84b313bc299d291331461035ee Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 14 May 2024 11:50:42 -0400 Subject: [PATCH 14/30] fix: docs --- FULL_HELP_DOCS.md | 166 ++-------------------------------------------- 1 file changed, 4 insertions(+), 162 deletions(-) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 57f27dbbe..ec33eefa3 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -52,13 +52,6 @@ This document contains the help content for the `stellar` command-line program. * [`stellar network stop`↴](#stellar-network-stop) * [`stellar version`↴](#stellar-version) * [`stellar tx`↴](#stellar-tx) -* [`stellar tx inspect`↴](#stellar-tx-inspect) -* [`stellar tx inspect types`↴](#stellar-tx-inspect-types) -* [`stellar tx inspect types list`↴](#stellar-tx-inspect-types-list) -* [`stellar tx inspect guess`↴](#stellar-tx-inspect-guess) -* [`stellar tx inspect decode`↴](#stellar-tx-inspect-decode) -* [`stellar tx inspect encode`↴](#stellar-tx-inspect-encode) -* [`stellar tx inspect version`↴](#stellar-tx-inspect-version) * [`stellar tx send`↴](#stellar-tx-send) * [`stellar tx simulate`↴](#stellar-tx-simulate) * [`stellar cache`↴](#stellar-cache) @@ -1332,168 +1325,19 @@ Sign, Simulate, and Send transactions ###### **Subcommands:** -* `inspect` — Add a new identity (keypair, ledger, macOS keychain) -* `send` — Submit a transaction to the network -* `simulate` — Simulate a transaction - - - -## `stellar tx inspect` - -Add a new identity (keypair, ledger, macOS keychain) - -**Usage:** `stellar tx inspect [CHANNEL] ` - -###### **Subcommands:** - -* `types` — View information about types -* `guess` — Guess the XDR type -* `decode` — Decode XDR -* `encode` — Encode XDR -* `version` — Print version information - -###### **Arguments:** - -* `` — Channel of XDR to operate on - - Default value: `+curr` - - Possible values: `+curr`, `+next` - - - - -## `stellar tx inspect types` - -View information about types - -**Usage:** `stellar tx inspect types ` - -###### **Subcommands:** - -* `list` — - - - -## `stellar tx inspect types list` - -**Usage:** `stellar tx inspect types list [OPTIONS]` - -###### **Options:** - -* `--output ` - - Default value: `plain` - - Possible values: `plain`, `json`, `json-formatted` - - - - -## `stellar tx inspect guess` - -Guess the XDR type - -**Usage:** `stellar tx inspect guess [OPTIONS] [FILE]` - -###### **Arguments:** - -* `` — File to decode, or stdin if omitted - -###### **Options:** - -* `--input ` - - Default value: `single-base64` - - Possible values: `single`, `single-base64`, `stream`, `stream-base64`, `stream-framed` - -* `--output ` - - Default value: `list` - - Possible values: `list` - -* `--certainty ` — Certainty as an arbitrary value - - Default value: `2` - - - -## `stellar tx inspect decode` - -Decode XDR - -**Usage:** `stellar tx inspect decode [OPTIONS] --type [FILES]...` - -###### **Arguments:** - -* `` — Files to decode, or stdin if omitted - -###### **Options:** - -* `--type ` — XDR type to decode -* `--input ` - - Default value: `stream-base64` - - Possible values: `single`, `single-base64`, `stream`, `stream-base64`, `stream-framed` - -* `--output ` - - Default value: `json` - - Possible values: `json`, `json-formatted`, `rust-debug`, `rust-debug-formatted` - - - - -## `stellar tx inspect encode` - -Encode XDR - -**Usage:** `stellar tx inspect encode [OPTIONS] --type [FILES]...` - -###### **Arguments:** - -* `` — Files to encode, or stdin if omitted - -###### **Options:** - -* `--type ` — XDR type to encode -* `--input ` - - Default value: `json` - - Possible values: `json` - -* `--output ` - - Default value: `single-base64` - - Possible values: `single`, `single-base64` - - - - -## `stellar tx inspect version` - -Print version information - -**Usage:** `stellar tx inspect version` +* `send` — Submit a transaction envelope from stdin to the network +* `simulate` — Simulate a transaction envelope from stdin ## `stellar tx send` -Submit a transaction to the network +Submit a transaction envelope from stdin to the network **Usage:** `stellar tx send [OPTIONS] --source-account ` ###### **Options:** -* `--xdr-base64 ` — Base64 encoded XDR transaction -* `--xdr-file ` * `--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 @@ -1509,14 +1353,12 @@ Submit a transaction to the network ## `stellar tx simulate` -Simulate a transaction +Simulate a transaction envelope from stdin **Usage:** `stellar tx simulate [OPTIONS] --source-account ` ###### **Options:** -* `--xdr-base64 ` — Base64 encoded XDR transaction -* `--xdr-file ` * `--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 From b24924294cd7d525764920bed3bca76d270f628e Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 14 May 2024 12:02:44 -0400 Subject: [PATCH 15/30] fix: fmt --- cmd/soroban-cli/src/commands/tx/xdr.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/soroban-cli/src/commands/tx/xdr.rs b/cmd/soroban-cli/src/commands/tx/xdr.rs index 4e740ca74..505ab8106 100644 --- a/cmd/soroban-cli/src/commands/tx/xdr.rs +++ b/cmd/soroban-cli/src/commands/tx/xdr.rs @@ -4,8 +4,7 @@ use std::{ }; use soroban_env_host::xdr::ReadXdr; -use soroban_sdk::xdr::{Limits, TransactionEnvelope, Transaction, TransactionV1Envelope}; - +use soroban_sdk::xdr::{Limits, Transaction, TransactionEnvelope, TransactionV1Envelope}; #[derive(Debug, thiserror::Error)] pub enum Error { @@ -33,8 +32,9 @@ pub fn from_stdin() -> Result { } pub fn unwrap_envelope_v1() -> Result { - let TransactionEnvelope::Tx(TransactionV1Envelope { tx, .. }) = txn_envelope_from_stdin()? else { + let TransactionEnvelope::Tx(TransactionV1Envelope { tx, .. }) = txn_envelope_from_stdin()? + else { return Err(Error::OnlyTransactionV1Supported); }; Ok(tx) -} \ No newline at end of file +} From 60e3a9d91dd6372fb331bf8939dc7f5829dbde97 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 14 May 2024 12:02:44 -0400 Subject: [PATCH 16/30] fix: expect fund to work --- cmd/crates/soroban-test/tests/it/integration/hello_world.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 fae0a8463..b61435fa5 100644 --- a/cmd/crates/soroban-test/tests/it/integration/hello_world.rs +++ b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs @@ -40,7 +40,8 @@ async fn invoke() { .arg("fund") .arg("test") .arg("--hd-path=1") - .assert(); + .assert() + .success(); let addr = sandbox .new_assert_cmd("keys") .arg("address") From 26346a255bed4654f51d55736d4e42f2aacbc8bb Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Mon, 20 May 2024 16:43:47 -0400 Subject: [PATCH 17/30] fix: make commands network runnable and fix tests --- Cargo.lock | 1 + cmd/crates/soroban-test/Cargo.toml | 1 + cmd/crates/soroban-test/src/lib.rs | 33 +++-- cmd/crates/soroban-test/tests/it/config.rs | 6 +- .../soroban-test/tests/it/integration.rs | 1 + .../soroban-test/tests/it/integration/tx.rs | 130 ++++++++++++++++++ .../soroban-test/tests/it/integration/util.rs | 24 ++-- cmd/crates/soroban-test/tests/it/util.rs | 1 + cmd/soroban-cli/src/commands/mod.rs | 2 +- cmd/soroban-cli/src/commands/tx/inspect.rs | 31 +++++ cmd/soroban-cli/src/commands/tx/mod.rs | 8 +- cmd/soroban-cli/src/commands/tx/send.rs | 40 +++++- cmd/soroban-cli/src/commands/tx/simulate.rs | 40 ++++-- cmd/soroban-cli/src/commands/tx/xdr.rs | 4 +- 14 files changed, 275 insertions(+), 47 deletions(-) create mode 100644 cmd/crates/soroban-test/tests/it/integration/tx.rs create mode 100644 cmd/soroban-cli/src/commands/tx/inspect.rs diff --git a/Cargo.lock b/Cargo.lock index 46ffe86eb..75305efdc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3959,6 +3959,7 @@ version = "21.0.0-preview.1" dependencies = [ "assert_cmd", "assert_fs", + "ed25519-dalek 2.0.0", "fs_extra", "predicates 2.1.5", "sep5", diff --git a/cmd/crates/soroban-test/Cargo.toml b/cmd/crates/soroban-test/Cargo.toml index b6388b292..2ab70e313 100644 --- a/cmd/crates/soroban-test/Cargo.toml +++ b/cmd/crates/soroban-test/Cargo.toml @@ -42,6 +42,7 @@ which = { workspace = true } tokio = "1.28.1" walkdir = "2.4.0" ulid.workspace = true +ed25519-dalek = { workspace = true } [features] it = [] diff --git a/cmd/crates/soroban-test/src/lib.rs b/cmd/crates/soroban-test/src/lib.rs index d55ad7bc7..8df6a97c0 100644 --- a/cmd/crates/soroban-test/src/lib.rs +++ b/cmd/crates/soroban-test/src/lib.rs @@ -115,7 +115,7 @@ impl TestEnv { .as_deref() .ok() .and_then(|n| n.parse().ok()) - .unwrap_or(8889); + .unwrap_or(8000); Self::with_port(host_port) } /// Create a new `assert_cmd::Command` for a given subcommand and set's the current directory @@ -218,14 +218,9 @@ impl TestEnv { T::parse_arg_vec(&arg).unwrap() } - /// Invoke an already parsed invoke command - pub async fn run_cmd_with( - &self, - cmd: T, - account: &str, - ) -> Result { + pub fn clone_config(&self, account: &str) -> config::Args { let config_dir = Some(self.dir().to_path_buf()); - let config = config::Args { + config::Args { network: network::Args { rpc_url: Some(self.rpc_url.clone()), network_passphrase: Some(LOCAL_NETWORK_PASSPHRASE.to_string()), @@ -234,16 +229,22 @@ impl TestEnv { source_account: account.to_string(), locator: config::locator::Args { global: false, - config_dir: config_dir.clone(), + config_dir, }, hd_path: None, - }; + } + } + + /// Invoke an already parsed invoke command + pub async fn run_cmd_with( + &self, + cmd: T, + account: &str, + ) -> Result { + let config = self.clone_config(account); cmd.run_against_rpc_server( Some(&global::Args { - locator: config::locator::Args { - global: false, - config_dir, - }, + locator: config.locator.clone(), filter_logs: Vec::default(), quiet: false, verbose: false, @@ -289,6 +290,10 @@ impl TestEnv { fs_extra::dir::copy(&self.temp_dir, dst, &CopyOptions::new())?; Ok(()) } + + pub fn client(&self) -> soroban_rpc::Client { + soroban_rpc::Client::new(&self.rpc_url).unwrap() + } } pub fn temp_ledger_file() -> OsString { diff --git a/cmd/crates/soroban-test/tests/it/config.rs b/cmd/crates/soroban-test/tests/it/config.rs index bca7088ee..dd33713aa 100644 --- a/cmd/crates/soroban-test/tests/it/config.rs +++ b/cmd/crates/soroban-test/tests/it/config.rs @@ -3,9 +3,7 @@ use soroban_test::{AssertExt, TestEnv}; use std::{fs, path::Path}; use crate::util::{add_key, add_test_id, SecretKind, DEFAULT_SEED_PHRASE}; -use soroban_cli::commands::network; - -const NETWORK_PASSPHRASE: &str = "Local Sandbox Stellar Network ; September 2022"; +use soroban_cli::commands::network::{self, LOCAL_NETWORK_PASSPHRASE}; fn ls(sandbox: &TestEnv) -> Vec { sandbox @@ -51,7 +49,7 @@ fn add_network(sandbox: &TestEnv, name: &str) { .args([ "--rpc-url=https://127.0.0.1", "--network-passphrase", - NETWORK_PASSPHRASE, + LOCAL_NETWORK_PASSPHRASE, name, ]) .assert() diff --git a/cmd/crates/soroban-test/tests/it/integration.rs b/cmd/crates/soroban-test/tests/it/integration.rs index 4e92b931a..cca151048 100644 --- a/cmd/crates/soroban-test/tests/it/integration.rs +++ b/cmd/crates/soroban-test/tests/it/integration.rs @@ -2,4 +2,5 @@ mod custom_types; mod dotenv; mod hello_world; mod util; +mod tx; mod wrap; diff --git a/cmd/crates/soroban-test/tests/it/integration/tx.rs b/cmd/crates/soroban-test/tests/it/integration/tx.rs new file mode 100644 index 000000000..13f11463e --- /dev/null +++ b/cmd/crates/soroban-test/tests/it/integration/tx.rs @@ -0,0 +1,130 @@ +use ed25519_dalek::Signer; +use sha2::{Digest, Sha256}; +use soroban_cli::{ + commands::{contract::install, tx}, + fee, +}; +use soroban_sdk::xdr::{ + self, AccountEntry, Limits, ReadXdr, Transaction, TransactionEnvelope, TransactionV1Envelope, + VecM, WriteXdr, +}; +use soroban_test::{AssertExt, TestEnv}; + +use crate::{ + integration::util::{deploy_contract, HELLO_WORLD}, + util::LOCAL_NETWORK_PASSPHRASE, +}; + +#[tokio::test] +async fn txn_simulate() { + let host_port = 8000; + let sandbox = &TestEnv::with_rpc_url(&format!("http://moss:{host_port}/soroban/rpc")); + let xdr_base64 = deploy_contract(sandbox, HELLO_WORLD, true).await; + println!("{xdr_base64}"); + let cmd = tx::simulate::Cmd::default(); + let tx_env = TransactionEnvelope::from_xdr_base64(&xdr_base64, Limits::none()).unwrap(); + let TransactionEnvelope::Tx(TransactionV1Envelope { tx, .. }) = &tx_env else { + panic!("Only transaction v1 is supported") + }; + let assembled = cmd.simulate(tx, &sandbox.client()).await.unwrap(); + let assembled_str = sandbox + .new_assert_cmd("tx") + .arg("simulate") + .write_stdin(xdr_base64.as_bytes()) + .assert() + .success() + .stdout_as_str(); + println!("{assembled_str}"); + assert_eq!( + assembled + .transaction() + .to_xdr_base64(Limits::none()) + .unwrap(), + assembled_str + ); +} + +#[tokio::test] +async fn txn_send() { + let host_port = 8000; + let sandbox = &TestEnv::with_rpc_url(&format!("http://moss:{host_port}/soroban/rpc")); + sandbox + .new_assert_cmd("contract") + .arg("install") + .args(["--wasm", HELLO_WORLD.path().as_os_str().to_str().unwrap()]) + .assert() + .success(); + + let xdr_base64 = deploy_contract(sandbox, HELLO_WORLD, true).await; + println!("{xdr_base64}"); + let tx_env = TransactionEnvelope::from_xdr_base64(&xdr_base64, Limits::none()).unwrap(); + let TransactionEnvelope::Tx(TransactionV1Envelope { tx, .. }) = &tx_env else { + panic!("Only transaction v1 is supported") + }; + let assembled = tx::simulate::Cmd::default() + .simulate(tx, &sandbox.client()) + .await + .unwrap(); + let mut tx = assembled.transaction().clone(); + let address = sandbox + .new_assert_cmd("keys") + .arg("address") + .arg("test") + .assert() + .stdout_as_str(); + let secret_key = sandbox + .new_assert_cmd("keys") + .arg("show") + .arg("test") + .assert() + .stdout_as_str(); + println!("Secret key: {secret_key}"); + let key = stellar_strkey::ed25519::PrivateKey::from_string(&secret_key).unwrap(); + let key = ed25519_dalek::SigningKey::from_bytes(&key.0); + let xdr::AccountEntry { seq_num, .. } = sandbox.client().get_account(&address).await.unwrap(); + tx.seq_num = xdr::SequenceNumber(seq_num.0 + 1); + let tx_env = sign(tx, &key, LOCAL_NETWORK_PASSPHRASE).unwrap(); + + println!( + "Transaction to send:\n{}", + tx_env.to_xdr_base64(Limits::none()).unwrap() + ); + + let tx_env = assembled.sign(&key, LOCAL_NETWORK_PASSPHRASE).unwrap(); + let assembled_str = sandbox + .new_assert_cmd("tx") + .arg("send") + .arg("--source=test") + .write_stdin(tx_env.to_xdr_base64(Limits::none()).unwrap()) + .assert() + .success() + .stdout_as_str(); + println!("Transaction sent: {assembled_str}"); +} + +fn sign( + tx: Transaction, + key: &ed25519_dalek::SigningKey, + network_passphrase: &str, +) -> Result { + let tx_hash = hash(&tx, network_passphrase).unwrap(); + let tx_signature = key.sign(&tx_hash); + + let decorated_signature = xdr::DecoratedSignature { + hint: xdr::SignatureHint(key.verifying_key().to_bytes()[28..].try_into()?), + signature: xdr::Signature(tx_signature.to_bytes().try_into()?), + }; + + Ok(TransactionEnvelope::Tx(TransactionV1Envelope { + tx, + signatures: vec![decorated_signature].try_into()?, + })) +} + +pub fn hash(tx: &Transaction, network_passphrase: &str) -> Result<[u8; 32], xdr::Error> { + let signature_payload = xdr::TransactionSignaturePayload { + network_id: xdr::Hash(Sha256::digest(network_passphrase).into()), + tagged_transaction: xdr::TransactionSignaturePayloadTaggedTransaction::Tx(tx.clone()), + }; + Ok(Sha256::digest(signature_payload.to_xdr(Limits::none())?).into()) +} diff --git a/cmd/crates/soroban-test/tests/it/integration/util.rs b/cmd/crates/soroban-test/tests/it/integration/util.rs index 4fc870e34..2cf91653b 100644 --- a/cmd/crates/soroban-test/tests/it/integration/util.rs +++ b/cmd/crates/soroban-test/tests/it/integration/util.rs @@ -1,4 +1,5 @@ use soroban_cli::commands; +use soroban_sdk::xdr::{Limits, WriteXdr}; use soroban_test::{TestEnv, Wasm}; use std::fmt::Display; @@ -21,14 +22,14 @@ where pub const TEST_SALT: &str = "f55ff16f66f43360266b95db6f8fec01d76031054306ae4a4b380598f6cfd114"; pub async fn deploy_hello(sandbox: &TestEnv) -> String { - deploy_contract(sandbox, HELLO_WORLD).await + deploy_contract(sandbox, HELLO_WORLD, false).await } pub async fn deploy_custom(sandbox: &TestEnv) -> String { - deploy_contract(sandbox, CUSTOM_TYPES).await + deploy_contract(sandbox, CUSTOM_TYPES, false).await } -pub async fn deploy_contract(sandbox: &TestEnv, wasm: &Wasm<'static>) -> String { +pub async fn deploy_contract(sandbox: &TestEnv, wasm: &Wasm<'static>, build_only: bool) -> String { let cmd = sandbox.cmd_with_config::<_, commands::contract::deploy::wasm::Cmd>(&[ "--fee", "1000000", @@ -37,13 +38,18 @@ pub async fn deploy_contract(sandbox: &TestEnv, wasm: &Wasm<'static>) -> String "--salt", TEST_SALT, "--ignore-checks", + build_only.then_some("--build-only").unwrap_or_default(), ]); - sandbox - .run_cmd_with(cmd, "test") - .await - .unwrap() - .into_result() - .unwrap() + let res = sandbox.run_cmd_with(cmd, "test").await.unwrap(); + if build_only { + match res.to_envelope() { + commands::txn_result::TxnEnvelopeResult::TxnEnvelope(e) => { + return e.to_xdr_base64(Limits::none()).unwrap() + } + commands::txn_result::TxnEnvelopeResult::Res(_) => todo!(), + } + } + res.into_result().unwrap() } pub async fn extend_contract(sandbox: &TestEnv, id: &str) { diff --git a/cmd/crates/soroban-test/tests/it/util.rs b/cmd/crates/soroban-test/tests/it/util.rs index c2f01054c..28fbdb20c 100644 --- a/cmd/crates/soroban-test/tests/it/util.rs +++ b/cmd/crates/soroban-test/tests/it/util.rs @@ -60,3 +60,4 @@ pub async fn invoke_custom( } pub const DEFAULT_CONTRACT_ID: &str = "CDR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OO5Z"; +pub const LOCAL_NETWORK_PASSPHRASE: &str = "Local Sandbox Stellar Network ; September 2022"; \ No newline at end of file diff --git a/cmd/soroban-cli/src/commands/mod.rs b/cmd/soroban-cli/src/commands/mod.rs index 1536114db..bb6343de6 100644 --- a/cmd/soroban-cli/src/commands/mod.rs +++ b/cmd/soroban-cli/src/commands/mod.rs @@ -101,7 +101,7 @@ impl Root { Cmd::Network(network) => network.run().await?, Cmd::Version(version) => version.run(), Cmd::Keys(id) => id.run().await?, - Cmd::Tx(tx) => tx.run().await?, + Cmd::Tx(tx) => tx.run(&self.global_args).await?, Cmd::Cache(data) => data.run()?, }; Ok(()) diff --git a/cmd/soroban-cli/src/commands/tx/inspect.rs b/cmd/soroban-cli/src/commands/tx/inspect.rs new file mode 100644 index 000000000..a49438ed9 --- /dev/null +++ b/cmd/soroban-cli/src/commands/tx/inspect.rs @@ -0,0 +1,31 @@ +use soroban_rpc::GetTransactionResponse; +use stellar_xdr::cli as xdr_cli; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + XdrCli(#[from] xdr_cli::Error), +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +pub struct Cmd { + #[clap(flatten)] + pub xdr_args: xdr_cli::Root, + +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + let response = self.send().await?; + println!("{}", serde_json::to_string_pretty(&response)?); + Ok(()) + } + + pub async fn send(&self) -> Result { + let txn_env = self.xdr_args.txn_envelope()?; + let network = self.config.get_network()?; + let client = crate::rpc::Client::new(&network.rpc_url)?; + Ok(client.send_transaction(&txn_env).await?) + } +} diff --git a/cmd/soroban-cli/src/commands/tx/mod.rs b/cmd/soroban-cli/src/commands/tx/mod.rs index d06b5a544..a03d92453 100644 --- a/cmd/soroban-cli/src/commands/tx/mod.rs +++ b/cmd/soroban-cli/src/commands/tx/mod.rs @@ -1,5 +1,7 @@ use clap::Parser; +use super::global; + pub mod send; pub mod simulate; pub mod xdr; @@ -23,10 +25,10 @@ pub enum Error { } impl Cmd { - pub async fn run(&self) -> Result<(), Error> { + pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { match self { - Cmd::Send(cmd) => cmd.run().await?, - Cmd::Simulate(cmd) => cmd.run().await?, + Cmd::Send(cmd) => cmd.run(global_args).await?, + Cmd::Simulate(cmd) => cmd.run(global_args).await?, }; Ok(()) } diff --git a/cmd/soroban-cli/src/commands/tx/send.rs b/cmd/soroban-cli/src/commands/tx/send.rs index c9496f08e..4b7e5eb7a 100644 --- a/cmd/soroban-cli/src/commands/tx/send.rs +++ b/cmd/soroban-cli/src/commands/tx/send.rs @@ -1,5 +1,11 @@ +use async_trait::async_trait; use soroban_rpc::GetTransactionResponse; +use crate::{ + commands::{config, global, NetworkRunnable}, + xdr::TransactionEnvelope, +}; + #[derive(thiserror::Error, Debug)] pub enum Error { #[error(transparent)] @@ -15,22 +21,44 @@ pub enum Error { #[derive(Debug, clap::Parser, Clone)] #[group(skip)] /// Command to send a transaction envelope to the network +/// e.g. `cat file.txt | soroban tx send` pub struct Cmd { #[clap(flatten)] pub config: super::super::config::Args, } impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - let response = self.send().await?; + pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { + let response = self + .run_against_rpc_server(Some(global_args), Some(&self.config)) + .await?; println!("{}", serde_json::to_string_pretty(&response)?); Ok(()) } - pub async fn send(&self) -> Result { - let txn_env = super::xdr::txn_envelope_from_stdin()?; - let network = self.config.get_network()?; + pub async fn send( + &self, + tx_env: &TransactionEnvelope, + client: &crate::rpc::Client, + ) -> Result { + Ok(client.send_transaction(tx_env).await?) + } +} + +#[async_trait] +impl NetworkRunnable for Cmd { + type Error = Error; + + type Result = GetTransactionResponse; + 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()?; let client = crate::rpc::Client::new(&network.rpc_url)?; - Ok(client.send_transaction(&txn_env).await?) + let tx_env = super::xdr::tx_envelope_from_stdin()?; + self.send(&tx_env, &client).await } } diff --git a/cmd/soroban-cli/src/commands/tx/simulate.rs b/cmd/soroban-cli/src/commands/tx/simulate.rs index 20abeaef3..7022b6106 100644 --- a/cmd/soroban-cli/src/commands/tx/simulate.rs +++ b/cmd/soroban-cli/src/commands/tx/simulate.rs @@ -1,5 +1,8 @@ +use async_trait::async_trait; use soroban_rpc::Assembled; -use soroban_sdk::xdr::{self, WriteXdr}; +use soroban_sdk::xdr::{self, Transaction, WriteXdr}; + +use crate::commands::{config, global, NetworkRunnable}; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -15,7 +18,7 @@ pub enum Error { /// Command to simulate a transaction envelope via rpc /// e.g. `cat file.txt | soroban tx simulate` -#[derive(Debug, clap::Parser, Clone)] +#[derive(Debug, clap::Parser, Clone, Default)] #[group(skip)] pub struct Cmd { #[clap(flatten)] @@ -23,16 +26,37 @@ pub struct Cmd { } impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - let res = self.simulate().await?; + pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { + let res = self + .run_against_rpc_server(Some(global_args), Some(&self.config)) + .await?; println!("{}", res.transaction().to_xdr_base64(xdr::Limits::none())?); Ok(()) } - pub async fn simulate(&self) -> Result { - let tx = super::xdr::unwrap_envelope_v1()?; - let network = self.config.get_network()?; + pub async fn simulate( + &self, + tx: &Transaction, + client: &crate::rpc::Client, + ) -> Result { + Ok(client.create_assembled_transaction(tx).await?) + } +} + +#[async_trait] +impl NetworkRunnable for Cmd { + type Error = Error; + + type Result = Assembled; + 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()?; let client = crate::rpc::Client::new(&network.rpc_url)?; - Ok(client.create_assembled_transaction(&tx).await?) + let tx = super::xdr::unwrap_envelope_v1()?; + self.simulate(&tx, &client).await } } diff --git a/cmd/soroban-cli/src/commands/tx/xdr.rs b/cmd/soroban-cli/src/commands/tx/xdr.rs index 505ab8106..c433a7002 100644 --- a/cmd/soroban-cli/src/commands/tx/xdr.rs +++ b/cmd/soroban-cli/src/commands/tx/xdr.rs @@ -20,7 +20,7 @@ pub enum Error { OnlyTransactionV1Supported, } -pub fn txn_envelope_from_stdin() -> Result { +pub fn tx_envelope_from_stdin() -> Result { from_stdin() } pub fn from_stdin() -> Result { @@ -32,7 +32,7 @@ pub fn from_stdin() -> Result { } pub fn unwrap_envelope_v1() -> Result { - let TransactionEnvelope::Tx(TransactionV1Envelope { tx, .. }) = txn_envelope_from_stdin()? + let TransactionEnvelope::Tx(TransactionV1Envelope { tx, .. }) = tx_envelope_from_stdin()? else { return Err(Error::OnlyTransactionV1Supported); }; From 1c3ec6d883082d26a7f4e387422dac5d97c16725 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Mon, 20 May 2024 16:46:22 -0400 Subject: [PATCH 18/30] fix: fmt --- cmd/crates/soroban-test/tests/it/integration.rs | 2 +- cmd/crates/soroban-test/tests/it/util.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/crates/soroban-test/tests/it/integration.rs b/cmd/crates/soroban-test/tests/it/integration.rs index cca151048..a321be73a 100644 --- a/cmd/crates/soroban-test/tests/it/integration.rs +++ b/cmd/crates/soroban-test/tests/it/integration.rs @@ -1,6 +1,6 @@ mod custom_types; mod dotenv; mod hello_world; -mod util; mod tx; +mod util; mod wrap; diff --git a/cmd/crates/soroban-test/tests/it/util.rs b/cmd/crates/soroban-test/tests/it/util.rs index 28fbdb20c..261936864 100644 --- a/cmd/crates/soroban-test/tests/it/util.rs +++ b/cmd/crates/soroban-test/tests/it/util.rs @@ -60,4 +60,4 @@ pub async fn invoke_custom( } pub const DEFAULT_CONTRACT_ID: &str = "CDR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OO5Z"; -pub const LOCAL_NETWORK_PASSPHRASE: &str = "Local Sandbox Stellar Network ; September 2022"; \ No newline at end of file +pub const LOCAL_NETWORK_PASSPHRASE: &str = "Local Sandbox Stellar Network ; September 2022"; From 17e3ad217504a8d0fd820dc34518e6ae45f41895 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Mon, 20 May 2024 16:57:35 -0400 Subject: [PATCH 19/30] fix: use default TestEnv --- cmd/crates/soroban-test/tests/it/integration/tx.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/crates/soroban-test/tests/it/integration/tx.rs b/cmd/crates/soroban-test/tests/it/integration/tx.rs index 13f11463e..c7d943a11 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx.rs @@ -17,8 +17,8 @@ use crate::{ #[tokio::test] async fn txn_simulate() { - let host_port = 8000; - let sandbox = &TestEnv::with_rpc_url(&format!("http://moss:{host_port}/soroban/rpc")); + + let sandbox = &TestEnv::new(); let xdr_base64 = deploy_contract(sandbox, HELLO_WORLD, true).await; println!("{xdr_base64}"); let cmd = tx::simulate::Cmd::default(); @@ -46,8 +46,8 @@ async fn txn_simulate() { #[tokio::test] async fn txn_send() { - let host_port = 8000; - let sandbox = &TestEnv::with_rpc_url(&format!("http://moss:{host_port}/soroban/rpc")); + + let sandbox = &TestEnv::new(); sandbox .new_assert_cmd("contract") .arg("install") From df3576007e5e8af1b55dc588f49e920072d735e9 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Mon, 20 May 2024 17:00:02 -0400 Subject: [PATCH 20/30] fix: clippy --- cmd/crates/soroban-test/tests/it/integration/tx.rs | 2 -- cmd/crates/soroban-test/tests/it/util.rs | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/crates/soroban-test/tests/it/integration/tx.rs b/cmd/crates/soroban-test/tests/it/integration/tx.rs index c7d943a11..2901dee61 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx.rs @@ -17,7 +17,6 @@ use crate::{ #[tokio::test] async fn txn_simulate() { - let sandbox = &TestEnv::new(); let xdr_base64 = deploy_contract(sandbox, HELLO_WORLD, true).await; println!("{xdr_base64}"); @@ -46,7 +45,6 @@ async fn txn_simulate() { #[tokio::test] async fn txn_send() { - let sandbox = &TestEnv::new(); sandbox .new_assert_cmd("contract") diff --git a/cmd/crates/soroban-test/tests/it/util.rs b/cmd/crates/soroban-test/tests/it/util.rs index 261936864..6bf5f8c22 100644 --- a/cmd/crates/soroban-test/tests/it/util.rs +++ b/cmd/crates/soroban-test/tests/it/util.rs @@ -60,4 +60,5 @@ pub async fn invoke_custom( } pub const DEFAULT_CONTRACT_ID: &str = "CDR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OO5Z"; +#[allow(dead_code)] pub const LOCAL_NETWORK_PASSPHRASE: &str = "Local Sandbox Stellar Network ; September 2022"; From 87f965c56134fdf32b6cdf2e26e8dc9bc16908cc Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Mon, 27 May 2024 10:29:37 -0600 Subject: [PATCH 21/30] fix: remove send as it is hard to test without sign --- .../soroban-test/tests/it/integration/tx.rs | 101 +----------------- cmd/soroban-cli/src/commands/tx/mod.rs | 7 -- cmd/soroban-cli/src/commands/tx/send.rs | 64 ----------- 3 files changed, 3 insertions(+), 169 deletions(-) delete mode 100644 cmd/soroban-cli/src/commands/tx/send.rs diff --git a/cmd/crates/soroban-test/tests/it/integration/tx.rs b/cmd/crates/soroban-test/tests/it/integration/tx.rs index 2901dee61..bf9d01cec 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx.rs @@ -1,19 +1,8 @@ -use ed25519_dalek::Signer; -use sha2::{Digest, Sha256}; -use soroban_cli::{ - commands::{contract::install, tx}, - fee, -}; -use soroban_sdk::xdr::{ - self, AccountEntry, Limits, ReadXdr, Transaction, TransactionEnvelope, TransactionV1Envelope, - VecM, WriteXdr, -}; +use soroban_cli::commands::tx; +use soroban_sdk::xdr::{Limits, ReadXdr, TransactionEnvelope, TransactionV1Envelope, WriteXdr}; use soroban_test::{AssertExt, TestEnv}; -use crate::{ - integration::util::{deploy_contract, HELLO_WORLD}, - util::LOCAL_NETWORK_PASSPHRASE, -}; +use crate::integration::util::{deploy_contract, HELLO_WORLD}; #[tokio::test] async fn txn_simulate() { @@ -42,87 +31,3 @@ async fn txn_simulate() { assembled_str ); } - -#[tokio::test] -async fn txn_send() { - let sandbox = &TestEnv::new(); - sandbox - .new_assert_cmd("contract") - .arg("install") - .args(["--wasm", HELLO_WORLD.path().as_os_str().to_str().unwrap()]) - .assert() - .success(); - - let xdr_base64 = deploy_contract(sandbox, HELLO_WORLD, true).await; - println!("{xdr_base64}"); - let tx_env = TransactionEnvelope::from_xdr_base64(&xdr_base64, Limits::none()).unwrap(); - let TransactionEnvelope::Tx(TransactionV1Envelope { tx, .. }) = &tx_env else { - panic!("Only transaction v1 is supported") - }; - let assembled = tx::simulate::Cmd::default() - .simulate(tx, &sandbox.client()) - .await - .unwrap(); - let mut tx = assembled.transaction().clone(); - let address = sandbox - .new_assert_cmd("keys") - .arg("address") - .arg("test") - .assert() - .stdout_as_str(); - let secret_key = sandbox - .new_assert_cmd("keys") - .arg("show") - .arg("test") - .assert() - .stdout_as_str(); - println!("Secret key: {secret_key}"); - let key = stellar_strkey::ed25519::PrivateKey::from_string(&secret_key).unwrap(); - let key = ed25519_dalek::SigningKey::from_bytes(&key.0); - let xdr::AccountEntry { seq_num, .. } = sandbox.client().get_account(&address).await.unwrap(); - tx.seq_num = xdr::SequenceNumber(seq_num.0 + 1); - let tx_env = sign(tx, &key, LOCAL_NETWORK_PASSPHRASE).unwrap(); - - println!( - "Transaction to send:\n{}", - tx_env.to_xdr_base64(Limits::none()).unwrap() - ); - - let tx_env = assembled.sign(&key, LOCAL_NETWORK_PASSPHRASE).unwrap(); - let assembled_str = sandbox - .new_assert_cmd("tx") - .arg("send") - .arg("--source=test") - .write_stdin(tx_env.to_xdr_base64(Limits::none()).unwrap()) - .assert() - .success() - .stdout_as_str(); - println!("Transaction sent: {assembled_str}"); -} - -fn sign( - tx: Transaction, - key: &ed25519_dalek::SigningKey, - network_passphrase: &str, -) -> Result { - let tx_hash = hash(&tx, network_passphrase).unwrap(); - let tx_signature = key.sign(&tx_hash); - - let decorated_signature = xdr::DecoratedSignature { - hint: xdr::SignatureHint(key.verifying_key().to_bytes()[28..].try_into()?), - signature: xdr::Signature(tx_signature.to_bytes().try_into()?), - }; - - Ok(TransactionEnvelope::Tx(TransactionV1Envelope { - tx, - signatures: vec![decorated_signature].try_into()?, - })) -} - -pub fn hash(tx: &Transaction, network_passphrase: &str) -> Result<[u8; 32], xdr::Error> { - let signature_payload = xdr::TransactionSignaturePayload { - network_id: xdr::Hash(Sha256::digest(network_passphrase).into()), - tagged_transaction: xdr::TransactionSignaturePayloadTaggedTransaction::Tx(tx.clone()), - }; - Ok(Sha256::digest(signature_payload.to_xdr(Limits::none())?).into()) -} diff --git a/cmd/soroban-cli/src/commands/tx/mod.rs b/cmd/soroban-cli/src/commands/tx/mod.rs index a03d92453..48161e3c7 100644 --- a/cmd/soroban-cli/src/commands/tx/mod.rs +++ b/cmd/soroban-cli/src/commands/tx/mod.rs @@ -2,14 +2,11 @@ use clap::Parser; use super::global; -pub mod send; pub mod simulate; pub mod xdr; #[derive(Debug, Parser)] pub enum Cmd { - /// Submit a transaction envelope from stdin to the network - Send(send::Cmd), /// Simulate a transaction envelope from stdin Simulate(simulate::Cmd), } @@ -19,15 +16,11 @@ pub enum Error { /// An error during the simulation #[error(transparent)] Simulate(#[from] simulate::Error), - /// An error during the send - #[error(transparent)] - Send(#[from] send::Error), } impl Cmd { pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { match self { - Cmd::Send(cmd) => cmd.run(global_args).await?, Cmd::Simulate(cmd) => cmd.run(global_args).await?, }; Ok(()) diff --git a/cmd/soroban-cli/src/commands/tx/send.rs b/cmd/soroban-cli/src/commands/tx/send.rs deleted file mode 100644 index 4b7e5eb7a..000000000 --- a/cmd/soroban-cli/src/commands/tx/send.rs +++ /dev/null @@ -1,64 +0,0 @@ -use async_trait::async_trait; -use soroban_rpc::GetTransactionResponse; - -use crate::{ - commands::{config, global, NetworkRunnable}, - xdr::TransactionEnvelope, -}; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - XdrArgs(#[from] super::xdr::Error), - #[error(transparent)] - Config(#[from] super::super::config::Error), - #[error(transparent)] - Rpc(#[from] crate::rpc::Error), - #[error(transparent)] - SerdeJson(#[from] serde_json::Error), -} - -#[derive(Debug, clap::Parser, Clone)] -#[group(skip)] -/// Command to send a transaction envelope to the network -/// e.g. `cat file.txt | soroban tx send` -pub struct Cmd { - #[clap(flatten)] - pub config: super::super::config::Args, -} - -impl Cmd { - pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { - let response = self - .run_against_rpc_server(Some(global_args), Some(&self.config)) - .await?; - println!("{}", serde_json::to_string_pretty(&response)?); - Ok(()) - } - - pub async fn send( - &self, - tx_env: &TransactionEnvelope, - client: &crate::rpc::Client, - ) -> Result { - Ok(client.send_transaction(tx_env).await?) - } -} - -#[async_trait] -impl NetworkRunnable for Cmd { - type Error = Error; - - type Result = GetTransactionResponse; - 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()?; - let client = crate::rpc::Client::new(&network.rpc_url)?; - let tx_env = super::xdr::tx_envelope_from_stdin()?; - self.send(&tx_env, &client).await - } -} From bb6d15407ab117104944d941af9b51fb993cb62a Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Mon, 27 May 2024 15:16:28 -0600 Subject: [PATCH 22/30] fix: docs --- FULL_HELP_DOCS.md | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index ec33eefa3..09d7bd599 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -52,7 +52,6 @@ This document contains the help content for the `stellar` command-line program. * [`stellar network stop`↴](#stellar-network-stop) * [`stellar version`↴](#stellar-version) * [`stellar tx`↴](#stellar-tx) -* [`stellar tx send`↴](#stellar-tx-send) * [`stellar tx simulate`↴](#stellar-tx-simulate) * [`stellar cache`↴](#stellar-cache) * [`stellar cache clean`↴](#stellar-cache-clean) @@ -1325,32 +1324,10 @@ Sign, Simulate, and Send transactions ###### **Subcommands:** -* `send` — Submit a transaction envelope from stdin to the network * `simulate` — Simulate a transaction envelope from stdin -## `stellar tx send` - -Submit a transaction envelope from stdin to the network - -**Usage:** `stellar tx send [OPTIONS] --source-account ` - -###### **Options:** - -* `--rpc-url ` — RPC server endpoint -* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server -* `--network ` — Name of network to use from config -* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--global` — Use global config - - Possible values: `true`, `false` - -* `--config-dir ` — Location of config directory, default is "." - - - ## `stellar tx simulate` Simulate a transaction envelope from stdin From fc3874cc2059b248bc64a62c2ad3f91ce6a9f9e6 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 30 May 2024 16:36:03 -0600 Subject: [PATCH 23/30] fix: simulate should return a Txn Envelope --- .cargo/config.toml | 2 +- cmd/soroban-cli/src/commands/tx/simulate.rs | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 2559ffee7..02f193482 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,9 +1,9 @@ # paths = ["/path/to/override"] # path dependency overrides [alias] # command aliases -md-gen = "run --bin doc-gen --features clap-markdown" f = "fmt" md-gen = "run --bin doc-gen --features clap-markdown" +s = "run --quiet --" # b = "build" # c = "check" # t = "test" diff --git a/cmd/soroban-cli/src/commands/tx/simulate.rs b/cmd/soroban-cli/src/commands/tx/simulate.rs index 7022b6106..092a2c162 100644 --- a/cmd/soroban-cli/src/commands/tx/simulate.rs +++ b/cmd/soroban-cli/src/commands/tx/simulate.rs @@ -1,6 +1,6 @@ +use crate::xdr::{self, Transaction, TransactionEnvelope, TransactionV1Envelope, VecM, WriteXdr}; use async_trait::async_trait; use soroban_rpc::Assembled; -use soroban_sdk::xdr::{self, Transaction, WriteXdr}; use crate::commands::{config, global, NetworkRunnable}; @@ -30,7 +30,15 @@ impl Cmd { let res = self .run_against_rpc_server(Some(global_args), Some(&self.config)) .await?; - println!("{}", res.transaction().to_xdr_base64(xdr::Limits::none())?); + let tx = res.transaction().clone(); + println!( + "{}", + TransactionEnvelope::Tx(TransactionV1Envelope { + tx, + signatures: VecM::default() + }) + .to_xdr_base64(xdr::Limits::none())? + ); Ok(()) } From a9996a58b1a6d1f97d29d84b30c7a01d88c8de07 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 30 May 2024 18:19:38 -0600 Subject: [PATCH 24/30] fix: testing added DeployKind to allow deploys to return various data --- .../soroban-test/tests/it/integration/tx.rs | 27 +++++++++----- .../soroban-test/tests/it/integration/util.rs | 37 +++++++++++++++---- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/cmd/crates/soroban-test/tests/it/integration/tx.rs b/cmd/crates/soroban-test/tests/it/integration/tx.rs index bf9d01cec..852c87a52 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx.rs @@ -1,16 +1,20 @@ use soroban_cli::commands::tx; -use soroban_sdk::xdr::{Limits, ReadXdr, TransactionEnvelope, TransactionV1Envelope, WriteXdr}; +use soroban_sdk::xdr::{ + Limits, ReadXdr, TransactionEnvelope, TransactionV1Envelope, VecM, WriteXdr, +}; use soroban_test::{AssertExt, TestEnv}; -use crate::integration::util::{deploy_contract, HELLO_WORLD}; +use crate::integration::util::{deploy_contract, DeployKind, HELLO_WORLD}; #[tokio::test] async fn txn_simulate() { let sandbox = &TestEnv::new(); - let xdr_base64 = deploy_contract(sandbox, HELLO_WORLD, true).await; - println!("{xdr_base64}"); + let xdr_base64_build_only = deploy_contract(sandbox, HELLO_WORLD, DeployKind::BuildOnly).await; + let xdr_base64_sim_only = deploy_contract(sandbox, HELLO_WORLD, DeployKind::SimOnly).await; + println!("{xdr_base64_build_only}"); let cmd = tx::simulate::Cmd::default(); - let tx_env = TransactionEnvelope::from_xdr_base64(&xdr_base64, Limits::none()).unwrap(); + let tx_env = + TransactionEnvelope::from_xdr_base64(&xdr_base64_build_only, Limits::none()).unwrap(); let TransactionEnvelope::Tx(TransactionV1Envelope { tx, .. }) = &tx_env else { panic!("Only transaction v1 is supported") }; @@ -18,16 +22,19 @@ async fn txn_simulate() { let assembled_str = sandbox .new_assert_cmd("tx") .arg("simulate") - .write_stdin(xdr_base64.as_bytes()) + .write_stdin(xdr_base64_build_only.as_bytes()) .assert() .success() .stdout_as_str(); println!("{assembled_str}"); + assert_eq!(xdr_base64_sim_only, assembled_str); + let txn_env = TransactionEnvelope::Tx(TransactionV1Envelope { + tx: assembled.transaction().clone(), + signatures: VecM::default(), + }); + assert_eq!( - assembled - .transaction() - .to_xdr_base64(Limits::none()) - .unwrap(), + txn_env.to_xdr_base64(Limits::none()).unwrap(), assembled_str ); } diff --git a/cmd/crates/soroban-test/tests/it/integration/util.rs b/cmd/crates/soroban-test/tests/it/integration/util.rs index a26deb0ae..9336ac814 100644 --- a/cmd/crates/soroban-test/tests/it/integration/util.rs +++ b/cmd/crates/soroban-test/tests/it/integration/util.rs @@ -22,19 +22,39 @@ where pub const TEST_SALT: &str = "f55ff16f66f43360266b95db6f8fec01d76031054306ae4a4b380598f6cfd114"; +pub enum DeployKind { + BuildOnly, + Normal, + SimOnly, +} + +impl Display for DeployKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DeployKind::BuildOnly => write!(f, "--build-only"), + DeployKind::Normal => write!(f, ""), + DeployKind::SimOnly => write!(f, "--sim-only"), + } + } +} + pub async fn deploy_hello(sandbox: &TestEnv) -> String { - deploy_contract(sandbox, HELLO_WORLD, false).await + deploy_contract(sandbox, HELLO_WORLD, DeployKind::Normal).await } pub async fn deploy_custom(sandbox: &TestEnv) -> String { - deploy_contract(sandbox, CUSTOM_TYPES, false).await + deploy_contract(sandbox, CUSTOM_TYPES, DeployKind::Normal).await } pub async fn deploy_swap(sandbox: &TestEnv) -> String { - deploy_contract(sandbox, SWAP).await + deploy_contract(sandbox, SWAP, DeployKind::Normal).await } -pub async fn deploy_contract(sandbox: &TestEnv, wasm: &Wasm<'static>) -> String { +pub async fn deploy_contract( + sandbox: &TestEnv, + wasm: &Wasm<'static>, + deploy: DeployKind, +) -> String { let cmd = sandbox.cmd_with_config::<_, commands::contract::deploy::wasm::Cmd>(&[ "--fee", "1000000", @@ -43,16 +63,17 @@ pub async fn deploy_contract(sandbox: &TestEnv, wasm: &Wasm<'static>) -> String "--salt", TEST_SALT, "--ignore-checks", - build_only.then_some("--build-only").unwrap_or_default(), + deploy.to_string().as_str(), ]); let res = sandbox.run_cmd_with(cmd, "test").await.unwrap(); - if build_only { - match res.to_envelope() { + match deploy { + DeployKind::BuildOnly | DeployKind::SimOnly => match res.to_envelope() { commands::txn_result::TxnEnvelopeResult::TxnEnvelope(e) => { return e.to_xdr_base64(Limits::none()).unwrap() } commands::txn_result::TxnEnvelopeResult::Res(_) => todo!(), - } + }, + DeployKind::Normal => (), } res.into_result().unwrap() } From 0552599af01659fedf3490ac660869ac1f2fa320 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Fri, 31 May 2024 09:29:45 -0600 Subject: [PATCH 25/30] fix: add check for sim_only --- cmd/crates/soroban-test/tests/it/integration/hello_world.rs | 1 + cmd/soroban-cli/src/commands/contract/deploy/wasm.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) 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 b61435fa5..4a675b648 100644 --- a/cmd/crates/soroban-test/tests/it/integration/hello_world.rs +++ b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs @@ -25,6 +25,7 @@ async fn invoke_view_with_non_existent_source_account() { } #[tokio::test] +#[allow(clippy::too_many_lines)] async fn invoke() { let sandbox = &TestEnv::new(); let c = soroban_rpc::Client::new(&sandbox.rpc_url).unwrap(); diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index 0b944c78a..b901b0ce5 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -127,7 +127,7 @@ impl NetworkRunnable for Cmd { ) -> Result, Error> { let config = config.unwrap_or(&self.config); let wasm_hash = if let Some(wasm) = &self.wasm { - let hash = if self.fee.build_only { + let hash = if self.fee.build_only || self.fee.sim_only { wasm::Args { wasm: wasm.clone() }.hash()? } else { install::Cmd { From b2a075fa0f4dfc8adc7bc1afffb0a79b809664a1 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 18 Jun 2024 10:44:44 -0400 Subject: [PATCH 26/30] fix: update to use new RPC method --- cmd/soroban-cli/src/commands/tx/simulate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/soroban-cli/src/commands/tx/simulate.rs b/cmd/soroban-cli/src/commands/tx/simulate.rs index 092a2c162..69b2ad4fa 100644 --- a/cmd/soroban-cli/src/commands/tx/simulate.rs +++ b/cmd/soroban-cli/src/commands/tx/simulate.rs @@ -47,7 +47,7 @@ impl Cmd { tx: &Transaction, client: &crate::rpc::Client, ) -> Result { - Ok(client.create_assembled_transaction(tx).await?) + Ok(client.simulate_and_assemble_transaction(tx).await?) } } From c65f16c5f8b7aba96f4cf3432c993516ddfc0c90 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 20 Jun 2024 10:38:46 -0400 Subject: [PATCH 27/30] fix: tests and docs --- FULL_HELP_DOCS.md | 2 +- cmd/crates/soroban-test/tests/it/integration/util.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index fef0376d7..dead49279 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -103,7 +103,7 @@ Anything after the `--` double dash (the "slop") is parsed as arguments to the c * `network` — Start and configure networks * `version` — Print version information * `tx` — Sign, Simulate, and Send transactions -* `cache` — Cache for tranasctions and contract specs +* `cache` — Cache for transactions and contract specs ###### **Options:** diff --git a/cmd/crates/soroban-test/tests/it/integration/util.rs b/cmd/crates/soroban-test/tests/it/integration/util.rs index ab22752e6..c7f348e20 100644 --- a/cmd/crates/soroban-test/tests/it/integration/util.rs +++ b/cmd/crates/soroban-test/tests/it/integration/util.rs @@ -52,7 +52,7 @@ pub async fn deploy_swap(sandbox: &TestEnv) -> String { } pub async fn deploy_custom_account(sandbox: &TestEnv) -> String { - deploy_contract(sandbox, CUSTOM_ACCOUNT).await + deploy_contract(sandbox, CUSTOM_ACCOUNT, DeployKind::Normal).await } pub async fn deploy_contract( From 0a2e72ef3c0428feca7ddcc01aa2b1f9bcf0d77e Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Fri, 21 Jun 2024 15:11:51 -0400 Subject: [PATCH 28/30] fix: address comments and use new tx.into() for tx_env for cleaner code --- .../soroban-test/tests/it/integration/tx.rs | 14 +++-------- cmd/soroban-cli/src/commands/tx/simulate.rs | 25 ++++--------------- cmd/soroban-cli/src/commands/tx/xdr.rs | 5 ++-- 3 files changed, 10 insertions(+), 34 deletions(-) diff --git a/cmd/crates/soroban-test/tests/it/integration/tx.rs b/cmd/crates/soroban-test/tests/it/integration/tx.rs index 852c87a52..d467e8e1c 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx.rs @@ -11,14 +11,10 @@ async fn txn_simulate() { let sandbox = &TestEnv::new(); let xdr_base64_build_only = deploy_contract(sandbox, HELLO_WORLD, DeployKind::BuildOnly).await; let xdr_base64_sim_only = deploy_contract(sandbox, HELLO_WORLD, DeployKind::SimOnly).await; - println!("{xdr_base64_build_only}"); let cmd = tx::simulate::Cmd::default(); let tx_env = TransactionEnvelope::from_xdr_base64(&xdr_base64_build_only, Limits::none()).unwrap(); - let TransactionEnvelope::Tx(TransactionV1Envelope { tx, .. }) = &tx_env else { - panic!("Only transaction v1 is supported") - }; - let assembled = cmd.simulate(tx, &sandbox.client()).await.unwrap(); + let tx = soroban_cli::commands::tx::xdr::unwrap_envelope_v1(tx_env).unwrap(); let assembled_str = sandbox .new_assert_cmd("tx") .arg("simulate") @@ -26,13 +22,9 @@ async fn txn_simulate() { .assert() .success() .stdout_as_str(); - println!("{assembled_str}"); assert_eq!(xdr_base64_sim_only, assembled_str); - let txn_env = TransactionEnvelope::Tx(TransactionV1Envelope { - tx: assembled.transaction().clone(), - signatures: VecM::default(), - }); - + let assembled = cmd.simulate(tx, &sandbox.client()).await.unwrap(); + let txn_env: TransactionEnvelope = assembled.transaction().clone().into(); assert_eq!( txn_env.to_xdr_base64(Limits::none()).unwrap(), assembled_str diff --git a/cmd/soroban-cli/src/commands/tx/simulate.rs b/cmd/soroban-cli/src/commands/tx/simulate.rs index 69b2ad4fa..58be37deb 100644 --- a/cmd/soroban-cli/src/commands/tx/simulate.rs +++ b/cmd/soroban-cli/src/commands/tx/simulate.rs @@ -1,4 +1,4 @@ -use crate::xdr::{self, Transaction, TransactionEnvelope, TransactionV1Envelope, VecM, WriteXdr}; +use crate::xdr::{self, TransactionEnvelope, WriteXdr}; use async_trait::async_trait; use soroban_rpc::Assembled; @@ -30,25 +30,10 @@ impl Cmd { let res = self .run_against_rpc_server(Some(global_args), Some(&self.config)) .await?; - let tx = res.transaction().clone(); - println!( - "{}", - TransactionEnvelope::Tx(TransactionV1Envelope { - tx, - signatures: VecM::default() - }) - .to_xdr_base64(xdr::Limits::none())? - ); + let tx_env: TransactionEnvelope = res.transaction().clone().into(); + println!("{}", tx_env.to_xdr_base64(xdr::Limits::none())?); Ok(()) } - - pub async fn simulate( - &self, - tx: &Transaction, - client: &crate::rpc::Client, - ) -> Result { - Ok(client.simulate_and_assemble_transaction(tx).await?) - } } #[async_trait] @@ -64,7 +49,7 @@ impl NetworkRunnable for Cmd { let config = config.unwrap_or(&self.config); let network = config.get_network()?; let client = crate::rpc::Client::new(&network.rpc_url)?; - let tx = super::xdr::unwrap_envelope_v1()?; - self.simulate(&tx, &client).await + let tx = super::xdr::unwrap_envelope_v1(super::xdr::tx_envelope_from_stdin()?)?; + Ok(client.simulate_and_assemble_transaction(&tx).await?) } } diff --git a/cmd/soroban-cli/src/commands/tx/xdr.rs b/cmd/soroban-cli/src/commands/tx/xdr.rs index c433a7002..71bd59af6 100644 --- a/cmd/soroban-cli/src/commands/tx/xdr.rs +++ b/cmd/soroban-cli/src/commands/tx/xdr.rs @@ -31,9 +31,8 @@ pub fn from_stdin() -> Result { T::from_xdr_base64(buf.trim(), Limits::none()).map_err(|_| Error::StdinDecode) } -pub fn unwrap_envelope_v1() -> Result { - let TransactionEnvelope::Tx(TransactionV1Envelope { tx, .. }) = tx_envelope_from_stdin()? - else { +pub fn unwrap_envelope_v1(tx_env: TransactionEnvelope) -> Result { + let TransactionEnvelope::Tx(TransactionV1Envelope { tx, .. }) = tx_env else { return Err(Error::OnlyTransactionV1Supported); }; Ok(tx) From 348d6476f53faf91c3eacb9f73a8f7292e3e164d Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Fri, 21 Jun 2024 15:14:12 -0400 Subject: [PATCH 29/30] fix: remove inspect.rs --- cmd/soroban-cli/src/commands/tx/inspect.rs | 31 ---------------------- 1 file changed, 31 deletions(-) delete mode 100644 cmd/soroban-cli/src/commands/tx/inspect.rs diff --git a/cmd/soroban-cli/src/commands/tx/inspect.rs b/cmd/soroban-cli/src/commands/tx/inspect.rs deleted file mode 100644 index a49438ed9..000000000 --- a/cmd/soroban-cli/src/commands/tx/inspect.rs +++ /dev/null @@ -1,31 +0,0 @@ -use soroban_rpc::GetTransactionResponse; -use stellar_xdr::cli as xdr_cli; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - XdrCli(#[from] xdr_cli::Error), -} - -#[derive(Debug, clap::Parser, Clone)] -#[group(skip)] -pub struct Cmd { - #[clap(flatten)] - pub xdr_args: xdr_cli::Root, - -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - let response = self.send().await?; - println!("{}", serde_json::to_string_pretty(&response)?); - Ok(()) - } - - pub async fn send(&self) -> Result { - let txn_env = self.xdr_args.txn_envelope()?; - let network = self.config.get_network()?; - let client = crate::rpc::Client::new(&network.rpc_url)?; - Ok(client.send_transaction(&txn_env).await?) - } -} From 61907ca12cc086e3835de010bd4814d66e46233f Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Fri, 21 Jun 2024 15:22:31 -0400 Subject: [PATCH 30/30] fix: update tests --- cmd/crates/soroban-test/tests/it/integration/tx.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/crates/soroban-test/tests/it/integration/tx.rs b/cmd/crates/soroban-test/tests/it/integration/tx.rs index d467e8e1c..9f5204a8d 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx.rs @@ -1,7 +1,4 @@ -use soroban_cli::commands::tx; -use soroban_sdk::xdr::{ - Limits, ReadXdr, TransactionEnvelope, TransactionV1Envelope, VecM, WriteXdr, -}; +use soroban_sdk::xdr::{Limits, ReadXdr, TransactionEnvelope, WriteXdr}; use soroban_test::{AssertExt, TestEnv}; use crate::integration::util::{deploy_contract, DeployKind, HELLO_WORLD}; @@ -11,7 +8,6 @@ async fn txn_simulate() { let sandbox = &TestEnv::new(); let xdr_base64_build_only = deploy_contract(sandbox, HELLO_WORLD, DeployKind::BuildOnly).await; let xdr_base64_sim_only = deploy_contract(sandbox, HELLO_WORLD, DeployKind::SimOnly).await; - let cmd = tx::simulate::Cmd::default(); let tx_env = TransactionEnvelope::from_xdr_base64(&xdr_base64_build_only, Limits::none()).unwrap(); let tx = soroban_cli::commands::tx::xdr::unwrap_envelope_v1(tx_env).unwrap(); @@ -23,7 +19,11 @@ async fn txn_simulate() { .success() .stdout_as_str(); assert_eq!(xdr_base64_sim_only, assembled_str); - let assembled = cmd.simulate(tx, &sandbox.client()).await.unwrap(); + let assembled = sandbox + .client() + .simulate_and_assemble_transaction(&tx) + .await + .unwrap(); let txn_env: TransactionEnvelope = assembled.transaction().clone().into(); assert_eq!( txn_env.to_xdr_base64(Limits::none()).unwrap(),