use std::fmt;
use multiversx_chain_core::types::ReturnCode;
use crate::{
host::context::{TxFunctionName, TxInput},
vm_err_msg,
};
use super::{AsyncCallTxData, GasUsed, TxErrorTrace, TxLog, TxPanic, TxResultCalls};
#[derive(Clone, Debug)]
#[must_use]
pub struct TxResult {
pub result_status: ReturnCode,
pub result_message: String,
pub result_values: Vec<Vec<u8>>,
pub esdt_transfer_log: Option<TxLog>,
pub result_logs: Vec<TxLog>,
pub gas_used: GasUsed,
pub error_trace: Vec<TxErrorTrace>,
pub pending_calls: TxResultCalls,
pub all_calls: Vec<AsyncCallTxData>,
}
impl Default for TxResult {
fn default() -> Self {
TxResult {
result_status: ReturnCode::Success,
result_message: String::new(),
result_values: Vec::new(),
esdt_transfer_log: None,
result_logs: Vec::new(),
gas_used: GasUsed::Unknown,
error_trace: Vec::new(),
pending_calls: TxResultCalls::empty(),
all_calls: Vec::new(),
}
}
}
impl TxResult {
pub fn empty() -> TxResult {
TxResult::default()
}
pub fn print(&self) {
println!("{self}");
}
pub fn from_panic_obj(panic_obj: &TxPanic) -> Self {
TxResult::from_error(panic_obj.status, &panic_obj.message)
}
pub fn from_panic_string(s: &str) -> Self {
TxResult::from_error(ReturnCode::UserError, s)
}
pub fn from_unknown_panic() -> Self {
Self::from_panic_string("")
}
pub fn from_error<S>(return_code: ReturnCode, result_message: S) -> Self
where
S: Into<String>,
{
TxResult {
result_status: return_code,
result_message: result_message.into(),
..Default::default()
}
}
pub fn from_vm_error<S>(result_message: S) -> Self
where
S: Into<String>,
{
TxResult::from_error(ReturnCode::ExecutionFailed, result_message)
}
pub fn from_function_not_found() -> Self {
TxResult::from_error(ReturnCode::FunctionNotFound, vm_err_msg::FUNCTION_NOT_FOUND)
}
pub fn all_logs(&self) -> Vec<&TxLog> {
let mut all_logs = Vec::new();
if let Some(esdt_log) = &self.esdt_transfer_log {
all_logs.push(esdt_log);
}
for log in &self.result_logs {
all_logs.push(log);
}
all_logs
}
pub fn append_all_logs(&mut self, source: &mut TxResult) {
if let Some(esdt_log) = source.esdt_transfer_log.take() {
self.result_logs.push(esdt_log);
}
self.result_logs.append(&mut source.result_logs);
}
pub fn merge_after_sync_call(&mut self, sync_call_result: &TxResult) {
self.result_values
.extend_from_slice(&sync_call_result.result_values);
if let Some(transfer_log) = &sync_call_result.esdt_transfer_log {
self.result_logs.push(transfer_log.clone());
}
self.result_logs
.extend_from_slice(&sync_call_result.result_logs);
self.error_trace
.extend_from_slice(&sync_call_result.error_trace);
if let Some(sync_result_async) = &sync_call_result.pending_calls.async_call {
assert!(
self.pending_calls.async_call.is_none(),
"Multiple async calls not supported"
);
self.pending_calls.async_call = Some(sync_result_async.clone());
}
}
pub fn merge_error(&mut self, error_tx_result: TxResult) {
let mut old_value = std::mem::replace(self, error_tx_result);
if let Some(transfer_log) = old_value.esdt_transfer_log {
self.result_logs.push(transfer_log);
}
self.error_trace.append(&mut old_value.error_trace);
}
pub fn assert_ok(&self) {
assert!(
self.result_status.is_success(),
"Tx success expected, but failed. Status: {}, message: \"{}\"",
self.result_status,
self.result_message.as_str()
);
}
pub fn assert_error(&self, expected_status: u64, expected_message: &str) {
assert!(
self.result_message.as_str() == expected_message,
"Tx error message mismatch. Want status {}, message \"{}\". Have status {}, message \"{}\"",
expected_status,
expected_message,
self.result_status,
self.result_message.as_str()
);
assert!(
self.result_status.as_u64() == expected_status,
"Tx error status mismatch. Want status {}, message \"{}\". Have status {}, message \"{}\"",
expected_status,
expected_message,
self.result_status,
self.result_message.as_str()
);
}
pub fn assert_user_error(&self, expected_message: &str) {
self.assert_error(ReturnCode::UserError.as_u64(), expected_message);
}
}
impl fmt::Display for TxResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let results_hex: Vec<String> = self
.result_values
.iter()
.map(|r| format!("0x{}", hex::encode(r)))
.collect();
write!(
f,
"TxResult {{\n\tresult_status: {},\n\tresult_values:{results_hex:?}\n}}",
self.result_status
)
}
}
impl TxResult {
pub fn result_values_to_string(&self) -> String {
result_values_to_string(&self.result_values)
}
}
pub fn result_values_to_string(values: &[Vec<u8>]) -> String {
itertools::join(
values.iter().map(|val| format!("0x{}", hex::encode(val))),
", ",
)
}
impl TxResult {
pub fn append_internal_vm_errors_event_log(&mut self, input: &TxInput) {
if self.error_trace.is_empty() {
return;
}
self.result_logs.push(TxLog {
address: input.from.clone(),
endpoint: TxFunctionName::from_static("internalVMErrors"),
topics: vec![input.to.to_vec(), input.func_name.to_bytes()],
data: self
.error_trace
.iter()
.map(|err| err.error_trace_message.as_bytes().to_vec())
.collect(),
});
}
}