multiversx_chain_vm/host/context/
tx_result.rs

1use std::fmt;
2
3use multiversx_chain_core::types::ReturnCode;
4
5use crate::{
6    host::context::{TxFunctionName, TxInput},
7    vm_err_msg,
8};
9
10use super::{AsyncCallTxData, GasUsed, TxErrorTrace, TxLog, TxPanic, TxResultCalls};
11
12#[derive(Clone, Debug)]
13#[must_use]
14pub struct TxResult {
15    pub result_status: ReturnCode,
16    pub result_message: String,
17    pub result_values: Vec<Vec<u8>>,
18
19    /// Contains a log generated by ESDT transfer builtin functions,
20    /// like `MultiESDTNFTTransfer` or `ESDTTransfer`.
21    ///
22    /// There is at most one such log per transaction, hence Option, not Vec.
23    ///
24    /// It is kept separately, because, unlike regular logs,
25    /// it is not erased in case of tx failure (e.g. via sync call fallbile).
26    ///
27    /// It also appears in front of the regular logs, it is easier to merge this way.
28    ///
29    /// It does get merged into the regular logs when merging results after a sync call.
30    pub esdt_transfer_log: Option<TxLog>,
31
32    /// Logs created during the transaction execution.
33    pub result_logs: Vec<TxLog>,
34
35    pub gas_used: GasUsed,
36
37    /// Accumulates errors when they occur.
38    ///
39    /// It mimics the behavior of the Go VM,
40    /// and they contribute to the the internalVMErrors event log.
41    ///
42    /// Of course, the data field of the internalVMErrors log is implementation-dependent,
43    /// so a 1:1 match is impossible.
44    pub error_trace: Vec<TxErrorTrace>,
45
46    /// Calls that need to be executed.
47    ///
48    /// Structure is emptied as soon as async calls are executed.
49    pub pending_calls: TxResultCalls,
50
51    /// All async calls launched from the tx (legacy async, promises, transfer-execute).
52    ///
53    /// Is never cleared of its contents.
54    pub all_calls: Vec<AsyncCallTxData>,
55}
56
57impl Default for TxResult {
58    fn default() -> Self {
59        TxResult {
60            result_status: ReturnCode::Success,
61            result_message: String::new(),
62            result_values: Vec::new(),
63            esdt_transfer_log: None,
64            result_logs: Vec::new(),
65            gas_used: GasUsed::Unknown,
66            error_trace: Vec::new(),
67            pending_calls: TxResultCalls::empty(),
68            all_calls: Vec::new(),
69        }
70    }
71}
72
73impl TxResult {
74    pub fn empty() -> TxResult {
75        TxResult::default()
76    }
77
78    pub fn print(&self) {
79        println!("{self}");
80    }
81
82    pub fn from_panic_obj(panic_obj: &TxPanic) -> Self {
83        TxResult::from_error(panic_obj.status, &panic_obj.message)
84    }
85
86    pub fn from_panic_string(s: &str) -> Self {
87        TxResult::from_error(ReturnCode::UserError, s)
88    }
89
90    pub fn from_unknown_panic() -> Self {
91        Self::from_panic_string("")
92    }
93
94    pub fn from_error<S>(return_code: ReturnCode, result_message: S) -> Self
95    where
96        S: Into<String>,
97    {
98        TxResult {
99            result_status: return_code,
100            result_message: result_message.into(),
101            ..Default::default()
102        }
103    }
104
105    pub fn from_vm_error<S>(result_message: S) -> Self
106    where
107        S: Into<String>,
108    {
109        TxResult::from_error(ReturnCode::ExecutionFailed, result_message)
110    }
111
112    pub fn from_function_not_found() -> Self {
113        TxResult::from_error(ReturnCode::FunctionNotFound, vm_err_msg::FUNCTION_NOT_FOUND)
114    }
115
116    /// Retrieves all logs, including the ESDT transfer log if present.
117    pub fn all_logs(&self) -> Vec<&TxLog> {
118        let mut all_logs = Vec::new();
119        if let Some(esdt_log) = &self.esdt_transfer_log {
120            all_logs.push(esdt_log);
121        }
122        for log in &self.result_logs {
123            all_logs.push(log);
124        }
125        all_logs
126    }
127
128    /// Just like Vec::append, consumes the source TxResult logs.
129    pub fn append_all_logs(&mut self, source: &mut TxResult) {
130        if let Some(esdt_log) = source.esdt_transfer_log.take() {
131            self.result_logs.push(esdt_log);
132        }
133        self.result_logs.append(&mut source.result_logs);
134    }
135
136    pub fn merge_after_sync_call(&mut self, sync_call_result: &TxResult) {
137        self.result_values
138            .extend_from_slice(&sync_call_result.result_values);
139        if let Some(transfer_log) = &sync_call_result.esdt_transfer_log {
140            self.result_logs.push(transfer_log.clone());
141        }
142        self.result_logs
143            .extend_from_slice(&sync_call_result.result_logs);
144        self.error_trace
145            .extend_from_slice(&sync_call_result.error_trace);
146        if let Some(sync_result_async) = &sync_call_result.pending_calls.async_call {
147            assert!(
148                self.pending_calls.async_call.is_none(),
149                "Multiple async calls not supported"
150            );
151            self.pending_calls.async_call = Some(sync_result_async.clone());
152        }
153    }
154
155    /// Clears all TxResult fields, and replaces the error status and message fields.
156    ///
157    /// It also concatenates the error traces. In practice, the error_tx_result contains no error trace.
158    ///
159    /// Implementation note: this is done via `std::mem::replace` to ensure that all fields are cleared, except the ones related to errors.
160    pub fn merge_error(&mut self, error_tx_result: TxResult) {
161        let mut old_value = std::mem::replace(self, error_tx_result);
162
163        if let Some(transfer_log) = old_value.esdt_transfer_log {
164            self.result_logs.push(transfer_log);
165        }
166        self.error_trace.append(&mut old_value.error_trace);
167    }
168
169    pub fn assert_ok(&self) {
170        assert!(
171            self.result_status.is_success(),
172            "Tx success expected, but failed. Status: {}, message: \"{}\"",
173            self.result_status,
174            self.result_message.as_str()
175        );
176    }
177
178    pub fn assert_error(&self, expected_status: u64, expected_message: &str) {
179        assert!(
180            self.result_message.as_str() == expected_message,
181            "Tx error message mismatch. Want status {}, message \"{}\". Have status {}, message \"{}\"",
182            expected_status,
183            expected_message,
184            self.result_status,
185            self.result_message.as_str()
186        );
187        assert!(
188            self.result_status.as_u64() == expected_status,
189            "Tx error status mismatch. Want status {}, message \"{}\". Have status {}, message \"{}\"",
190            expected_status,
191            expected_message,
192            self.result_status,
193            self.result_message.as_str()
194        );
195    }
196
197    pub fn assert_user_error(&self, expected_message: &str) {
198        self.assert_error(ReturnCode::UserError.as_u64(), expected_message);
199    }
200}
201
202impl fmt::Display for TxResult {
203    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204        let results_hex: Vec<String> = self
205            .result_values
206            .iter()
207            .map(|r| format!("0x{}", hex::encode(r)))
208            .collect();
209        write!(
210            f,
211            "TxResult {{\n\tresult_status: {},\n\tresult_values:{results_hex:?}\n}}",
212            self.result_status
213        )
214    }
215}
216
217impl TxResult {
218    pub fn result_values_to_string(&self) -> String {
219        result_values_to_string(&self.result_values)
220    }
221}
222
223pub fn result_values_to_string(values: &[Vec<u8>]) -> String {
224    itertools::join(
225        values.iter().map(|val| format!("0x{}", hex::encode(val))),
226        ", ",
227    )
228}
229
230impl TxResult {
231    pub fn append_internal_vm_errors_event_log(&mut self, input: &TxInput) {
232        if self.error_trace.is_empty() {
233            return;
234        }
235
236        self.result_logs.push(TxLog {
237            address: input.from.clone(),
238            endpoint: TxFunctionName::from_static("internalVMErrors"),
239            topics: vec![input.to.to_vec(), input.func_name.to_bytes()],
240            data: self
241                .error_trace
242                .iter()
243                .map(|err| err.error_trace_message.as_bytes().to_vec())
244                .collect(),
245        });
246    }
247}