diff --git a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs index 138bf6a670..85f4d6d930 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs @@ -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..75b61922b7 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -110,7 +110,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 +123,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 +166,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..383f5ed676 100644 --- a/cmd/soroban-cli/src/commands/contract/extend.rs +++ b/cmd/soroban-cli/src/commands/contract/extend.rs @@ -110,7 +110,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 +154,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?; @@ -199,7 +201,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/install.rs b/cmd/soroban-cli/src/commands/contract/install.rs index afaf6f4816..c630b5a1cc 100644 --- a/cmd/soroban-cli/src/commands/contract/install.rs +++ b/cmd/soroban-cli/src/commands/contract/install.rs @@ -119,12 +119,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) diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 0216176ca0..28f1083000 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}; @@ -258,12 +259,12 @@ impl Cmd { } pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { - let res = self.invoke(global_args).await?; + let res = self.invoke(global_args).await?; println!("{res}"); 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() { diff --git a/cmd/soroban-cli/src/commands/contract/read.rs b/cmd/soroban-cli/src/commands/contract/read.rs index a7b1d07a8b..1d23e5c6ab 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 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..96fdb3215e 100644 --- a/cmd/soroban-cli/src/commands/events.rs +++ b/cmd/soroban-cli/src/commands/events.rs @@ -124,6 +124,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 +169,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 +217,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()?) 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)