From d37632aea26b68cfce22bb2d31e16727c56629d4 Mon Sep 17 00:00:00 2001 From: Abeeujah <100226788+Abeeujah@users.noreply.github.com> Date: Fri, 4 Oct 2024 13:28:56 +0100 Subject: [PATCH 1/4] feat: Bring colors to help message (#1650) Co-authored-by: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> --- cmd/soroban-cli/src/commands/global.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/cmd/soroban-cli/src/commands/global.rs b/cmd/soroban-cli/src/commands/global.rs index 9148e30cc4..be883b6fdc 100644 --- a/cmd/soroban-cli/src/commands/global.rs +++ b/cmd/soroban-cli/src/commands/global.rs @@ -1,11 +1,24 @@ -use clap::arg; +use clap::{ + arg, + builder::styling::{AnsiColor, Effects, Styles}, +}; use std::path::PathBuf; use super::config; +const USAGE_STYLES: Styles = Styles::styled() + .header(AnsiColor::Green.on_default().effects(Effects::BOLD)) + .usage(AnsiColor::Green.on_default().effects(Effects::BOLD)) + .literal(AnsiColor::Cyan.on_default().effects(Effects::BOLD)) + .placeholder(AnsiColor::Cyan.on_default().effects(Effects::BOLD)) + .error(AnsiColor::Red.on_default().effects(Effects::BOLD)) + .valid(AnsiColor::Cyan.on_default().effects(Effects::BOLD)) + .invalid(AnsiColor::Yellow.on_default().effects(Effects::BOLD)); + #[derive(Debug, clap::Args, Clone, Default)] #[group(skip)] #[allow(clippy::struct_excessive_bools)] +#[command(styles = USAGE_STYLES)] pub struct Args { #[clap(flatten)] pub locator: config::locator::Args, From b940978a5a61fa7ddfcb75235cc2e5796f26c22e Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Sat, 5 Oct 2024 01:29:02 +1000 Subject: [PATCH 2/4] Add Assembled from stellar-rpc-client@9f74833 (#1648) --- .../soroban-test/tests/it/integration/tx.rs | 5 +- cmd/soroban-cli/src/assembled.rs | 541 ++++++++++++++++++ .../src/commands/contract/deploy/asset.rs | 3 +- .../src/commands/contract/deploy/wasm.rs | 3 +- .../src/commands/contract/extend.rs | 4 +- .../src/commands/contract/install.rs | 5 +- .../src/commands/contract/invoke.rs | 3 +- cmd/soroban-cli/src/commands/tx/simulate.rs | 9 +- cmd/soroban-cli/src/fee.rs | 2 +- cmd/soroban-cli/src/lib.rs | 1 + 10 files changed, 561 insertions(+), 15 deletions(-) create mode 100644 cmd/soroban-cli/src/assembled.rs diff --git a/cmd/crates/soroban-test/tests/it/integration/tx.rs b/cmd/crates/soroban-test/tests/it/integration/tx.rs index 66c4b69fed..5cc6bdb9b0 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx.rs @@ -1,3 +1,4 @@ +use soroban_cli::assembled::simulate_and_assemble_transaction; use soroban_sdk::xdr::{Limits, ReadXdr, TransactionEnvelope, WriteXdr}; use soroban_test::{AssertExt, TestEnv}; @@ -23,9 +24,7 @@ async fn simulate() { .success() .stdout_as_str(); assert_eq!(xdr_base64_sim_only, assembled_str); - let assembled = sandbox - .client() - .simulate_and_assemble_transaction(&tx) + let assembled = simulate_and_assemble_transaction(&sandbox.client(), &tx) .await .unwrap(); let txn_env: TransactionEnvelope = assembled.transaction().clone().into(); diff --git a/cmd/soroban-cli/src/assembled.rs b/cmd/soroban-cli/src/assembled.rs new file mode 100644 index 0000000000..312863d525 --- /dev/null +++ b/cmd/soroban-cli/src/assembled.rs @@ -0,0 +1,541 @@ +use sha2::{Digest, Sha256}; +use stellar_xdr::curr::{ + self as xdr, ExtensionPoint, Hash, InvokeHostFunctionOp, LedgerFootprint, Limits, Memo, + Operation, OperationBody, Preconditions, ReadXdr, RestoreFootprintOp, + SorobanAuthorizationEntry, SorobanAuthorizedFunction, SorobanResources, SorobanTransactionData, + Transaction, TransactionEnvelope, TransactionExt, TransactionSignaturePayload, + TransactionSignaturePayloadTaggedTransaction, TransactionV1Envelope, VecM, WriteXdr, +}; + +use soroban_rpc::{Error, RestorePreamble, SimulateTransactionResponse}; + +use soroban_rpc::{LogEvents, LogResources}; + +pub(crate) const DEFAULT_TRANSACTION_FEES: u32 = 100; + +pub async fn simulate_and_assemble_transaction( + client: &soroban_rpc::Client, + tx: &Transaction, +) -> Result { + let sim_res = client + .simulate_transaction_envelope(&TransactionEnvelope::Tx(TransactionV1Envelope { + tx: tx.clone(), + signatures: VecM::default(), + })) + .await?; + match sim_res.error { + None => Ok(Assembled::new(tx, sim_res)?), + Some(e) => { + diagnostic_events(&sim_res.events, tracing::Level::ERROR); + Err(Error::TransactionSimulationFailed(e)) + } + } +} + +pub struct Assembled { + pub(crate) txn: Transaction, + pub(crate) sim_res: SimulateTransactionResponse, +} + +/// Represents an assembled transaction ready to be signed and submitted to the network. +impl Assembled { + /// + /// Creates a new `Assembled` transaction. + /// + /// # Arguments + /// + /// * `txn` - The original transaction. + /// * `client` - The client used for simulation and submission. + /// + /// # Errors + /// + /// Returns an error if simulation fails or if assembling the transaction fails. + pub fn new(txn: &Transaction, sim_res: SimulateTransactionResponse) -> Result { + let txn = assemble(txn, &sim_res)?; + Ok(Self { txn, sim_res }) + } + + /// + /// Calculates the hash of the assembled transaction. + /// + /// # Arguments + /// + /// * `network_passphrase` - The network passphrase. + /// + /// # Errors + /// + /// Returns an error if generating the hash fails. + pub fn hash(&self, network_passphrase: &str) -> Result<[u8; 32], xdr::Error> { + let signature_payload = TransactionSignaturePayload { + network_id: Hash(Sha256::digest(network_passphrase).into()), + tagged_transaction: TransactionSignaturePayloadTaggedTransaction::Tx(self.txn.clone()), + }; + Ok(Sha256::digest(signature_payload.to_xdr(Limits::none())?).into()) + } + + /// Create a transaction for restoring any data in the `restore_preamble` field of the `SimulateTransactionResponse`. + /// + /// # Errors + pub fn restore_txn(&self) -> Result, Error> { + if let Some(restore_preamble) = &self.sim_res.restore_preamble { + restore(self.transaction(), restore_preamble).map(Option::Some) + } else { + Ok(None) + } + } + + /// Returns a reference to the original transaction. + #[must_use] + pub fn transaction(&self) -> &Transaction { + &self.txn + } + + /// Returns a reference to the simulation response. + #[must_use] + pub fn sim_response(&self) -> &SimulateTransactionResponse { + &self.sim_res + } + + #[must_use] + pub fn bump_seq_num(mut self) -> Self { + self.txn.seq_num.0 += 1; + self + } + + /// + /// # Errors + #[must_use] + pub fn auth_entries(&self) -> VecM { + self.txn + .operations + .first() + .and_then(|op| match op.body { + OperationBody::InvokeHostFunction(ref body) => (matches!( + body.auth.first().map(|x| &x.root_invocation.function), + Some(&SorobanAuthorizedFunction::ContractFn(_)) + )) + .then_some(body.auth.clone()), + _ => None, + }) + .unwrap_or_default() + } + + /// + /// # Errors + pub fn log( + &self, + log_events: Option, + log_resources: Option, + ) -> Result<(), Error> { + if let TransactionExt::V1(SorobanTransactionData { + resources: resources @ SorobanResources { footprint, .. }, + .. + }) = &self.txn.ext + { + if let Some(log) = log_resources { + log(resources); + } + if let Some(log) = log_events { + log(footprint, &[self.auth_entries()], &self.sim_res.events()?); + }; + } + Ok(()) + } + + #[must_use] + pub fn requires_auth(&self) -> bool { + requires_auth(&self.txn).is_some() + } + + #[must_use] + pub fn is_view(&self) -> bool { + let TransactionExt::V1(SorobanTransactionData { + resources: + SorobanResources { + footprint: LedgerFootprint { read_write, .. }, + .. + }, + .. + }) = &self.txn.ext + else { + return false; + }; + read_write.is_empty() + } + + #[must_use] + pub fn set_max_instructions(mut self, instructions: u32) -> Self { + if let TransactionExt::V1(SorobanTransactionData { + resources: + SorobanResources { + instructions: ref mut i, + .. + }, + .. + }) = &mut self.txn.ext + { + tracing::trace!("setting max instructions to {instructions} from {i}"); + *i = instructions; + } + self + } +} + +// Apply the result of a simulateTransaction onto a transaction envelope, preparing it for +// submission to the network. +/// +/// # Errors +fn assemble( + raw: &Transaction, + simulation: &SimulateTransactionResponse, +) -> Result { + let mut tx = raw.clone(); + + // Right now simulate.results is one-result-per-function, and assumes there is only one + // operation in the txn, so we need to enforce that here. I (Paul) think that is a bug + // in soroban-rpc.simulateTransaction design, and we should fix it there. + // TODO: We should to better handling so non-soroban txns can be a passthrough here. + if tx.operations.len() != 1 { + return Err(Error::UnexpectedOperationCount { + count: tx.operations.len(), + }); + } + + let transaction_data = simulation.transaction_data()?; + + let mut op = tx.operations[0].clone(); + if let OperationBody::InvokeHostFunction(ref mut body) = &mut op.body { + if body.auth.is_empty() { + if simulation.results.len() != 1 { + return Err(Error::UnexpectedSimulateTransactionResultSize { + length: simulation.results.len(), + }); + } + + let auths = simulation + .results + .iter() + .map(|r| { + VecM::try_from( + r.auth + .iter() + .map(|v| SorobanAuthorizationEntry::from_xdr_base64(v, Limits::none())) + .collect::, _>>()?, + ) + }) + .collect::, _>>()?; + if !auths.is_empty() { + body.auth = auths[0].clone(); + } + } + } + + // Update transaction fees to meet the minimum resource fees. + let classic_tx_fee: u64 = DEFAULT_TRANSACTION_FEES.into(); + + // Choose larger of existing fee or inclusion + resource fee. + tx.fee = tx.fee.max( + u32::try_from(classic_tx_fee + simulation.min_resource_fee) + .map_err(|_| Error::LargeFee(simulation.min_resource_fee + classic_tx_fee))?, + ); + + tx.operations = vec![op].try_into()?; + tx.ext = TransactionExt::V1(transaction_data); + Ok(tx) +} + +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()) +} + +fn restore(parent: &Transaction, restore: &RestorePreamble) -> Result { + let transaction_data = + SorobanTransactionData::from_xdr_base64(&restore.transaction_data, Limits::none())?; + let fee = u32::try_from(restore.min_resource_fee) + .map_err(|_| Error::LargeFee(restore.min_resource_fee))?; + Ok(Transaction { + source_account: parent.source_account.clone(), + fee: parent + .fee + .checked_add(fee) + .ok_or(Error::LargeFee(restore.min_resource_fee))?, + seq_num: parent.seq_num.clone(), + cond: Preconditions::None, + memo: Memo::None, + operations: vec![Operation { + source_account: None, + body: OperationBody::RestoreFootprint(RestoreFootprintOp { + ext: ExtensionPoint::V0, + }), + }] + .try_into()?, + ext: TransactionExt::V1(transaction_data), + }) +} + +fn diagnostic_events(events: &[impl std::fmt::Debug], level: tracing::Level) { + for (i, event) in events.iter().enumerate() { + if level == tracing::Level::TRACE { + tracing::trace!("{i}: {event:#?}"); + } else if level == tracing::Level::INFO { + tracing::info!("{i}: {event:#?}"); + } else if level == tracing::Level::ERROR { + tracing::error!("{i}: {event:#?}"); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use soroban_rpc::SimulateHostFunctionResultRaw; + use stellar_strkey::ed25519::PublicKey as Ed25519PublicKey; + use stellar_xdr::curr::{ + AccountId, ChangeTrustAsset, ChangeTrustOp, ExtensionPoint, Hash, HostFunction, + InvokeContractArgs, InvokeHostFunctionOp, LedgerFootprint, Memo, MuxedAccount, Operation, + Preconditions, PublicKey, ScAddress, ScSymbol, ScVal, SequenceNumber, + SorobanAuthorizedFunction, SorobanAuthorizedInvocation, SorobanResources, + SorobanTransactionData, Uint256, WriteXdr, + }; + + const SOURCE: &str = "GBZXN7PIRZGNMHGA7MUUUF4GWPY5AYPV6LY4UV2GL6VJGIQRXFDNMADI"; + + fn transaction_data() -> SorobanTransactionData { + SorobanTransactionData { + resources: SorobanResources { + footprint: LedgerFootprint { + read_only: VecM::default(), + read_write: VecM::default(), + }, + instructions: 0, + read_bytes: 5, + write_bytes: 0, + }, + resource_fee: 0, + ext: ExtensionPoint::V0, + } + } + + fn simulation_response() -> SimulateTransactionResponse { + let source_bytes = Ed25519PublicKey::from_string(SOURCE).unwrap().0; + let fn_auth = &SorobanAuthorizationEntry { + credentials: xdr::SorobanCredentials::Address(xdr::SorobanAddressCredentials { + address: ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256( + source_bytes, + )))), + nonce: 0, + signature_expiration_ledger: 0, + signature: ScVal::Void, + }), + root_invocation: SorobanAuthorizedInvocation { + function: SorobanAuthorizedFunction::ContractFn(InvokeContractArgs { + contract_address: ScAddress::Contract(Hash([0; 32])), + function_name: ScSymbol("fn".try_into().unwrap()), + args: VecM::default(), + }), + sub_invocations: VecM::default(), + }, + }; + + SimulateTransactionResponse { + min_resource_fee: 115, + latest_ledger: 3, + results: vec![SimulateHostFunctionResultRaw { + auth: vec![fn_auth.to_xdr_base64(Limits::none()).unwrap()], + xdr: ScVal::U32(0).to_xdr_base64(Limits::none()).unwrap(), + }], + transaction_data: transaction_data().to_xdr_base64(Limits::none()).unwrap(), + ..Default::default() + } + } + + fn single_contract_fn_transaction() -> Transaction { + let source_bytes = Ed25519PublicKey::from_string(SOURCE).unwrap().0; + Transaction { + source_account: MuxedAccount::Ed25519(Uint256(source_bytes)), + fee: 100, + seq_num: SequenceNumber(0), + cond: Preconditions::None, + memo: Memo::None, + operations: vec![Operation { + source_account: None, + body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { + host_function: HostFunction::InvokeContract(InvokeContractArgs { + contract_address: ScAddress::Contract(Hash([0x0; 32])), + function_name: ScSymbol::default(), + args: VecM::default(), + }), + auth: VecM::default(), + }), + }] + .try_into() + .unwrap(), + ext: TransactionExt::V0, + } + } + + #[test] + fn test_assemble_transaction_updates_tx_data_from_simulation_response() { + let sim = simulation_response(); + let txn = single_contract_fn_transaction(); + let Ok(result) = assemble(&txn, &sim) else { + panic!("assemble failed"); + }; + + // validate it auto updated the tx fees from sim response fees + // since it was greater than tx.fee + assert_eq!(215, result.fee); + + // validate it updated sorobantransactiondata block in the tx ext + assert_eq!(TransactionExt::V1(transaction_data()), result.ext); + } + + #[test] + fn test_assemble_transaction_adds_the_auth_to_the_host_function() { + let sim = simulation_response(); + let txn = single_contract_fn_transaction(); + let Ok(result) = assemble(&txn, &sim) else { + panic!("assemble failed"); + }; + + assert_eq!(1, result.operations.len()); + let OperationBody::InvokeHostFunction(ref op) = result.operations[0].body else { + panic!("unexpected operation type: {:#?}", result.operations[0]); + }; + + assert_eq!(1, op.auth.len()); + let auth = &op.auth[0]; + + let xdr::SorobanAuthorizedFunction::ContractFn(xdr::InvokeContractArgs { + ref function_name, + .. + }) = auth.root_invocation.function + else { + panic!("unexpected function type"); + }; + assert_eq!("fn".to_string(), format!("{}", function_name.0)); + + let xdr::SorobanCredentials::Address(xdr::SorobanAddressCredentials { + address: + xdr::ScAddress::Account(xdr::AccountId(xdr::PublicKey::PublicKeyTypeEd25519(address))), + .. + }) = &auth.credentials + else { + panic!("unexpected credentials type"); + }; + assert_eq!( + SOURCE.to_string(), + stellar_strkey::ed25519::PublicKey(address.0).to_string() + ); + } + + #[test] + fn test_assemble_transaction_errors_for_non_invokehostfn_ops() { + let source_bytes = Ed25519PublicKey::from_string(SOURCE).unwrap().0; + let txn = Transaction { + source_account: MuxedAccount::Ed25519(Uint256(source_bytes)), + fee: 100, + seq_num: SequenceNumber(0), + cond: Preconditions::None, + memo: Memo::None, + operations: vec![Operation { + source_account: None, + body: OperationBody::ChangeTrust(ChangeTrustOp { + line: ChangeTrustAsset::Native, + limit: 0, + }), + }] + .try_into() + .unwrap(), + ext: TransactionExt::V0, + }; + + let result = assemble( + &txn, + &SimulateTransactionResponse { + min_resource_fee: 115, + transaction_data: transaction_data().to_xdr_base64(Limits::none()).unwrap(), + latest_ledger: 3, + ..Default::default() + }, + ); + + match result { + Ok(_) => {} + Err(e) => panic!("expected assembled operation, got: {e:#?}"), + } + } + + #[test] + fn test_assemble_transaction_errors_for_errors_for_mismatched_simulation() { + let txn = single_contract_fn_transaction(); + + let result = assemble( + &txn, + &SimulateTransactionResponse { + min_resource_fee: 115, + transaction_data: transaction_data().to_xdr_base64(Limits::none()).unwrap(), + latest_ledger: 3, + ..Default::default() + }, + ); + + match result { + Err(Error::UnexpectedSimulateTransactionResultSize { length }) => { + assert_eq!(0, length); + } + r => panic!("expected UnexpectedSimulateTransactionResultSize error, got: {r:#?}"), + } + } + + #[test] + fn test_assemble_transaction_overflow_behavior() { + // + // Test two separate cases: + // + // 1. Given a near-max (u32::MAX - 100) resource fee make sure the tx + // fee does not overflow after adding the base inclusion fee (100). + // 2. Given a large resource fee that WILL exceed u32::MAX with the + // base inclusion fee, ensure the overflow is caught with an error + // rather than silently ignored. + let txn = single_contract_fn_transaction(); + let mut response = simulation_response(); + + // sanity check so these can be adjusted if the above helper changes + assert_eq!(txn.fee, 100, "modified txn.fee: update the math below"); + + // 1: wiggle room math overflows but result fits + response.min_resource_fee = (u32::MAX - 100).into(); + + match assemble(&txn, &response) { + Ok(asstxn) => { + let expected = u32::MAX; + assert_eq!(asstxn.fee, expected); + } + r => panic!("expected success, got: {r:#?}"), + } + + // 2: combo overflows, should throw + response.min_resource_fee = (u32::MAX - 99).into(); + + match assemble(&txn, &response) { + Err(Error::LargeFee(fee)) => { + let expected = u64::from(u32::MAX) + 1; + assert_eq!(expected, fee, "expected {expected} != {fee} actual"); + } + r => panic!("expected LargeFee error, got: {r:#?}"), + } + } +} diff --git a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs index d2fcb62c4a..8ff8e08b30 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs @@ -12,6 +12,7 @@ use std::convert::Infallible; use std::{array::TryFromSliceError, fmt::Debug, num::ParseIntError}; use crate::{ + assembled::simulate_and_assemble_transaction, commands::{ global, txn_result::{TxnEnvelopeResult, TxnResult}, @@ -118,7 +119,7 @@ impl NetworkRunnable for Cmd { if self.fee.build_only { return Ok(TxnResult::Txn(tx)); } - let txn = client.simulate_and_assemble_transaction(&tx).await?; + let txn = simulate_and_assemble_transaction(&client, &tx).await?; let txn = self.fee.apply_to_assembled_txn(txn).transaction().clone(); if self.fee.sim_only { return Ok(TxnResult::Txn(txn)); diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index 42b31dd7d5..36ce2e9f7d 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -16,6 +16,7 @@ use soroban_env_host::{ }; use crate::{ + assembled::simulate_and_assemble_transaction, commands::{contract::install, HEADING_RPC}, config::{self, data, locator, network}, rpc::{self, Client}, @@ -240,7 +241,7 @@ impl NetworkRunnable for Cmd { print.infoln("Simulating deploy transaction…"); - let txn = client.simulate_and_assemble_transaction(&txn).await?; + let txn = simulate_and_assemble_transaction(&client, &txn).await?; let txn = self.fee.apply_to_assembled_txn(txn).transaction().clone(); if self.fee.sim_only { diff --git a/cmd/soroban-cli/src/commands/contract/extend.rs b/cmd/soroban-cli/src/commands/contract/extend.rs index b06cacf3e4..3f74772540 100644 --- a/cmd/soroban-cli/src/commands/contract/extend.rs +++ b/cmd/soroban-cli/src/commands/contract/extend.rs @@ -9,6 +9,7 @@ use soroban_env_host::xdr::{ }; use crate::{ + assembled::simulate_and_assemble_transaction, commands::{ global, txn_result::{TxnEnvelopeResult, TxnResult}, @@ -170,8 +171,7 @@ impl NetworkRunnable for Cmd { if self.fee.build_only { return Ok(TxnResult::Txn(tx)); } - let tx = client - .simulate_and_assemble_transaction(&tx) + let tx = simulate_and_assemble_transaction(&client, &tx) .await? .transaction() .clone(); diff --git a/cmd/soroban-cli/src/commands/contract/install.rs b/cmd/soroban-cli/src/commands/contract/install.rs index 9e8f6b0985..b70cadb784 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::assembled::simulate_and_assemble_transaction; use crate::commands::txn_result::{TxnEnvelopeResult, TxnResult}; use crate::commands::{global, NetworkRunnable}; use crate::config::{self, data, network}; @@ -177,9 +178,7 @@ impl NetworkRunnable for Cmd { print.infoln("Simulating install transaction…"); - let txn = client - .simulate_and_assemble_transaction(&tx_without_preflight) - .await?; + let txn = simulate_and_assemble_transaction(&client, &tx_without_preflight).await?; let txn = self.fee.apply_to_assembled_txn(txn).transaction().clone(); if self.fee.sim_only { diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index fa90ef9b3f..55e2554bf2 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -23,6 +23,7 @@ use soroban_spec::read::FromWasmError; use super::super::events; use super::arg_parsing; +use crate::assembled::simulate_and_assemble_transaction; use crate::commands::contract::arg_parsing::{build_host_function_parameters, output_to_string}; use crate::commands::txn_result::{TxnEnvelopeResult, TxnResult}; use crate::commands::NetworkRunnable; @@ -246,7 +247,7 @@ impl NetworkRunnable for Cmd { if self.fee.build_only { return Ok(TxnResult::Txn(tx)); } - let txn = client.simulate_and_assemble_transaction(&tx).await?; + let txn = simulate_and_assemble_transaction(&client, &tx).await?; let txn = self.fee.apply_to_assembled_txn(txn); if self.fee.sim_only { return Ok(TxnResult::Txn(txn.transaction().clone())); diff --git a/cmd/soroban-cli/src/commands/tx/simulate.rs b/cmd/soroban-cli/src/commands/tx/simulate.rs index 58be37deb2..1f534884db 100644 --- a/cmd/soroban-cli/src/commands/tx/simulate.rs +++ b/cmd/soroban-cli/src/commands/tx/simulate.rs @@ -1,6 +1,8 @@ -use crate::xdr::{self, TransactionEnvelope, WriteXdr}; +use crate::{ + assembled::{simulate_and_assemble_transaction, Assembled}, + xdr::{self, TransactionEnvelope, WriteXdr}, +}; use async_trait::async_trait; -use soroban_rpc::Assembled; use crate::commands::{config, global, NetworkRunnable}; @@ -50,6 +52,7 @@ impl NetworkRunnable for Cmd { let network = config.get_network()?; let client = crate::rpc::Client::new(&network.rpc_url)?; let tx = super::xdr::unwrap_envelope_v1(super::xdr::tx_envelope_from_stdin()?)?; - Ok(client.simulate_and_assemble_transaction(&tx).await?) + let tx = simulate_and_assemble_transaction(&client, &tx).await?; + Ok(tx) } } diff --git a/cmd/soroban-cli/src/fee.rs b/cmd/soroban-cli/src/fee.rs index 698d66007b..c2bc32bdb8 100644 --- a/cmd/soroban-cli/src/fee.rs +++ b/cmd/soroban-cli/src/fee.rs @@ -1,7 +1,7 @@ use clap::arg; +use crate::assembled::Assembled; use soroban_env_host::xdr; -use soroban_rpc::Assembled; use crate::commands::HEADING_RPC; diff --git a/cmd/soroban-cli/src/lib.rs b/cmd/soroban-cli/src/lib.rs index f5ea218844..4c904855cf 100644 --- a/cmd/soroban-cli/src/lib.rs +++ b/cmd/soroban-cli/src/lib.rs @@ -11,6 +11,7 @@ pub(crate) use soroban_rpc as rpc; mod cli; pub use cli::main; +pub mod assembled; pub mod commands; pub mod config; pub mod fee; From fb4e6bd9352251a243801ad189362ca2abc9d7c0 Mon Sep 17 00:00:00 2001 From: Gleb Date: Fri, 4 Oct 2024 16:23:43 -0700 Subject: [PATCH 3/4] Deprecate contract inspect (#1636) --- FULL_HELP_DOCS.md | 4 ++-- cmd/soroban-cli/src/commands/contract/inspect.rs | 8 +++++++- cmd/soroban-cli/src/commands/contract/mod.rs | 5 +++-- cmd/soroban-cli/src/commands/network/container/shared.rs | 3 +-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index b8b03c5801..5128ce396e 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -85,7 +85,7 @@ Tools for smart contract developers * `id` — Generate the contract id for a given contract or asset * `info` — Access info about contracts * `init` — Initialize a Soroban project with an example contract -* `inspect` — Inspect a WASM file listing contract functions, meta, etc +* `inspect` — (Deprecated in favor of `contract info` subcommands) Inspect a WASM file listing contract functions, meta, etc * `install` — Install a WASM file to the ledger without creating a contract instance * `invoke` — Invoke a contract function * `optimize` — Optimize a WASM file @@ -630,7 +630,7 @@ Initialize a Soroban project with an example contract ## `stellar contract inspect` -Inspect a WASM file listing contract functions, meta, etc +(Deprecated in favor of `contract info` subcommands) Inspect a WASM file listing contract functions, meta, etc **Usage:** `stellar contract inspect [OPTIONS] --wasm ` diff --git a/cmd/soroban-cli/src/commands/contract/inspect.rs b/cmd/soroban-cli/src/commands/contract/inspect.rs index 36a1a23024..46fdc33a72 100644 --- a/cmd/soroban-cli/src/commands/contract/inspect.rs +++ b/cmd/soroban-cli/src/commands/contract/inspect.rs @@ -5,6 +5,8 @@ use std::{fmt::Debug, path::PathBuf}; use tracing::debug; use super::SpecOutput; +use crate::commands::global::Args; +use crate::print::Print; use crate::{config::locator, wasm}; #[derive(Parser, Debug, Clone)] @@ -33,7 +35,11 @@ pub enum Error { } impl Cmd { - pub fn run(&self) -> Result<(), Error> { + pub fn run(&self, global_args: &Args) -> Result<(), Error> { + Print::new(global_args.quiet).warnln( + "`contract inspect` has been deprecated in favor of `contract info`. \ + Please use `contract info` instead.", + ); let wasm = self.wasm.parse()?; debug!("File: {}", self.wasm.wasm.to_string_lossy()); let output = match self.output { diff --git a/cmd/soroban-cli/src/commands/contract/mod.rs b/cmd/soroban-cli/src/commands/contract/mod.rs index 761784de92..9ac6b8527b 100644 --- a/cmd/soroban-cli/src/commands/contract/mod.rs +++ b/cmd/soroban-cli/src/commands/contract/mod.rs @@ -56,7 +56,8 @@ pub enum Cmd { /// Initialize a Soroban project with an example contract Init(init::Cmd), - /// Inspect a WASM file listing contract functions, meta, etc + /// (Deprecated in favor of `contract info` subcommands) Inspect a WASM file listing contract functions, meta, etc + #[command(display_order = 100)] Inspect(inspect::Cmd), /// Install a WASM file to the ledger without creating a contract instance @@ -147,7 +148,7 @@ impl Cmd { Cmd::Id(id) => id.run()?, Cmd::Info(info) => info.run().await?, Cmd::Init(init) => init.run(global_args)?, - Cmd::Inspect(inspect) => inspect.run()?, + Cmd::Inspect(inspect) => inspect.run(global_args)?, Cmd::Install(install) => install.run(global_args).await?, Cmd::Invoke(invoke) => invoke.run(global_args).await?, Cmd::Optimize(optimize) => optimize.run()?, diff --git a/cmd/soroban-cli/src/commands/network/container/shared.rs b/cmd/soroban-cli/src/commands/network/container/shared.rs index 38cb17af20..c7964abb07 100644 --- a/cmd/soroban-cli/src/commands/network/container/shared.rs +++ b/cmd/soroban-cli/src/commands/network/container/shared.rs @@ -157,7 +157,7 @@ fn try_docker_desktop_socket( &default_docker_desktop_host, DEFAULT_TIMEOUT, API_DEFAULT_VERSION, - ).map_err(|e| { + ).inspect_err(|_| { print.errorln(format!( "Failed to connect to the Docker daemon at {host:?}. Is the docker daemon running?" )); @@ -167,7 +167,6 @@ fn try_docker_desktop_socket( print.infoln( "Please note that if you are using Docker Desktop, you may need to utilize the `--docker-host` flag to pass in the location of the docker socket on your machine." ); - e }) } From 46c534ee4223120b3ee9ca29aef3d4dda6603600 Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Tue, 8 Oct 2024 00:00:49 +1000 Subject: [PATCH 4/4] Make all logs in upgrade check debug (#1655) --- cmd/soroban-cli/src/upgrade_check.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/soroban-cli/src/upgrade_check.rs b/cmd/soroban-cli/src/upgrade_check.rs index 294056dd13..81ad7a0eef 100644 --- a/cmd/soroban-cli/src/upgrade_check.rs +++ b/cmd/soroban-cli/src/upgrade_check.rs @@ -56,7 +56,7 @@ pub async fn upgrade_check(quiet: bool) { let current_version = crate::commands::version::pkg(); let mut stats = UpgradeCheck::load().unwrap_or_else(|e| { - tracing::error!("Failed to load upgrade check data: {e}"); + tracing::debug!("Failed to load upgrade check data: {e}"); UpgradeCheck::default() }); @@ -72,7 +72,7 @@ pub async fn upgrade_check(quiet: bool) { }; } Err(e) => { - tracing::error!("Failed to fetch stellar-cli info from crates.io: {e}"); + tracing::debug!("Failed to fetch stellar-cli info from crates.io: {e}"); // Only update the latest check time if the fetch failed // This way we don't spam the user with errors stats.latest_check_time = now; @@ -80,7 +80,7 @@ pub async fn upgrade_check(quiet: bool) { } if let Err(e) = stats.save() { - tracing::error!("Failed to save upgrade check data: {e}"); + tracing::debug!("Failed to save upgrade check data: {e}"); } }