diff --git a/cmd/crates/soroban-test/src/lib.rs b/cmd/crates/soroban-test/src/lib.rs index 9ac8841937..bdff53c8a9 100644 --- a/cmd/crates/soroban-test/src/lib.rs +++ b/cmd/crates/soroban-test/src/lib.rs @@ -235,21 +235,24 @@ 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, - }), - 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, + }), + Some(&config), + ) + .await? + .res() + .unwrap()) } /// Reference to current directory of the `TestEnv`. diff --git a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs index 138bf6a670..c79ccc3616 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs @@ -12,7 +12,7 @@ use std::convert::Infallible; use std::{array::TryFromSliceError, fmt::Debug, num::ParseIntError}; use crate::{ - commands::{config, global, NetworkRunnable}, + commands::{config, global, txn_result::TxnResult, NetworkRunnable}, rpc::{Client, Error as SorobanRpcError}, utils::{contract_id_hash_from_asset, parsing::parse_asset}, }; @@ -73,7 +73,7 @@ impl NetworkRunnable for Cmd { &self, _: 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)?; @@ -101,14 +101,18 @@ impl NetworkRunnable for Cmd { network_passphrase, &key, )?; - self.fee.exit_if_build_only(&tx)?; + 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)?; client .send_assembled_transaction(txn, &key, &[], network_passphrase, None, None) .await?; - 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 95b27b470d..d88e8183c0 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -16,7 +16,9 @@ use soroban_env_host::{ use crate::commands::{ contract::{self, id::wasm::get_contract_id}, - global, NetworkRunnable, + global, + txn_result::{self, TxnResult}, + NetworkRunnable, }; use crate::{ commands::{config, contract::install, HEADING_RPC}, @@ -91,6 +93,8 @@ pub enum Error { Infallible(#[from] std::convert::Infallible), #[error(transparent)] WasmId(#[from] contract::id::wasm::Error), + #[error(transparent)] + TxnResult(#[from] txn_result::Error), } impl Cmd { @@ -110,7 +114,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 wasm_hash = if let Some(wasm) = &self.wasm { let mut fee = self.fee.clone(); @@ -123,7 +127,7 @@ impl NetworkRunnable for Cmd { } .run_against_rpc_server(global_args, Some(config)) .await?; - hex::encode(hash) + hex::encode(hash.try_res()?) } else { self.wasm_hash .as_ref() @@ -166,13 +170,18 @@ impl NetworkRunnable for Cmd { salt, &key, )?; - self.fee.exit_if_build_only(&txn)?; + 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)?; client .send_assembled_transaction(txn, &key, &[], &network.network_passphrase, None, None) .await?; - 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 237065158b..1820226bcf 100644 --- a/cmd/soroban-cli/src/commands/contract/extend.rs +++ b/cmd/soroban-cli/src/commands/contract/extend.rs @@ -9,7 +9,7 @@ use soroban_env_host::xdr::{ }; use crate::{ - commands::{config, global, NetworkRunnable}, + commands::{config, global, txn_result::TxnResult, NetworkRunnable}, key, rpc::{self, Client}, wasm, Pwd, @@ -80,7 +80,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 { @@ -110,7 +114,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); @@ -154,7 +158,9 @@ impl NetworkRunnable for Cmd { resource_fee: 0, }), }; - self.fee.exit_if_build_only(&tx)?; + 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?; @@ -184,7 +190,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)); } } @@ -199,7 +205,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 eefb1b4b89..5b223e71ad 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 afaf6f4816..5b126bb3fa 100644 --- a/cmd/soroban-cli/src/commands/contract/install.rs +++ b/cmd/soroban-cli/src/commands/contract/install.rs @@ -10,6 +10,7 @@ use soroban_env_host::xdr::{ }; use super::restore; +use crate::commands::txn_result::TxnResult; use crate::commands::{global, NetworkRunnable}; use crate::key; use crate::rpc::{self, Client}; @@ -66,7 +67,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(()) } @@ -80,7 +84,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()?; @@ -119,12 +123,14 @@ impl NetworkRunnable for Cmd { let (tx_without_preflight, hash) = build_install_contract_code_tx(&contract, sequence + 1, self.fee.fee, &key)?; - self.fee.exit_if_build_only(&tx_without_preflight)?; + 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?; if !contract_data.entries.unwrap_or_default().is_empty() { - return Ok(hash); + return Ok(TxnResult::Res(hash)); } let txn = client .create_assembled_transaction(&tx_without_preflight) @@ -160,7 +166,7 @@ impl NetworkRunnable for Cmd { .await?; } - 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 0216176ca0..e9e67279a6 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -28,6 +28,7 @@ use super::super::{ config::{self, locator}, events, }; +use crate::commands::txn_result::TxnResult; use crate::commands::NetworkRunnable; use crate::{commands::global, rpc, Pwd}; use soroban_spec_tools::{contract, Spec}; @@ -263,7 +264,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 } @@ -300,7 +301,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); @@ -334,7 +335,9 @@ impl NetworkRunnable for Cmd { self.fee.fee, &key, )?; - self.fee.exit_if_build_only(&tx)?; + 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 (return_value, events) = if self.is_view() { @@ -380,7 +383,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 @@ -391,7 +398,7 @@ pub fn output_to_string(spec: &Spec, res: &ScVal, function: &str) -> Result 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 7ff6e45aa7..eca84f0bb0 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, locator}, contract::extend, - global, NetworkRunnable, + global, + txn_result::{self, TxnResult}, + NetworkRunnable, }, key, rpc::{self, Client}, @@ -83,13 +85,21 @@ pub enum Error { Key(#[from] key::Error), #[error(transparent)] Extend(#[from] extend::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(), @@ -117,7 +127,7 @@ 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); @@ -158,7 +168,9 @@ impl NetworkRunnable for Cmd { resource_fee: 0, }), }; - self.fee.exit_if_build_only(&tx)?; + 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?; @@ -192,7 +204,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 42145f5bfd..f1707f3499 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 fc052fee07..71303102fa 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. @@ -169,5 +171,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 0000000000..f69f549bef --- /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 0525848c9b..f6ac76d58f 100644 --- a/cmd/soroban-cli/src/fee.rs +++ b/cmd/soroban-cli/src/fee.rs @@ -26,14 +26,6 @@ pub struct Args { } impl Args { - pub fn exit_if_build_only(&self, txn: &xdr::Transaction) -> Result<(), xdr::Error> { - if self.build_only { - println!("{}", txn.to_xdr_base64(xdr::Limits::none())?); - std::process::exit(0); - } - Ok(()) - } - pub fn apply_to_assembled_txn(&self, txn: Assembled) -> Result { let simulated_txn = if let Some(instructions) = self.instructions { txn.set_max_instructions(instructions) diff --git a/docs/soroban-cli-full-docs.md b/docs/soroban-cli-full-docs.md index 423b92ff81..479af87b5c 100644 --- a/docs/soroban-cli-full-docs.md +++ b/docs/soroban-cli-full-docs.md @@ -583,7 +583,7 @@ Deploy builtin Soroban Asset Contract Possible values: `true`, `false` -* `--sim-only` — Simulation the transaction only write the base64 to stdout +* `--sim-only` — Simulation the transaction only write the base64 xdr to stdout Possible values: `true`, `false` @@ -742,7 +742,7 @@ If no keys are specified the contract itself is extended. Possible values: `true`, `false` -* `--sim-only` — Simulation the transaction only write the base64 to stdout +* `--sim-only` — Simulation the transaction only write the base64 xdr to stdout Possible values: `true`, `false` @@ -782,7 +782,7 @@ Deploy a wasm contract Possible values: `true`, `false` -* `--sim-only` — Simulation the transaction only write the base64 to stdout +* `--sim-only` — Simulation the transaction only write the base64 xdr to stdout Possible values: `true`, `false` @@ -954,7 +954,7 @@ Install a WASM file to the ledger without creating a contract instance Possible values: `true`, `false` -* `--sim-only` — Simulation the transaction only write the base64 to stdout +* `--sim-only` — Simulation the transaction only write the base64 xdr to stdout Possible values: `true`, `false` @@ -1011,7 +1011,7 @@ soroban contract invoke ... -- --help Possible values: `true`, `false` -* `--sim-only` — Simulation the transaction only write the base64 to stdout +* `--sim-only` — Simulation the transaction only write the base64 xdr to stdout Possible values: `true`, `false` @@ -1131,7 +1131,7 @@ If no keys are specificed the contract itself is restored. Possible values: `true`, `false` -* `--sim-only` — Simulation the transaction only write the base64 to stdout +* `--sim-only` — Simulation the transaction only write the base64 xdr to stdout Possible values: `true`, `false` @@ -1428,7 +1428,7 @@ Deploy a token contract to wrap an existing Stellar classic asset for smart cont Possible values: `true`, `false` -* `--sim-only` — Simulation the transaction only write the base64 to stdout +* `--sim-only` — Simulation the transaction only write the base64 xdr to stdout Possible values: `true`, `false`