diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_type/src/lib.rs b/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_type/src/lib.rs index 9e4b442b53..1f9c0e0f0f 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_type/src/lib.rs +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_type/src/lib.rs @@ -1,4 +1,5 @@ #![no_std] +#![allow(clippy::ignored_unit_patterns)] use soroban_sdk::{ contract, contracterror, contractimpl, contracttype, symbol_short, vec, Address, Bytes, BytesN, Env, Map, String, Symbol, Val, Vec, I256, U256, diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/udt/src/lib.rs b/cmd/crates/soroban-test/tests/fixtures/test-wasms/udt/src/lib.rs index 695d8a7a3c..b0cd04c63c 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/udt/src/lib.rs +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/udt/src/lib.rs @@ -1,4 +1,5 @@ #![no_std] +#![allow(clippy::ignored_unit_patterns)] use soroban_sdk::{contract, contractimpl, contracttype, Vec}; #[contracttype] diff --git a/cmd/soroban-cli/src/commands/contract/deploy.rs b/cmd/soroban-cli/src/commands/contract/deploy.rs index 5467b992a2..79365bc858 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy.rs @@ -155,7 +155,15 @@ impl Cmd { &key, )?; client - .prepare_and_send_transaction(&tx, &key, &[], &network.network_passphrase, None, None) + .prepare_and_send_transaction( + &tx, + &key, + &[], + &network.network_passphrase, + None, + None, + true, + ) .await?; Ok(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 7e9f1e98ca..971dc83a3b 100644 --- a/cmd/soroban-cli/src/commands/contract/extend.rs +++ b/cmd/soroban-cli/src/commands/contract/extend.rs @@ -144,15 +144,27 @@ impl Cmd { }), }; - let (result, meta, events) = client - .prepare_and_send_transaction(&tx, &key, &[], &network.network_passphrase, None, None) + let res = client + .prepare_and_send_transaction( + &tx, + &key, + &[], + &network.network_passphrase, + None, + None, + true, + ) .await?; - tracing::trace!(?result); - tracing::trace!(?meta); + let events = res.events()?; if !events.is_empty() { tracing::info!("Events:\n {events:#?}"); } + let meta = res + .as_signed()? + .result_meta + .as_ref() + .ok_or(Error::MissingOperationResult)?; // The transaction from core will succeed regardless of whether it actually found & extended // the entry, so we have to inspect the result meta to tell if it worked or not. diff --git a/cmd/soroban-cli/src/commands/contract/install.rs b/cmd/soroban-cli/src/commands/contract/install.rs index ea5719140f..d894b20473 100644 --- a/cmd/soroban-cli/src/commands/contract/install.rs +++ b/cmd/soroban-cli/src/commands/contract/install.rs @@ -105,14 +105,10 @@ impl Cmd { build_install_contract_code_tx(contract, sequence + 1, self.fee.fee, &key)?; // Currently internal errors are not returned if the contract code is expired - if let ( - TransactionResult { - result: TransactionResultResult::TxInternalError, - .. - }, - _, - _, - ) = client + if let Some(TransactionResult { + result: TransactionResultResult::TxInternalError, + .. + }) = client .prepare_and_send_transaction( &tx_without_preflight, &key, @@ -120,8 +116,11 @@ impl Cmd { &network.network_passphrase, None, None, + true, ) .await? + .as_signed()? + .result { // Now just need to restore it and don't have to install again restore::Cmd { diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 2d4c77c61b..bf2cbb107c 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -300,7 +300,7 @@ impl Cmd { &key, )?; - let (result, meta, events) = client + let res = client .prepare_and_send_transaction( &tx, &key, @@ -309,20 +309,11 @@ impl Cmd { Some(log_events), (global_args.verbose || global_args.very_verbose || self.cost) .then_some(log_resources), + false, ) .await?; - - tracing::debug!(?result); - crate::log::diagnostic_events(&events, tracing::Level::INFO); - let xdr::TransactionMeta::V3(xdr::TransactionMetaV3 { - soroban_meta: Some(xdr::SorobanTransactionMeta { return_value, .. }), - .. - }) = meta - else { - return Err(Error::MissingOperationResult); - }; - - output_to_string(&spec, &return_value, &function) + crate::log::diagnostic_events(&res.events()?, tracing::Level::INFO); + output_to_string(&spec, &res.return_value()?, &function) } pub fn read_wasm(&self) -> Result>, Error> { diff --git a/cmd/soroban-cli/src/commands/contract/restore.rs b/cmd/soroban-cli/src/commands/contract/restore.rs index fb7f560fae..1cb6898b12 100644 --- a/cmd/soroban-cli/src/commands/contract/restore.rs +++ b/cmd/soroban-cli/src/commands/contract/restore.rs @@ -148,11 +148,24 @@ impl Cmd { }), }; - let (result, meta, events) = client - .prepare_and_send_transaction(&tx, &key, &[], &network.network_passphrase, None, None) + let res = client + .prepare_and_send_transaction( + &tx, + &key, + &[], + &network.network_passphrase, + None, + None, + true, + ) .await?; - tracing::trace!(?result); + let meta = res + .as_signed()? + .result_meta + .as_ref() + .ok_or(Error::MissingOperationResult)?; + let events = res.events()?; tracing::trace!(?meta); if !events.is_empty() { tracing::info!("Events:\n {events:#?}"); @@ -177,7 +190,7 @@ impl Cmd { operations[0].changes.len() ); } - parse_operations(&operations).ok_or(Error::MissingOperationResult) + parse_operations(operations).ok_or(Error::MissingOperationResult) } } diff --git a/cmd/soroban-cli/src/commands/lab/token/wrap.rs b/cmd/soroban-cli/src/commands/lab/token/wrap.rs index 2b6f4dd4f5..db5a91ea00 100644 --- a/cmd/soroban-cli/src/commands/lab/token/wrap.rs +++ b/cmd/soroban-cli/src/commands/lab/token/wrap.rs @@ -92,7 +92,7 @@ impl Cmd { )?; client - .prepare_and_send_transaction(&tx, &key, &[], network_passphrase, None, None) + .prepare_and_send_transaction(&tx, &key, &[], network_passphrase, None, None, true) .await?; Ok(stellar_strkey::Contract(contract_id.0).to_string()) diff --git a/cmd/soroban-cli/src/rpc/mod.rs b/cmd/soroban-cli/src/rpc/mod.rs index d9fe541a31..c7672c89bf 100644 --- a/cmd/soroban-cli/src/rpc/mod.rs +++ b/cmd/soroban-cli/src/rpc/mod.rs @@ -99,6 +99,11 @@ pub enum Error { LargeFee(u64), #[error("Cannot authorize raw transactions")] CannotAuthorizeRawTransaction, + + #[error("Missing result for tnx")] + MissingOp, + #[error("A simulation is not a transaction")] + NotSignedTransaction, } #[derive(serde::Deserialize, serde::Serialize, Debug)] @@ -623,10 +628,7 @@ soroban config identity fund {address} --helper-url "# } } - pub async fn send_transaction( - &self, - tx: &TransactionEnvelope, - ) -> Result<(TransactionResult, TransactionMeta, Vec), Error> { + pub async fn send_transaction(&self, tx: &TransactionEnvelope) -> Result { let client = self.client()?; tracing::trace!("Sending:\n{tx:#?}"); let SendTransactionResponse { @@ -665,14 +667,8 @@ soroban config identity fund {address} --helper-url "# "SUCCESS" => { // TODO: the caller should probably be printing this tracing::trace!("{response:#?}"); - let GetTransactionResponse { - result, - result_meta, - .. - } = response; - let meta = result_meta.ok_or(Error::MissingResult)?; - let events = extract_events(&meta); - return Ok((result.ok_or(Error::MissingResult)?, meta, events)); + + return Ok(txn::Finished::signed(response)); } "FAILED" => { tracing::error!("{response:#?}"); @@ -716,6 +712,7 @@ soroban config identity fund {address} --helper-url "# } } + #[allow(clippy::too_many_arguments)] pub async fn prepare_and_send_transaction( &self, tx_without_preflight: &Transaction, @@ -724,7 +721,8 @@ soroban config identity fund {address} --helper-url "# network_passphrase: &str, log_events: Option, log_resources: Option, - ) -> Result<(TransactionResult, TransactionMeta, Vec), Error> { + always_submit: bool, + ) -> Result { let txn = txn::Assembled::new(tx_without_preflight, self).await?; let seq_num = txn.sim_res().latest_ledger + 60; //5 min; let authorized = txn @@ -733,8 +731,12 @@ soroban config identity fund {address} --helper-url "# .authorize(self, source_key, signers, seq_num, network_passphrase) .await?; authorized.log(log_events, log_resources)?; - let tx = authorized.sign(source_key, network_passphrase)?; - self.send_transaction(&tx).await + if always_submit || authorized.requires_auth() { + let tx = authorized.sign(source_key, network_passphrase)?; + self.send_transaction(&tx).await + } else { + Ok(authorized.finish_simulation()) + } } pub async fn get_transaction(&self, tx_id: &str) -> Result { diff --git a/cmd/soroban-cli/src/rpc/txn.rs b/cmd/soroban-cli/src/rpc/txn.rs index 32cda06335..9adaf8fd2c 100644 --- a/cmd/soroban-cli/src/rpc/txn.rs +++ b/cmd/soroban-cli/src/rpc/txn.rs @@ -14,6 +14,9 @@ use crate::rpc::{Client, Error, RestorePreamble, SimulateTransactionResponse}; use super::{LogEvents, LogResources}; +mod finished; +pub use finished::*; + pub struct Assembled { txn: Transaction, sim_res: SimulateTransactionResponse, @@ -155,6 +158,14 @@ impl Assembled { } Ok(()) } + + pub fn requires_auth(&self) -> bool { + requires_auth(&self.txn) + } + + pub fn finish_simulation(self) -> Finished { + Finished::simulated(self.sim_res) + } } // Apply the result of a simulateTransaction onto a transaction envelope, preparing it for @@ -219,6 +230,20 @@ pub fn assemble( Ok(tx) } +fn requires_auth(txn: &Transaction) -> bool { + let [Operation { + body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { auth, .. }), + .. + }] = txn.operations.as_slice() + else { + return false; + }; + matches!( + auth.get(0).map(|x| &x.root_invocation.function), + Some(&SorobanAuthorizedFunction::ContractFn(_)) + ) +} + // Use the given source_key and signers, to sign all SorobanAuthorizationEntry's in the given // transaction. If unable to sign, return an error. fn sign_soroban_authorizations( @@ -229,18 +254,10 @@ fn sign_soroban_authorizations( network_passphrase: &str, ) -> Result, Error> { let mut tx = raw.clone(); - let mut op = match tx.operations.as_slice() { - [op @ Operation { - body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { auth, .. }), - .. - }] if matches!( - auth.get(0).map(|x| &x.root_invocation.function), - Some(&SorobanAuthorizedFunction::ContractFn(_)) - ) => - { - op.clone() - } - _ => return Ok(None), + let mut op = if requires_auth(&tx) { + tx.operations[0].clone() + } else { + return Ok(None); }; let Operation { diff --git a/cmd/soroban-cli/src/rpc/txn/finished.rs b/cmd/soroban-cli/src/rpc/txn/finished.rs new file mode 100644 index 0000000000..13dfeebe78 --- /dev/null +++ b/cmd/soroban-cli/src/rpc/txn/finished.rs @@ -0,0 +1,66 @@ +use soroban_env_host::xdr::{self, DiagnosticEvent, ScVal}; + +use super::Error; +use crate::rpc::{extract_events, GetTransactionResponse, SimulateTransactionResponse}; + +pub enum Kind { + Simulated(SimulateTransactionResponse), + Signed(GetTransactionResponse), +} + +pub struct Finished { + txn_res: Kind, +} + +impl Finished { + pub fn simulated(txn_res: SimulateTransactionResponse) -> Self { + Self { + txn_res: Kind::Simulated(txn_res), + } + } + + pub fn signed(txn_res: GetTransactionResponse) -> Self { + Self { + txn_res: Kind::Signed(txn_res), + } + } + + pub fn return_value(&self) -> Result { + match &self.txn_res { + Kind::Simulated(sim_res) => Ok(sim_res + .results()? + .get(0) + .ok_or(Error::MissingOp)? + .xdr + .clone()), + Kind::Signed(GetTransactionResponse { + result_meta: + Some(xdr::TransactionMeta::V3(xdr::TransactionMetaV3 { + soroban_meta: Some(xdr::SorobanTransactionMeta { return_value, .. }), + .. + })), + .. + }) => Ok(return_value.clone()), + Kind::Signed(_) => Err(Error::MissingOp), + } + } + + pub fn events(&self) -> Result, Error> { + match &self.txn_res { + Kind::Simulated(sim_res) => sim_res.events(), + Kind::Signed(GetTransactionResponse { + result_meta: Some(meta), + .. + }) => Ok(extract_events(meta)), + Kind::Signed(_) => Err(Error::MissingOp), + } + } + + pub fn as_signed(&self) -> Result<&GetTransactionResponse, Error> { + if let Kind::Signed(res) = &self.txn_res { + Ok(res) + } else { + Err(Error::NotSignedTransaction) + } + } +}