From 25849dadd8d58fa434265a7631002c21606c9cec Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 2 May 2024 11:49:42 -0400 Subject: [PATCH 01/10] 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/10] 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/10] 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 48c5d0b2f1db031c47c53ddde28a43f567942e31 Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Thu, 9 May 2024 20:46:53 +1000 Subject: [PATCH 04/10] make TxnResult optional on NetworkRunnable because only some network runnable commands actually build a tx --- cmd/crates/soroban-test/src/lib.rs | 39 ++++++----- .../src/commands/contract/deploy/asset.rs | 8 +-- .../src/commands/contract/deploy/wasm.rs | 17 ++--- .../src/commands/contract/extend.rs | 27 ++++---- .../src/commands/contract/fetch.rs | 12 +--- .../src/commands/contract/install.rs | 10 +-- .../src/commands/contract/invoke.rs | 8 +-- cmd/soroban-cli/src/commands/contract/read.rs | 17 ++--- .../src/commands/contract/restore.rs | 19 +++--- cmd/soroban-cli/src/commands/events.rs | 33 ++++----- cmd/soroban-cli/src/commands/mod.rs | 2 +- cmd/soroban-cli/src/commands/txn_result.rs | 67 +++++-------------- cmd/soroban-cli/src/fee.rs | 3 +- 13 files changed, 101 insertions(+), 161 deletions(-) diff --git a/cmd/crates/soroban-test/src/lib.rs b/cmd/crates/soroban-test/src/lib.rs index 161535cd0..d55ad7bc7 100644 --- a/cmd/crates/soroban-test/src/lib.rs +++ b/cmd/crates/soroban-test/src/lib.rs @@ -193,7 +193,9 @@ impl TestEnv { source: &str, ) -> Result { let cmd = self.cmd_with_config::(command_str); - self.run_cmd_with(cmd, source).await + self.run_cmd_with(cmd, source) + .await + .map(|r| r.into_result().unwrap()) } /// A convenience method for using the invoke command. @@ -236,25 +238,22 @@ impl TestEnv { }, hd_path: None, }; - 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? - .into_res() - .unwrap()) + 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 } /// 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 e77f7015c..7672baa0d 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 = stellar_strkey::Contract; + type Result = TxnResult; async fn run_against_rpc_server( &self, args: Option<&global::Args>, config: Option<&config::Args>, - ) -> Result, Error> { + ) -> Result { let config = config.unwrap_or(&self.config); // Parse asset let asset = parse_asset(&self.asset)?; @@ -111,12 +111,12 @@ impl NetworkRunnable for Cmd { &key, )?; if self.fee.build_only { - return Ok(TxnResult::from_xdr(&tx)?); + return Ok(TxnResult::Txn(tx)); } 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::Txn(raw) => return Ok(TxnResult::Txn(raw)), TxnResult::Res(txn) => txn, }; let get_txn_resp = client diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index 3cb034498..95e846e44 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -18,7 +18,7 @@ use crate::commands::{ config::data, contract::{self, id::wasm::get_contract_id}, global, network, - txn_result::{self, TxnResult}, + txn_result::TxnResult, NetworkRunnable, }; use crate::{ @@ -95,8 +95,6 @@ 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,7 +113,7 @@ impl Cmd { #[async_trait::async_trait] impl NetworkRunnable for Cmd { type Error = Error; - type Result = String; + type Result = TxnResult; async fn run_against_rpc_server( &self, @@ -127,17 +125,16 @@ impl NetworkRunnable for Cmd { 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, + fee: self.fee.clone(), ignore_checks: self.ignore_checks, } .run_against_rpc_server(global_args, Some(config)) .await? - .try_into_res()? + .into_result() + .expect("the value (hash) is expected because it should always be available since build-only is a shared parameter") }; hex::encode(hash) } else { @@ -183,13 +180,13 @@ impl NetworkRunnable for Cmd { &key, )?; if self.fee.build_only { - return Ok(TxnResult::from_xdr(&txn)?); + return Ok(TxnResult::Txn(txn)); } 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::Txn(raw) => return Ok(TxnResult::Txn(raw)), TxnResult::Res(txn) => txn, }; let get_txn_resp = client diff --git a/cmd/soroban-cli/src/commands/contract/extend.rs b/cmd/soroban-cli/src/commands/contract/extend.rs index cebddd0c0..117568c20 100644 --- a/cmd/soroban-cli/src/commands/contract/extend.rs +++ b/cmd/soroban-cli/src/commands/contract/extend.rs @@ -3,9 +3,9 @@ use std::{fmt::Debug, path::Path, str::FromStr}; use clap::{command, Parser}; use soroban_env_host::xdr::{ Error as XdrError, ExtendFootprintTtlOp, ExtensionPoint, LedgerEntry, LedgerEntryChange, - LedgerEntryData, LedgerFootprint, Memo, MuxedAccount, Operation, OperationBody, Preconditions, - SequenceNumber, SorobanResources, SorobanTransactionData, Transaction, TransactionExt, - TransactionMeta, TransactionMetaV3, TtlEntry, Uint256, + LedgerEntryData, LedgerFootprint, Limits, Memo, MuxedAccount, Operation, OperationBody, + Preconditions, SequenceNumber, SorobanResources, SorobanTransactionData, Transaction, + TransactionExt, TransactionMeta, TransactionMetaV3, TtlEntry, Uint256, WriteXdr, }; use crate::{ @@ -90,14 +90,15 @@ 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 TxnResult::Res(ttl_ledger) = &res else { - println!("{}", res.xdr().unwrap()); - return Ok(()); - }; - if self.ttl_ledger_only { - println!("{ttl_ledger}"); - } else { - println!("New ttl ledger: {ttl_ledger}"); + match res { + TxnResult::Txn(tx) => println!("{}", tx.to_xdr_base64(Limits::none())?), + TxnResult::Res(ttl_ledger) => { + if self.ttl_ledger_only { + println!("{ttl_ledger}"); + } else { + println!("New ttl ledger: {ttl_ledger}"); + } + } } Ok(()) @@ -117,7 +118,7 @@ impl Cmd { #[async_trait::async_trait] impl NetworkRunnable for Cmd { type Error = Error; - type Result = u32; + type Result = TxnResult; async fn run_against_rpc_server( &self, @@ -168,7 +169,7 @@ impl NetworkRunnable for Cmd { }), }; if self.fee.build_only { - return Ok(TxnResult::from_xdr(&tx)?); + return Ok(TxnResult::Txn(tx)); } let res = client .prepare_and_send_transaction(&tx, &key, &[], &network.network_passphrase, None, None) diff --git a/cmd/soroban-cli/src/commands/contract/fetch.rs b/cmd/soroban-cli/src/commands/contract/fetch.rs index d9cb8d427..73ba12c2d 100644 --- a/cmd/soroban-cli/src/commands/contract/fetch.rs +++ b/cmd/soroban-cli/src/commands/contract/fetch.rs @@ -21,7 +21,6 @@ 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}, @@ -75,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}")] @@ -119,10 +116,7 @@ impl Cmd { } pub async fn get_bytes(&self) -> Result, Error> { - match self.run_against_rpc_server(None, None).await? { - TxnResult::Xdr(_) => Err(Error::UnexpectedXdr), - TxnResult::Res(v) => Ok(v), - } + self.run_against_rpc_server(None, None).await } pub fn network(&self) -> Result { @@ -143,7 +137,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()?; @@ -151,7 +145,7 @@ impl NetworkRunnable for Cmd { client .verify_network_passphrase(Some(&network.network_passphrase)) .await?; - Ok(TxnResult::Res(client.get_remote_wasm(&contract_id).await?)) + Ok(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 646a2d53a..4b2f7b5eb 100644 --- a/cmd/soroban-cli/src/commands/contract/install.rs +++ b/cmd/soroban-cli/src/commands/contract/install.rs @@ -7,7 +7,7 @@ use soroban_env_host::xdr::{ self, ContractCodeEntryExt, Error as XdrError, Hash, HostFunction, InvokeHostFunctionOp, LedgerEntryData, Limits, Memo, MuxedAccount, Operation, OperationBody, Preconditions, ReadXdr, ScMetaEntry, ScMetaV0, SequenceNumber, Transaction, TransactionExt, TransactionResult, - TransactionResultResult, Uint256, VecM, + TransactionResultResult, Uint256, VecM, WriteXdr, }; use super::restore; @@ -74,7 +74,7 @@ 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::Xdr(xdr) => xdr, + TxnResult::Txn(tx) => tx.to_xdr_base64(Limits::none())?, TxnResult::Res(hash) => hex::encode(hash), }; println!("{res_str}"); @@ -85,7 +85,7 @@ impl Cmd { #[async_trait::async_trait] impl NetworkRunnable for Cmd { type Error = Error; - type Result = Hash; + type Result = TxnResult; async fn run_against_rpc_server( &self, args: Option<&global::Args>, @@ -130,7 +130,7 @@ impl NetworkRunnable for Cmd { 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)?); + return Ok(TxnResult::Txn(tx_without_preflight)); } let code_key = xdr::LedgerKey::ContractCode(xdr::LedgerKeyContractCode { hash: hash.clone() }); @@ -163,7 +163,7 @@ impl NetworkRunnable for Cmd { .await?; let txn = self.fee.apply_to_assembled_txn(txn)?; let txn = match txn { - TxnResult::Xdr(raw) => return Ok(TxnResult::Xdr(raw)), + TxnResult::Txn(raw) => return Ok(TxnResult::Txn(raw)), TxnResult::Res(txn) => txn, }; let txn_resp = client diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 47220305d..09d541d39 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -21,7 +21,7 @@ use soroban_env_host::{ HostError, }; -use soroban_sdk::xdr::{ +use soroban_env_host::xdr::{ AccountEntry, AccountEntryExt, AccountId, ContractDataEntry, DiagnosticEvent, Thresholds, }; use soroban_spec::read::FromWasmError; @@ -308,7 +308,7 @@ impl Cmd { #[async_trait::async_trait] impl NetworkRunnable for Cmd { type Error = Error; - type Result = String; + type Result = TxnResult; async fn run_against_rpc_server( &self, @@ -379,12 +379,12 @@ impl NetworkRunnable for Cmd { account_id, )?; if self.fee.build_only { - return Ok(TxnResult::from_xdr(&tx)?); + return Ok(TxnResult::Txn(tx)); } 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::Txn(raw) => return Ok(TxnResult::Txn(raw)), TxnResult::Res(txn) => txn, }; let sim_res = txn.sim_response(); diff --git a/cmd/soroban-cli/src/commands/contract/read.rs b/cmd/soroban-cli/src/commands/contract/read.rs index 1d23e5c6a..d91bd42bd 100644 --- a/cmd/soroban-cli/src/commands/contract/read.rs +++ b/cmd/soroban-cli/src/commands/contract/read.rs @@ -9,12 +9,11 @@ use soroban_env_host::{ ContractDataEntry, Error as XdrError, LedgerEntryData, LedgerKey, LedgerKeyContractData, ScVal, WriteXdr, }, - HostError, + HostError, Limits, }; -use soroban_sdk::xdr::Limits; use crate::{ - commands::{config, global, txn_result::TxnResult, NetworkRunnable}, + commands::{config, global, NetworkRunnable}, key, rpc::{self, Client, FullLedgerEntries, FullLedgerEntry}, }; @@ -91,13 +90,7 @@ pub enum Error { impl Cmd { pub async fn run(&self) -> Result<(), Error> { - let entries = match self.run_against_rpc_server(None, None).await? { - TxnResult::Res(res) => res, - TxnResult::Xdr(xdr) => { - println!("{xdr}"); - return Ok(()); - } - }; + let entries = self.run_against_rpc_server(None, None).await?; self.output_entries(&entries) } @@ -184,12 +177,12 @@ impl NetworkRunnable for Cmd { &self, _: Option<&global::Args>, config: Option<&config::Args>, - ) -> Result, Error> { + ) -> Result { 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(TxnResult::Res(client.get_full_ledger_entries(&keys).await?)) + Ok(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 6fc8eb17f..f78316886 100644 --- a/cmd/soroban-cli/src/commands/contract/restore.rs +++ b/cmd/soroban-cli/src/commands/contract/restore.rs @@ -3,9 +3,9 @@ use std::{fmt::Debug, path::Path, str::FromStr}; use clap::{command, Parser}; use soroban_env_host::xdr::{ Error as XdrError, ExtensionPoint, LedgerEntry, LedgerEntryChange, LedgerEntryData, - LedgerFootprint, Memo, MuxedAccount, Operation, OperationBody, OperationMeta, Preconditions, - RestoreFootprintOp, SequenceNumber, SorobanResources, SorobanTransactionData, Transaction, - TransactionExt, TransactionMeta, TransactionMetaV3, TtlEntry, Uint256, + LedgerFootprint, Limits, Memo, MuxedAccount, Operation, OperationBody, OperationMeta, + Preconditions, RestoreFootprintOp, SequenceNumber, SorobanResources, SorobanTransactionData, + Transaction, TransactionExt, TransactionMeta, TransactionMetaV3, TtlEntry, Uint256, WriteXdr, }; use stellar_strkey::DecodeError; @@ -14,7 +14,7 @@ use crate::{ config::{self, data, locator}, contract::extend, global, network, - txn_result::{self, TxnResult}, + txn_result::TxnResult, NetworkRunnable, }, key, @@ -89,9 +89,6 @@ pub enum Error { Data(#[from] data::Error), #[error(transparent)] Network(#[from] network::Error), - - #[error(transparent)] - TxnResult(#[from] txn_result::Error), } impl Cmd { @@ -99,8 +96,8 @@ impl Cmd { 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::Xdr(xdr) => { - println!("{xdr}"); + TxnResult::Txn(xdr) => { + println!("{}", xdr.to_xdr_base64(Limits::none())?); return Ok(()); } }; @@ -125,7 +122,7 @@ impl Cmd { #[async_trait::async_trait] impl NetworkRunnable for Cmd { type Error = Error; - type Result = u32; + type Result = TxnResult; async fn run_against_rpc_server( &self, @@ -173,7 +170,7 @@ impl NetworkRunnable for Cmd { }), }; if self.fee.build_only { - return Ok(TxnResult::from_xdr(&tx)?); + return Ok(TxnResult::Txn(tx)); } let res = client .prepare_and_send_transaction(&tx, &key, &[], &network.network_passphrase, None, None) diff --git a/cmd/soroban-cli/src/commands/events.rs b/cmd/soroban-cli/src/commands/events.rs index f1707f349..23cd07e9d 100644 --- a/cmd/soroban-cli/src/commands/events.rs +++ b/cmd/soroban-cli/src/commands/events.rs @@ -5,9 +5,7 @@ use soroban_env_host::xdr::{self, Limits, ReadXdr}; use super::{ config::{self, locator}, - global, network, - txn_result::TxnResult, - NetworkRunnable, + global, network, NetworkRunnable, }; use crate::{rpc, utils}; @@ -126,8 +124,6 @@ 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)] @@ -171,8 +167,7 @@ impl Cmd { })?; } - let txn_res = self.run_against_rpc_server(None, None).await?; - let response = txn_res.try_res()?; + let response = self.run_against_rpc_server(None, None).await?; for event in &response.events { match self.output { @@ -219,7 +214,7 @@ impl NetworkRunnable for Cmd { &self, _args: Option<&global::Args>, config: Option<&config::Args>, - ) -> Result, Error> { + ) -> Result { let start = self.start()?; let network = if let Some(config) = config { Ok(config.get_network()?) @@ -231,17 +226,15 @@ impl NetworkRunnable for Cmd { client .verify_network_passphrase(Some(&network.network_passphrase)) .await?; - 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)?, - )) + Ok(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 f904c465f..37328cd1a 100644 --- a/cmd/soroban-cli/src/commands/mod.rs +++ b/cmd/soroban-cli/src/commands/mod.rs @@ -170,5 +170,5 @@ pub trait NetworkRunnable { &self, global_args: Option<&global::Args>, config: Option<&config::Args>, - ) -> Result, Self::Error>; + ) -> Result; } diff --git a/cmd/soroban-cli/src/commands/txn_result.rs b/cmd/soroban-cli/src/commands/txn_result.rs index 02cc27f4c..1134e5555 100644 --- a/cmd/soroban-cli/src/commands/txn_result.rs +++ b/cmd/soroban-cli/src/commands/txn_result.rs @@ -1,69 +1,34 @@ use std::fmt::{Display, Formatter}; -use soroban_sdk::xdr::{self, Limits, WriteXdr}; +use soroban_env_host::xdr::{Limits, Transaction, WriteXdr}; -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("Expect xdr string")] - XdrStringExpected, - #[error("Expect result")] - ResultExpected, +pub enum TxnResult { + Txn(Transaction), + Res(R), } -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<&T> { - match self { - TxnResult::Res(res) => Some(res), - TxnResult::Xdr(_) => None, - } - } - - pub fn into_res(self) -> Option { +impl TxnResult { + pub fn into_result(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<&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), + TxnResult::Txn(_) => None, } } } -impl Display for TxnResult +impl Display for TxnResult where - T: Display, + V: Display, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - TxnResult::Xdr(xdr) => write!(f, "{xdr}"), - TxnResult::Res(res) => write!(f, "{res}"), + TxnResult::Txn(tx) => write!( + f, + "{}", + tx.to_xdr_base64(Limits::none()) + .map_err(|_| std::fmt::Error)? + ), + TxnResult::Res(value) => write!(f, "{value}"), } } } diff --git a/cmd/soroban-cli/src/fee.rs b/cmd/soroban-cli/src/fee.rs index 70ad9abd9..257109b2e 100644 --- a/cmd/soroban-cli/src/fee.rs +++ b/cmd/soroban-cli/src/fee.rs @@ -36,7 +36,8 @@ impl Args { add_padding_to_instructions(txn) }; if self.sim_only { - TxnResult::from_xdr(simulated_txn.transaction()) + // TODO: Move into callers. + Ok(TxnResult::Txn(simulated_txn.transaction().clone())) } else { Ok(TxnResult::Res(simulated_txn)) } From e84777a48baf5613a4c5cf6ee00945922f743bb2 Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Thu, 9 May 2024 21:09:37 +1000 Subject: [PATCH 05/10] tiny fix --- cmd/soroban-cli/src/commands/contract/read.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/read.rs b/cmd/soroban-cli/src/commands/contract/read.rs index d91bd42bd..d7daacd3f 100644 --- a/cmd/soroban-cli/src/commands/contract/read.rs +++ b/cmd/soroban-cli/src/commands/contract/read.rs @@ -7,9 +7,9 @@ use clap::{command, Parser, ValueEnum}; use soroban_env_host::{ xdr::{ ContractDataEntry, Error as XdrError, LedgerEntryData, LedgerKey, LedgerKeyContractData, - ScVal, WriteXdr, + Limits, ScVal, WriteXdr, }, - HostError, Limits, + HostError, }; use crate::{ From cff3677c88ad431c2ffa5bf6cb765bd27e54c3bf Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Thu, 9 May 2024 21:10:27 +1000 Subject: [PATCH 06/10] move if sim_only into callers for consistency with build only and making the apply_to_assembled_txn less ambiguous --- .../src/commands/contract/deploy/asset.rs | 9 ++++----- .../src/commands/contract/deploy/wasm.rs | 9 ++++----- cmd/soroban-cli/src/commands/contract/install.rs | 9 ++++----- cmd/soroban-cli/src/commands/contract/invoke.rs | 9 ++++----- cmd/soroban-cli/src/fee.rs | 14 +++----------- 5 files changed, 19 insertions(+), 31 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs index 7672baa0d..1f8d4544f 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs @@ -114,11 +114,10 @@ impl NetworkRunnable for Cmd { return Ok(TxnResult::Txn(tx)); } let txn = client.create_assembled_transaction(&tx).await?; - let txn = self.fee.apply_to_assembled_txn(txn)?; - let txn = match txn { - TxnResult::Txn(raw) => return Ok(TxnResult::Txn(raw)), - TxnResult::Res(txn) => txn, - }; + let txn = self.fee.apply_to_assembled_txn(txn); + if self.fee.sim_only { + return Ok(TxnResult::Txn(txn.transaction().clone())); + } let get_txn_resp = client .send_assembled_transaction(txn, &key, &[], network_passphrase, None, None) .await? diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index 95e846e44..dc4632043 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -184,11 +184,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::Txn(raw) => return Ok(TxnResult::Txn(raw)), - TxnResult::Res(txn) => txn, - }; + let txn = self.fee.apply_to_assembled_txn(txn); + if self.fee.sim_only { + return Ok(TxnResult::Txn(txn.transaction().clone())); + } 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/install.rs b/cmd/soroban-cli/src/commands/contract/install.rs index 4b2f7b5eb..ba7886446 100644 --- a/cmd/soroban-cli/src/commands/contract/install.rs +++ b/cmd/soroban-cli/src/commands/contract/install.rs @@ -161,11 +161,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 = match txn { - TxnResult::Txn(raw) => return Ok(TxnResult::Txn(raw)), - TxnResult::Res(txn) => txn, - }; + let txn = self.fee.apply_to_assembled_txn(txn); + if self.fee.sim_only { + return Ok(TxnResult::Txn(txn.transaction().clone())); + } 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 09d541d39..ef1fd7db1 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -382,11 +382,10 @@ impl NetworkRunnable for Cmd { return Ok(TxnResult::Txn(tx)); } let txn = client.create_assembled_transaction(&tx).await?; - let txn = self.fee.apply_to_assembled_txn(txn)?; - let txn = match txn { - TxnResult::Txn(raw) => return Ok(TxnResult::Txn(raw)), - TxnResult::Res(txn) => txn, - }; + let txn = self.fee.apply_to_assembled_txn(txn); + if self.fee.sim_only { + return Ok(TxnResult::Txn(txn.transaction().clone())); + } 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/fee.rs b/cmd/soroban-cli/src/fee.rs index 257109b2e..c7fb7f7a3 100644 --- a/cmd/soroban-cli/src/fee.rs +++ b/cmd/soroban-cli/src/fee.rs @@ -3,7 +3,7 @@ use clap::arg; use soroban_env_host::xdr; use soroban_rpc::Assembled; -use crate::commands::{txn_result::TxnResult, HEADING_RPC}; +use crate::commands::HEADING_RPC; #[derive(Debug, clap::Args, Clone)] #[group(skip)] @@ -26,21 +26,13 @@ pub struct Args { } impl Args { - pub fn apply_to_assembled_txn( - &self, - txn: Assembled, - ) -> Result, xdr::Error> { + pub fn apply_to_assembled_txn(&self, txn: Assembled) -> Assembled { let simulated_txn = if let Some(instructions) = self.instructions { txn.set_max_instructions(instructions) } else { add_padding_to_instructions(txn) }; - if self.sim_only { - // TODO: Move into callers. - Ok(TxnResult::Txn(simulated_txn.transaction().clone())) - } else { - Ok(TxnResult::Res(simulated_txn)) - } + simulated_txn } } From e5b7edbc14a0ede30e9b41f7e2c5d16513810363 Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Thu, 9 May 2024 21:18:56 +1000 Subject: [PATCH 07/10] small fix to tests --- cmd/crates/soroban-test/tests/it/util.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/crates/soroban-test/tests/it/util.rs b/cmd/crates/soroban-test/tests/it/util.rs index c70091503..c2f01054c 100644 --- a/cmd/crates/soroban-test/tests/it/util.rs +++ b/cmd/crates/soroban-test/tests/it/util.rs @@ -53,7 +53,10 @@ pub async fn invoke_custom( ) -> Result { let mut i: contract::invoke::Cmd = sandbox.cmd_with_config(&["--id", id, "--", func, arg]); i.wasm = Some(wasm.to_path_buf()); - sandbox.run_cmd_with(i, TEST_ACCOUNT).await + sandbox + .run_cmd_with(i, TEST_ACCOUNT) + .await + .map(|r| r.into_result().unwrap()) } pub const DEFAULT_CONTRACT_ID: &str = "CDR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OO5Z"; From 622d09cb9c7de440bc545b68d2ca6f17ef96ac21 Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Thu, 9 May 2024 21:22:06 +1000 Subject: [PATCH 08/10] fix clippy --- cmd/soroban-cli/src/fee.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cmd/soroban-cli/src/fee.rs b/cmd/soroban-cli/src/fee.rs index c7fb7f7a3..b7b7e0441 100644 --- a/cmd/soroban-cli/src/fee.rs +++ b/cmd/soroban-cli/src/fee.rs @@ -27,12 +27,11 @@ pub struct Args { impl Args { pub fn apply_to_assembled_txn(&self, txn: Assembled) -> Assembled { - let simulated_txn = if let Some(instructions) = self.instructions { + if let Some(instructions) = self.instructions { txn.set_max_instructions(instructions) } else { add_padding_to_instructions(txn) - }; - simulated_txn + } } } From 457a54becee30520b7ae9a50048febc5ed5c962f Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Thu, 9 May 2024 21:32:43 +1000 Subject: [PATCH 09/10] fix a bug with how sim only works with install --- .../src/commands/contract/install.rs | 45 ++++++++++--------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/install.rs b/cmd/soroban-cli/src/commands/contract/install.rs index ba7886446..4e1b83d9a 100644 --- a/cmd/soroban-cli/src/commands/contract/install.rs +++ b/cmd/soroban-cli/src/commands/contract/install.rs @@ -132,28 +132,33 @@ impl NetworkRunnable for Cmd { if self.fee.build_only { return Ok(TxnResult::Txn(tx_without_preflight)); } - let code_key = - xdr::LedgerKey::ContractCode(xdr::LedgerKeyContractCode { hash: hash.clone() }); - let contract_data = client.get_ledger_entries(&[code_key]).await?; - // Skip install if the contract is already installed, and the contract has an extension version that isn't V0. - // In protocol 21 extension V1 was added that stores additional information about a contract making execution - // of the contract cheaper. So if folks want to reinstall we should let them which is why the install will still - // go ahead if the contract has a V0 extension. - if let Some(entries) = contract_data.entries { - if let Some(entry_result) = entries.first() { - let entry: LedgerEntryData = - LedgerEntryData::from_xdr_base64(&entry_result.xdr, Limits::none())?; + // Don't check whether the contract is already installed when the user + // has requested to perform simulation only and is hoping to get a + // transaction back. + if !self.fee.sim_only { + let code_key = + xdr::LedgerKey::ContractCode(xdr::LedgerKeyContractCode { hash: hash.clone() }); + let contract_data = client.get_ledger_entries(&[code_key]).await?; + // Skip install if the contract is already installed, and the contract has an extension version that isn't V0. + // In protocol 21 extension V1 was added that stores additional information about a contract making execution + // of the contract cheaper. So if folks want to reinstall we should let them which is why the install will still + // go ahead if the contract has a V0 extension. + if let Some(entries) = contract_data.entries { + if let Some(entry_result) = entries.first() { + let entry: LedgerEntryData = + LedgerEntryData::from_xdr_base64(&entry_result.xdr, Limits::none())?; - match &entry { - LedgerEntryData::ContractCode(code) => { - // Skip reupload if this isn't V0 because V1 extension already - // exists. - if code.ext.ne(&ContractCodeEntryExt::V0) { - return Ok(TxnResult::Res(hash)); + match &entry { + LedgerEntryData::ContractCode(code) => { + // Skip reupload if this isn't V0 because V1 extension already + // exists. + if code.ext.ne(&ContractCodeEntryExt::V0) { + return Ok(TxnResult::Res(hash)); + } + } + _ => { + tracing::warn!("Entry retrieved should be of type ContractCode"); } - } - _ => { - tracing::warn!("Entry retrieved should be of type ContractCode"); } } } From 92cd3931e52f8448767106f18a607c61051d4efb Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Thu, 9 May 2024 21:40:24 +1000 Subject: [PATCH 10/10] fix rpc tests --- .../soroban-test/tests/it/integration/hello_world.rs | 5 +++-- cmd/crates/soroban-test/tests/it/integration/util.rs | 7 ++++++- cmd/soroban-cli/src/commands/txn_result.rs | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) 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 758c6fce0..fae0a8463 100644 --- a/cmd/crates/soroban-test/tests/it/integration/hello_world.rs +++ b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs @@ -2,6 +2,7 @@ use predicates::boolean::PredicateBooleanExt; use soroban_cli::commands::{ config::{locator, secret}, contract::{self, fetch}, + txn_result::TxnResult, }; use soroban_rpc::GetLatestLedgerResponse; use soroban_test::{AssertExt, TestEnv, LOCAL_NETWORK_PASSPHRASE}; @@ -20,7 +21,7 @@ async fn invoke_view_with_non_existent_source_account() { 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:?}]"#)); + assert_eq!(res, TxnResult::Res(format!(r#"["Hello",{world:?}]"#))); } #[tokio::test] @@ -163,7 +164,7 @@ fn hello_world_cmd(id: &str, arg: &str) -> contract::invoke::Cmd { 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"]"#); + assert_eq!(res, TxnResult::Res(r#"["Hello","world"]"#.to_string())); } fn invoke_auth(sandbox: &TestEnv, id: &str, addr: &str) { diff --git a/cmd/crates/soroban-test/tests/it/integration/util.rs b/cmd/crates/soroban-test/tests/it/integration/util.rs index faa3534e4..4fc870e34 100644 --- a/cmd/crates/soroban-test/tests/it/integration/util.rs +++ b/cmd/crates/soroban-test/tests/it/integration/util.rs @@ -38,7 +38,12 @@ pub async fn deploy_contract(sandbox: &TestEnv, wasm: &Wasm<'static>) -> String TEST_SALT, "--ignore-checks", ]); - sandbox.run_cmd_with(cmd, "test").await.unwrap() + sandbox + .run_cmd_with(cmd, "test") + .await + .unwrap() + .into_result() + .unwrap() } pub async fn extend_contract(sandbox: &TestEnv, id: &str) { diff --git a/cmd/soroban-cli/src/commands/txn_result.rs b/cmd/soroban-cli/src/commands/txn_result.rs index 1134e5555..6b189f25b 100644 --- a/cmd/soroban-cli/src/commands/txn_result.rs +++ b/cmd/soroban-cli/src/commands/txn_result.rs @@ -2,6 +2,7 @@ use std::fmt::{Display, Formatter}; use soroban_env_host::xdr::{Limits, Transaction, WriteXdr}; +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum TxnResult { Txn(Transaction), Res(R),