unc_workspaces/
result.rs

1//! Result and execution types from results of RPC calls to the network.
2
3use std::fmt;
4
5use base64::{engine::general_purpose, Engine as _};
6
7use unc_account_id::AccountId;
8use unc_gas::UncGas;
9use unc_primitives::borsh;
10use unc_primitives::errors::TxExecutionError;
11use unc_primitives::views::{
12    CallResult, ExecutionOutcomeWithIdView, ExecutionStatusView, FinalExecutionOutcomeView,
13    FinalExecutionStatus,
14};
15
16use crate::error::ErrorKind;
17use crate::types::{CryptoHash, Gas, UncToken};
18
19pub type Result<T, E = crate::error::Error> = core::result::Result<T, E>;
20
21/// Execution related info as a result of performing a successful transaction
22/// execution on the network. This value can be converted into the returned
23/// value of the transaction via [`ExecutionSuccess::json`] or [`ExecutionSuccess::borsh`]
24pub type ExecutionSuccess = ExecutionResult<Value>;
25
26/// Execution related info as a result of performing a failed transaction
27/// execution on the network. The related error message can be retrieved
28/// from this object or can be forwarded.
29pub type ExecutionFailure = ExecutionResult<TxExecutionError>;
30
31/// Struct to hold a type we want to return along w/ the execution result view.
32/// This view has extra info about the execution, such as gas usage and whether
33/// the transaction failed to be processed on the chain.
34#[non_exhaustive]
35#[must_use = "use `into_result()` to handle potential execution errors"]
36pub struct Execution<T> {
37    pub result: T,
38    pub details: ExecutionFinalResult,
39}
40
41impl<T> Execution<T> {
42    pub fn unwrap(self) -> T {
43        self.into_result().unwrap()
44    }
45
46    #[allow(clippy::result_large_err)]
47    pub fn into_result(self) -> Result<T, ExecutionFailure> {
48        self.details.into_result()?;
49        Ok(self.result)
50    }
51
52    /// Checks whether the transaction was successful. Returns true if
53    /// the transaction has a status of FinalExecutionStatus::Success.
54    pub fn is_success(&self) -> bool {
55        self.details.is_success()
56    }
57
58    /// Checks whether the transaction has failed. Returns true if
59    /// the transaction has a status of FinalExecutionStatus::Failure.
60    pub fn is_failure(&self) -> bool {
61        self.details.is_failure()
62    }
63}
64
65/// The transaction/receipt details of a transaction execution. This object
66/// can be used to retrieve data such as logs and gas burnt per transaction
67/// or receipt.
68#[derive(PartialEq, Eq, Clone)]
69pub(crate) struct ExecutionDetails {
70    pub(crate) transaction: ExecutionOutcome,
71    pub(crate) receipts: Vec<ExecutionOutcome>,
72}
73
74impl ExecutionDetails {
75    /// Returns just the transaction outcome.
76    pub fn outcome(&self) -> &ExecutionOutcome {
77        &self.transaction
78    }
79
80    /// Grab all outcomes after the execution of the transaction. This includes outcomes
81    /// from the transaction and all the receipts it generated.
82    pub fn outcomes(&self) -> Vec<&ExecutionOutcome> {
83        let mut outcomes = vec![&self.transaction];
84        outcomes.extend(self.receipt_outcomes());
85        outcomes
86    }
87
88    /// Grab all outcomes after the execution of the transaction. This includes outcomes
89    /// only from receipts generated by this transaction.
90    pub fn receipt_outcomes(&self) -> &[ExecutionOutcome] {
91        &self.receipts
92    }
93
94    /// Grab all outcomes that did not succeed the execution of this transaction. This
95    /// will also include the failures from receipts as well.
96    pub fn failures(&self) -> Vec<&ExecutionOutcome> {
97        let mut failures = Vec::new();
98        if matches!(self.transaction.status, ExecutionStatusView::Failure(_)) {
99            failures.push(&self.transaction);
100        }
101        failures.extend(self.receipt_failures());
102        failures
103    }
104
105    /// Just like `failures`, grab only failed receipt outcomes.
106    pub fn receipt_failures(&self) -> Vec<&ExecutionOutcome> {
107        self.receipts
108            .iter()
109            .filter(|receipt| matches!(receipt.status, ExecutionStatusView::Failure(_)))
110            .collect()
111    }
112
113    /// Grab all logs from both the transaction and receipt outcomes.
114    pub fn logs(&self) -> Vec<&str> {
115        self.outcomes()
116            .iter()
117            .flat_map(|outcome| &outcome.logs)
118            .map(String::as_str)
119            .collect()
120    }
121}
122
123/// The result after evaluating the status of an execution. This can be [`ExecutionSuccess`]
124/// for successful executions or a [`ExecutionFailure`] for failed ones.
125#[derive(PartialEq, Eq, Clone)]
126#[non_exhaustive]
127pub struct ExecutionResult<T> {
128    /// Total gas burnt by the execution
129    pub total_gas_burnt: Gas,
130
131    /// Value returned from an execution. This is a base64 encoded str for a successful
132    /// execution or a `TxExecutionError` if a failed one.
133    pub(crate) value: T,
134    // pub(crate) transaction: ExecutionOutcome,
135    // pub(crate) receipts: Vec<ExecutionOutcome>,
136    pub(crate) details: ExecutionDetails,
137}
138
139impl<T: fmt::Debug> fmt::Debug for ExecutionResult<T> {
140    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141        f.debug_struct("ExecutionResult")
142            .field("total_gas_burnt", &self.total_gas_burnt)
143            .field("transaction", &self.details.transaction)
144            .field("receipts", &self.details.receipts)
145            .field("value", &self.value)
146            .finish()
147    }
148}
149
150/// Execution related info found after performing a transaction. Can be converted
151/// into [`ExecutionSuccess`] or [`ExecutionFailure`] through [`into_result`]
152///
153/// [`into_result`]: crate::result::ExecutionFinalResult::into_result
154#[derive(PartialEq, Eq, Clone)]
155#[must_use = "use `into_result()` to handle potential execution errors"]
156pub struct ExecutionFinalResult {
157    /// Total gas burnt by the execution
158    pub total_gas_burnt: Gas,
159
160    pub(crate) status: FinalExecutionStatus,
161    pub(crate) details: ExecutionDetails,
162}
163
164impl fmt::Debug for ExecutionFinalResult {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        f.debug_struct("ExecutionFinalResult")
167            .field("total_gas_burnt", &self.total_gas_burnt)
168            .field("transaction", &self.details.transaction)
169            .field("receipts", &self.details.receipts)
170            .field("status", &self.status)
171            .finish()
172    }
173}
174
175impl ExecutionFinalResult {
176    pub(crate) fn from_view(view: FinalExecutionOutcomeView) -> Self {
177        let total_gas_burnt = view.transaction_outcome.outcome.gas_burnt
178            + view
179                .receipts_outcome
180                .iter()
181                .map(|t| t.outcome.gas_burnt)
182                .sum::<u64>();
183
184        let transaction = view.transaction_outcome.into();
185        let receipts = view
186            .receipts_outcome
187            .into_iter()
188            .map(ExecutionOutcome::from)
189            .collect();
190
191        let total_gas_burnt = UncGas::from_gas(total_gas_burnt);
192        Self {
193            total_gas_burnt,
194            status: view.status,
195            details: ExecutionDetails {
196                transaction,
197                receipts,
198            },
199        }
200    }
201
202    /// Converts this object into a [`Result`] holding either [`ExecutionSuccess`] or [`ExecutionFailure`].
203    #[allow(clippy::result_large_err)]
204    pub fn into_result(self) -> Result<ExecutionSuccess, ExecutionFailure> {
205        match self.status {
206            FinalExecutionStatus::SuccessValue(value) => Ok(ExecutionResult {
207                total_gas_burnt: self.total_gas_burnt,
208                value: Value::from_string(general_purpose::STANDARD.encode(value)),
209                details: self.details,
210            }),
211            FinalExecutionStatus::Failure(tx_error) => Err(ExecutionResult {
212                total_gas_burnt: self.total_gas_burnt,
213                value: tx_error,
214                details: self.details,
215            }),
216            _ => unreachable!(),
217        }
218    }
219
220    /// Returns the contained Ok value, consuming the self value.
221    ///
222    /// Because this function may panic, its use is generally discouraged. Instead, prefer
223    /// to call into [`into_result`] then pattern matching and handle the Err case explicitly.
224    ///
225    /// [`into_result`]: crate::result::ExecutionFinalResult::into_result
226    pub fn unwrap(self) -> ExecutionSuccess {
227        self.into_result().unwrap()
228    }
229
230    /// Deserialize an instance of type `T` from bytes of JSON text sourced from the
231    /// execution result of this call. This conversion can fail if the structure of
232    /// the internal state does not meet up with [`serde::de::DeserializeOwned`]'s
233    /// requirements.
234    pub fn json<T: serde::de::DeserializeOwned>(self) -> Result<T> {
235        let val = self.into_result()?;
236        match val.json() {
237            Err(err) => {
238                // This catches the case: `EOF while parsing a value at line 1 column 0`
239                // for a function that doesn't return anything; this is a more descriptive error.
240                if *err.kind() == ErrorKind::DataConversion && val.value.repr.is_empty() {
241                    return Err(ErrorKind::DataConversion.custom(
242                        "the function call returned an empty value, which cannot be parsed as JSON",
243                    ));
244                }
245
246                Err(err)
247            }
248            ok => ok,
249        }
250    }
251
252    /// Deserialize an instance of type `T` from bytes sourced from the execution
253    /// result. This conversion can fail if the structure of the internal state does
254    /// not meet up with [`borsh::BorshDeserialize`]'s requirements.
255    pub fn borsh<T: borsh::BorshDeserialize>(self) -> Result<T> {
256        self.into_result()?.borsh()
257    }
258
259    /// Grab the underlying raw bytes returned from calling into a contract's function.
260    /// If we want to deserialize these bytes into a rust datatype, use [`ExecutionResult::json`]
261    /// or [`ExecutionResult::borsh`] instead.
262    pub fn raw_bytes(self) -> Result<Vec<u8>> {
263        self.into_result()?.raw_bytes()
264    }
265
266    /// Checks whether the transaction was successful. Returns true if
267    /// the transaction has a status of [`FinalExecutionStatus::SuccessValue`].
268    pub fn is_success(&self) -> bool {
269        matches!(self.status, FinalExecutionStatus::SuccessValue(_))
270    }
271
272    /// Checks whether the transaction has failed. Returns true if
273    /// the transaction has a status of [`FinalExecutionStatus::Failure`].
274    pub fn is_failure(&self) -> bool {
275        matches!(self.status, FinalExecutionStatus::Failure(_))
276    }
277
278    /// Returns just the transaction outcome.
279    pub fn outcome(&self) -> &ExecutionOutcome {
280        self.details.outcome()
281    }
282
283    /// Grab all outcomes after the execution of the transaction. This includes outcomes
284    /// from the transaction and all the receipts it generated.
285    pub fn outcomes(&self) -> Vec<&ExecutionOutcome> {
286        self.details.outcomes()
287    }
288
289    /// Grab all outcomes after the execution of the transaction. This includes outcomes
290    /// only from receipts generated by this transaction.
291    pub fn receipt_outcomes(&self) -> &[ExecutionOutcome] {
292        self.details.receipt_outcomes()
293    }
294
295    /// Grab all outcomes that did not succeed the execution of this transaction. This
296    /// will also include the failures from receipts as well.
297    pub fn failures(&self) -> Vec<&ExecutionOutcome> {
298        self.details.failures()
299    }
300
301    /// Just like `failures`, grab only failed receipt outcomes.
302    pub fn receipt_failures(&self) -> Vec<&ExecutionOutcome> {
303        self.details.receipt_failures()
304    }
305
306    /// Grab all logs from both the transaction and receipt outcomes.
307    pub fn logs(&self) -> Vec<&str> {
308        self.details.logs()
309    }
310}
311
312impl ExecutionSuccess {
313    /// Deserialize an instance of type `T` from bytes of JSON text sourced from the
314    /// execution result of this call. This conversion can fail if the structure of
315    /// the internal state does not meet up with [`serde::de::DeserializeOwned`]'s
316    /// requirements.
317    pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T> {
318        self.value.json()
319    }
320
321    /// Deserialize an instance of type `T` from bytes sourced from the execution
322    /// result. This conversion can fail if the structure of the internal state does
323    /// not meet up with [`borsh::BorshDeserialize`]'s requirements.
324    pub fn borsh<T: borsh::BorshDeserialize>(&self) -> Result<T> {
325        self.value.borsh()
326    }
327
328    /// Grab the underlying raw bytes returned from calling into a contract's function.
329    /// If we want to deserialize these bytes into a rust datatype, use [`ExecutionResult::json`]
330    /// or [`ExecutionResult::borsh`] instead.
331    pub fn raw_bytes(&self) -> Result<Vec<u8>> {
332        self.value.raw_bytes()
333    }
334}
335
336impl<T> ExecutionResult<T> {
337    /// Returns just the transaction outcome.
338    pub fn outcome(&self) -> &ExecutionOutcome {
339        self.details.outcome()
340    }
341
342    /// Grab all outcomes after the execution of the transaction. This includes outcomes
343    /// from the transaction and all the receipts it generated.
344    pub fn outcomes(&self) -> Vec<&ExecutionOutcome> {
345        self.details.outcomes()
346    }
347
348    /// Grab all outcomes after the execution of the transaction. This includes outcomes
349    /// only from receipts generated by this transaction.
350    pub fn receipt_outcomes(&self) -> &[ExecutionOutcome] {
351        self.details.receipt_outcomes()
352    }
353
354    /// Grab all outcomes that did not succeed the execution of this transaction. This
355    /// will also include the failures from receipts as well.
356    pub fn failures(&self) -> Vec<&ExecutionOutcome> {
357        self.details.failures()
358    }
359
360    /// Just like `failures`, grab only failed receipt outcomes.
361    pub fn receipt_failures(&self) -> Vec<&ExecutionOutcome> {
362        self.details.receipt_failures()
363    }
364
365    /// Grab all logs from both the transaction and receipt outcomes.
366    pub fn logs(&self) -> Vec<&str> {
367        self.details.logs()
368    }
369}
370
371/// The result from a call into a View function. This contains the contents or
372/// the results from the view function call itself. The consumer of this object
373/// can choose how to deserialize its contents.
374#[derive(PartialEq, Eq, Clone, Debug)]
375#[non_exhaustive]
376pub struct ViewResultDetails {
377    /// Our result from our call into a view function.
378    pub result: Vec<u8>,
379    /// Logs generated from the view function.
380    pub logs: Vec<String>,
381}
382
383impl ViewResultDetails {
384    /// Deserialize an instance of type `T` from bytes of JSON text sourced from the
385    /// execution result of this call. This conversion can fail if the structure of
386    /// the internal state does not meet up with [`serde::de::DeserializeOwned`]'s
387    /// requirements.
388    pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T> {
389        serde_json::from_slice(&self.result).map_err(|e| ErrorKind::DataConversion.custom(e))
390    }
391
392    /// Deserialize an instance of type `T` from bytes sourced from this view call's
393    /// result. This conversion can fail if the structure of the internal state does
394    /// not meet up with [`borsh::BorshDeserialize`]'s requirements.
395    pub fn borsh<T: borsh::BorshDeserialize>(&self) -> Result<T> {
396        borsh::BorshDeserialize::try_from_slice(&self.result)
397            .map_err(|e| ErrorKind::DataConversion.custom(e))
398    }
399}
400
401impl From<CallResult> for ViewResultDetails {
402    fn from(result: CallResult) -> Self {
403        Self {
404            result: result.result,
405            logs: result.logs,
406        }
407    }
408}
409
410/// The execution outcome of a transaction. This type contains all data relevant to
411/// calling into a function, and getting the results back.
412#[derive(Clone, Debug, PartialEq, Eq)]
413#[non_exhaustive]
414pub struct ExecutionOutcome {
415    /// The hash of the transaction that generated this outcome.
416    pub transaction_hash: CryptoHash,
417    /// The hash of the block that generated this outcome.
418    pub block_hash: CryptoHash,
419    /// Logs from this transaction or receipt.
420    pub logs: Vec<String>,
421    /// Receipt IDs generated by this transaction or receipt.
422    pub receipt_ids: Vec<CryptoHash>,
423    /// The amount of the gas burnt by the given transaction or receipt.
424    pub gas_burnt: Gas,
425    /// The amount of tokens burnt corresponding to the burnt gas amount.
426    /// This value doesn't always equal to the `gas_burnt` multiplied by the gas price, because
427    /// the prepaid gas price might be lower than the actual gas price and it creates a deficit.
428    pub tokens_burnt: UncToken,
429    /// The id of the account on which the execution happens. For transaction this is signer_id,
430    /// for receipt this is receiver_id.
431    pub executor_id: AccountId,
432    /// Execution status. Contains the result in case of successful execution.
433    pub(crate) status: ExecutionStatusView,
434}
435
436impl ExecutionOutcome {
437    /// Checks whether this execution outcome was a success. Returns true if a success value or
438    /// receipt id is present.
439    pub fn is_success(&self) -> bool {
440        matches!(
441            self.status,
442            ExecutionStatusView::SuccessValue(_) | ExecutionStatusView::SuccessReceiptId(_)
443        )
444    }
445
446    /// Checks whether this execution outcome was a failure. Returns true if it failed with
447    /// an error or the execution state was unknown or pending.
448    pub fn is_failure(&self) -> bool {
449        matches!(
450            self.status,
451            ExecutionStatusView::Failure(_) | ExecutionStatusView::Unknown
452        )
453    }
454
455    /// Converts this [`ExecutionOutcome`] into a Result type to match against whether the
456    /// particular outcome has failed or not.
457    pub fn into_result(self) -> Result<ValueOrReceiptId> {
458        match self.status {
459            ExecutionStatusView::SuccessValue(value) => Ok(ValueOrReceiptId::Value(
460                Value::from_string(general_purpose::STANDARD.encode(value)),
461            )),
462            ExecutionStatusView::SuccessReceiptId(hash) => {
463                Ok(ValueOrReceiptId::ReceiptId(CryptoHash(hash.0)))
464            }
465            ExecutionStatusView::Failure(err) => Err(ErrorKind::Execution.custom(err)),
466            ExecutionStatusView::Unknown => {
467                Err(ErrorKind::Execution.message("Execution pending or unknown"))
468            }
469        }
470    }
471}
472
473/// Value or ReceiptId from a successful execution.
474#[derive(Debug)]
475pub enum ValueOrReceiptId {
476    /// The final action succeeded and returned some value or an empty vec encoded in base64.
477    Value(Value),
478    /// The final action of the receipt returned a promise or the signed transaction was converted
479    /// to a receipt. Contains the receipt_id of the generated receipt.
480    ReceiptId(CryptoHash),
481}
482
483/// Value type returned from an [`ExecutionOutcome`] or receipt result. This value
484/// can be converted into the underlying Rust datatype, or directly grab the raw
485/// bytes associated to the value.
486#[derive(Debug, Clone)]
487pub struct Value {
488    repr: String,
489}
490
491impl Value {
492    fn from_string(value: String) -> Self {
493        Self { repr: value }
494    }
495
496    /// Deserialize an instance of type `T` from bytes of JSON text sourced from the
497    /// execution result of this call. This conversion can fail if the structure of
498    /// the internal state does not meet up with [`serde::de::DeserializeOwned`]'s
499    /// requirements.
500    pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T> {
501        let buf = self.raw_bytes()?;
502        serde_json::from_slice(&buf).map_err(|e| ErrorKind::DataConversion.custom(e))
503    }
504
505    /// Deserialize an instance of type `T` from bytes sourced from the execution
506    /// result. This conversion can fail if the structure of the internal state does
507    /// not meet up with [`borsh::BorshDeserialize`]'s requirements.
508    pub fn borsh<T: borsh::BorshDeserialize>(&self) -> Result<T> {
509        let buf = self.raw_bytes()?;
510        borsh::BorshDeserialize::try_from_slice(&buf)
511            .map_err(|e| ErrorKind::DataConversion.custom(e))
512    }
513
514    /// Grab the underlying raw bytes returned from calling into a contract's function.
515    /// If we want to deserialize these bytes into a rust datatype, use [`json`]
516    /// or [`borsh`] instead.
517    ///
518    /// [`json`]: Value::json
519    /// [`borsh`]: Value::borsh
520    pub fn raw_bytes(&self) -> Result<Vec<u8>> {
521        general_purpose::STANDARD
522            .decode(&self.repr)
523            .map_err(|e| ErrorKind::DataConversion.custom(e))
524    }
525}
526
527impl From<ExecutionOutcomeWithIdView> for ExecutionOutcome {
528    fn from(view: ExecutionOutcomeWithIdView) -> Self {
529        Self {
530            transaction_hash: CryptoHash(view.id.0),
531            block_hash: CryptoHash(view.block_hash.0),
532            logs: view.outcome.logs,
533            receipt_ids: view
534                .outcome
535                .receipt_ids
536                .into_iter()
537                .map(|c| CryptoHash(c.0))
538                .collect(),
539            gas_burnt: UncGas::from_gas(view.outcome.gas_burnt),
540            tokens_burnt: UncToken::from_attounc(view.outcome.tokens_burnt),
541            executor_id: view.outcome.executor_id,
542            status: view.outcome.status,
543        }
544    }
545}