diff --git a/soroban-env-host/Cargo.toml b/soroban-env-host/Cargo.toml index 56d23a02e..d8a9b08e6 100644 --- a/soroban-env-host/Cargo.toml +++ b/soroban-env-host/Cargo.toml @@ -110,7 +110,8 @@ default-features = false features = ["arbitrary"] [features] -testutils = ["soroban-env-common/testutils", "recording_mode", "dep:backtrace"] +testutils = ["soroban-env-common/testutils", "recording_mode"] +backtrace = ["dep:backtrace"] next = ["soroban-env-common/next", "stellar-xdr/next"] tracy = ["dep:tracy-client", "soroban-env-common/tracy"] recording_mode = [] diff --git a/soroban-env-host/src/events/mod.rs b/soroban-env-host/src/events/mod.rs index fb70e1ac9..b1c880b98 100644 --- a/soroban-env-host/src/events/mod.rs +++ b/soroban-env-host/src/events/mod.rs @@ -123,11 +123,37 @@ impl core::fmt::Display for HostEvent { match &self.event.body { ContractEventBody::V0(ceb) => { write!(f, "topics:[")?; + + let mut is_fn_call = false; for (i, topic) in ceb.topics.iter().enumerate() { if i != 0 { write!(f, ", ")?; } + + // The second topic of the fn_call event is the contract id as ScBytes, + // but we want to display it as a C key instead, so this block + // tries to deduce if the event is the fn_call event. + if i == 1 && is_fn_call { + if let ScVal::Bytes(bytes) = topic { + let try_convert_to_hash = + TryInto::<[u8; 32]>::try_into(bytes.0.clone()); + if let Ok(contract_id) = try_convert_to_hash { + let strkey = stellar_strkey::Contract(contract_id); + write!(f, "{}", strkey)?; + continue; + } + } + } + display_scval(topic, f)?; + + if i == 0 { + if let ScVal::Symbol(first_topic_str) = topic { + if first_topic_str.0.as_slice() == "fn_call".as_bytes() { + is_fn_call = true; + } + } + } } write!(f, "], data:")?; display_scval(&ceb.data, f) diff --git a/soroban-env-host/src/host/error.rs b/soroban-env-host/src/host/error.rs index d2bfcfca4..6110b1b35 100644 --- a/soroban-env-host/src/host/error.rs +++ b/soroban-env-host/src/host/error.rs @@ -5,7 +5,7 @@ use crate::{ ConversionError, EnvBase, Error, Host, TryFromVal, U32Val, Val, }; -#[cfg(any(test, feature = "testutils"))] +#[cfg(any(test, feature = "backtrace"))] use backtrace::{Backtrace, BacktraceFrame}; use core::fmt::Debug; use std::{ @@ -18,7 +18,7 @@ use super::metered_clone::MeteredClone; #[derive(Clone)] pub(crate) struct DebugInfo { events: Events, - #[cfg(any(test, feature = "testutils"))] + #[cfg(any(test, feature = "backtrace"))] backtrace: Backtrace, } @@ -67,12 +67,12 @@ impl DebugInfo { Ok(()) } - #[cfg(not(any(test, feature = "testutils")))] + #[cfg(not(any(test, feature = "backtrace")))] fn write_backtrace(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Ok(()) } - #[cfg(any(test, feature = "testutils"))] + #[cfg(any(test, feature = "backtrace"))] fn write_backtrace(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // We do a little trimming here, skipping the first two frames (which // are always into, from, and one or more Host::err_foo calls) and all @@ -296,8 +296,11 @@ impl Host { self.with_debug_mode(|| { if let Ok(events_ref) = self.0.events.try_borrow() { let events = events_ref.externalize(self)?; - let backtrace = Backtrace::new_unresolved(); - res = Some(Box::new(DebugInfo { backtrace, events })); + res = Some(Box::new(DebugInfo { + #[cfg(any(test, feature = "backtrace"))] + backtrace: Backtrace::new_unresolved(), + events, + })); } Ok(()) }); diff --git a/soroban-env-host/src/test/e2e_tests.rs b/soroban-env-host/src/test/e2e_tests.rs index bc37aea52..8a136ea3e 100644 --- a/soroban-env-host/src/test/e2e_tests.rs +++ b/soroban-env-host/src/test/e2e_tests.rs @@ -610,6 +610,32 @@ fn test_wasm_upload_success() { assert!(res.budget.get_mem_bytes_consumed().unwrap() > 0); } +#[test] +fn test_wasm_upload_failure_due_to_unsupported_wasm_features() { + let ledger_key = get_wasm_key(ADD_F32); + let ledger_info = default_ledger_info(); + + let res = invoke_host_function_helper( + false, + &upload_wasm_host_fn(ADD_F32), + &resources(10_000_000, vec![], vec![ledger_key.clone()]), + &get_account_id([123; 32]), + vec![], + &ledger_info, + vec![], + &prng_seed(), + ) + .unwrap(); + assert!(res.budget.get_cpu_insns_consumed().unwrap() > 0); + assert!(res.budget.get_mem_bytes_consumed().unwrap() > 0); + + assert!(res.invoke_result.is_err()); + assert!(HostError::result_matches_err( + res.invoke_result, + (ScErrorType::WasmVm, ScErrorCode::InvalidAction) + )); +} + #[test] fn test_wasm_upload_success_in_recording_mode() { let ledger_key = get_wasm_key(ADD_I32); @@ -702,6 +728,29 @@ fn test_wasm_upload_failure_in_recording_mode() { ); } +#[test] +fn test_unsupported_wasm_upload_failure_in_recording_mode() { + let ledger_info = default_ledger_info(); + + let res = invoke_host_function_recording_helper( + true, + &upload_wasm_host_fn(ADD_F32), + &get_account_id([123; 32]), + None, + &ledger_info, + vec![], + &prng_seed(), + None, + ) + .unwrap(); + assert!(res.diagnostic_events.len() >= 1); + assert!(res.contract_events.is_empty()); + assert!(HostError::result_matches_err( + res.invoke_result, + (ScErrorType::WasmVm, ScErrorCode::InvalidAction) + )); +} + #[test] fn test_wasm_upload_success_using_simulation() { let res = invoke_host_function_using_simulation_with_signers(