Skip to main content

near_api_types/transaction/
result.rs

1//! Result and execution types from results of RPC calls to the network.
2
3use std::fmt;
4
5use base64::{Engine as _, engine::general_purpose};
6use borsh;
7use near_openapi_types::{
8    CallResult, ExecutionStatusView, FinalExecutionOutcomeView, FinalExecutionStatus,
9    TxExecutionError, TxExecutionStatus,
10};
11
12use crate::{
13    AccountId, CryptoHash, NearGas, NearToken, Signature,
14    errors::{DataConversionError, ExecutionError},
15    transaction::{SignedTransaction, Transaction},
16};
17
18/// Execution related info as a result of performing a successful transaction
19/// execution on the network.
20///
21/// This value can be converted into the returned
22/// value of the transaction via [`ExecutionSuccess::json`] or [`ExecutionSuccess::borsh`]
23pub type ExecutionSuccess = ExecutionResult<Value>;
24
25/// Execution related info as a result of performing a failed transaction
26/// execution on the network. The related error message can be retrieved
27/// from this object or can be forwarded.
28pub type ExecutionFailure = ExecutionResult<TxExecutionError>;
29
30/// Struct to hold a type we want to return along w/ the execution result view.
31///
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    #[track_caller]
43    pub fn assert_success(self) -> T {
44        #[allow(clippy::unwrap_used)]
45        self.into_result().unwrap()
46    }
47
48    #[allow(clippy::result_large_err)]
49    pub fn into_result(self) -> Result<T, ExecutionFailure> {
50        self.details.into_result()?;
51        Ok(self.result)
52    }
53
54    /// Checks whether the transaction was successful. Returns true if
55    /// the transaction has a status of FinalExecutionStatus::Success.
56    pub const fn is_success(&self) -> bool {
57        self.details.is_success()
58    }
59
60    /// Checks whether the transaction has failed. Returns true if
61    /// the transaction has a status of FinalExecutionStatus::Failure.
62    pub const fn is_failure(&self) -> bool {
63        self.details.is_failure()
64    }
65}
66
67/// The transaction/receipt details of a transaction execution. This object
68/// can be used to retrieve data such as logs and gas burnt per transaction
69/// or receipt.
70#[derive(Clone)]
71pub(crate) struct ExecutionDetails {
72    pub(crate) transaction_outcome: ExecutionOutcome,
73    pub(crate) transaction: SignedTransaction,
74    pub(crate) receipts: Vec<ExecutionOutcome>,
75}
76
77impl ExecutionDetails {
78    /// Returns just the transaction outcome.
79    pub const fn outcome(&self) -> &ExecutionOutcome {
80        &self.transaction_outcome
81    }
82
83    pub const fn transaction(&self) -> &Transaction {
84        &self.transaction.transaction
85    }
86
87    pub const fn signature(&self) -> &Signature {
88        &self.transaction.signature
89    }
90
91    /// Grab all outcomes after the execution of the transaction. This includes outcomes
92    /// from the transaction and all the receipts it generated.
93    pub fn outcomes(&self) -> Vec<&ExecutionOutcome> {
94        let mut outcomes = vec![&self.transaction_outcome];
95        outcomes.extend(self.receipt_outcomes());
96        outcomes
97    }
98
99    /// Grab all outcomes after the execution of the transaction. This includes outcomes
100    /// only from receipts generated by this transaction.
101    pub fn receipt_outcomes(&self) -> &[ExecutionOutcome] {
102        &self.receipts
103    }
104
105    /// Grab all outcomes that did not succeed the execution of this transaction. This
106    /// will also include the failures from receipts as well.
107    pub fn failures(&self) -> Vec<&ExecutionOutcome> {
108        let mut failures = Vec::new();
109        if matches!(
110            self.transaction_outcome.status,
111            ExecutionStatusView::Failure(_)
112        ) {
113            failures.push(&self.transaction_outcome);
114        }
115        failures.extend(self.receipt_failures());
116        failures
117    }
118
119    /// Just like `failures`, grab only failed receipt outcomes.
120    pub fn receipt_failures(&self) -> Vec<&ExecutionOutcome> {
121        self.receipts
122            .iter()
123            .filter(|receipt| matches!(receipt.status, ExecutionStatusView::Failure(_)))
124            .collect()
125    }
126
127    /// Grab all logs from both the transaction and receipt outcomes.
128    pub fn logs(&self) -> Vec<&str> {
129        self.outcomes()
130            .iter()
131            .flat_map(|outcome| &outcome.logs)
132            .map(String::as_str)
133            .collect()
134    }
135}
136
137/// The result after evaluating the status of an execution. This can be [`ExecutionSuccess`]
138/// for successful executions or a [`ExecutionFailure`] for failed ones.
139#[derive(Clone)]
140#[non_exhaustive]
141pub struct ExecutionResult<T> {
142    /// Total gas burnt by the execution
143    pub total_gas_burnt: NearGas,
144
145    /// Value returned from an execution. This is a base64 encoded str for a successful
146    /// execution or a `TxExecutionError` if a failed one.
147    pub(crate) value: T,
148    // pub(crate) transaction: ExecutionOutcome,
149    // pub(crate) receipts: Vec<ExecutionOutcome>,
150    pub(crate) details: ExecutionDetails,
151}
152
153impl<T: fmt::Debug> fmt::Debug for ExecutionResult<T> {
154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        f.debug_struct("ExecutionResult")
156            .field("total_gas_burnt", &self.total_gas_burnt)
157            .field("transaction", &self.details.transaction)
158            .field("receipts", &self.details.receipts)
159            .field("value", &self.value)
160            .finish()
161    }
162}
163
164impl fmt::Display for ExecutionResult<TxExecutionError> {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        write!(f, "ExecutionFailure: {:?}", self.value)
167    }
168}
169
170// Might be a good idea to consider wrapping this into thiserror as we do for other errors in the project
171// Though, to not introduce breaking change we will just mark it as error for now
172impl std::error::Error for ExecutionResult<TxExecutionError> {}
173
174/// Execution related info found after performing a transaction. Can be converted
175/// into [`ExecutionSuccess`] or [`ExecutionFailure`] through [`into_result`](ExecutionFinalResult::into_result)
176#[derive(Clone)]
177#[must_use = "use `into_result()` to handle potential execution errors"]
178pub struct ExecutionFinalResult {
179    /// Total gas burnt by the execution
180    pub total_gas_burnt: NearGas,
181
182    pub(crate) status: FinalExecutionStatus,
183    pub(crate) details: ExecutionDetails,
184}
185
186impl fmt::Debug for ExecutionFinalResult {
187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        f.debug_struct("ExecutionFinalResult")
189            .field("total_gas_burnt", &self.total_gas_burnt)
190            .field("transaction", &self.details.transaction)
191            .field("receipts", &self.details.receipts)
192            .field("status", &self.status)
193            .finish()
194    }
195}
196
197impl TryFrom<FinalExecutionOutcomeView> for ExecutionFinalResult {
198    type Error = DataConversionError;
199    fn try_from(view: FinalExecutionOutcomeView) -> Result<Self, Self::Error> {
200        let FinalExecutionOutcomeView {
201            receipts_outcome,
202            status,
203            transaction,
204            transaction_outcome,
205        } = view;
206
207        let total_gas_burnt = transaction_outcome.outcome.gas_burnt.as_gas()
208            + receipts_outcome
209                .iter()
210                .map(|t| t.outcome.gas_burnt.as_gas())
211                .sum::<u64>();
212
213        let transaction_outcome = transaction_outcome.into();
214        let receipts = receipts_outcome
215            .into_iter()
216            .map(ExecutionOutcome::from)
217            .collect();
218
219        let total_gas_burnt = NearGas::from_gas(total_gas_burnt);
220        Ok(Self {
221            total_gas_burnt,
222            status,
223            details: ExecutionDetails {
224                transaction_outcome,
225                transaction: SignedTransaction::try_from(transaction)?,
226                receipts,
227            },
228        })
229    }
230}
231
232impl ExecutionFinalResult {
233    /// Converts this object into a [`Result`] holding either [`ExecutionSuccess`] or [`ExecutionFailure`].
234    #[allow(clippy::result_large_err)]
235    pub fn into_result(self) -> Result<ExecutionSuccess, ExecutionFailure> {
236        match self.status {
237            FinalExecutionStatus::SuccessValue(value) => Ok(ExecutionResult {
238                total_gas_burnt: self.total_gas_burnt,
239                value: Value::from_string(value),
240                details: self.details,
241            }),
242            FinalExecutionStatus::Failure(tx_error) => Err(ExecutionResult {
243                total_gas_burnt: self.total_gas_burnt,
244                value: tx_error,
245                details: self.details,
246            }),
247            FinalExecutionStatus::NotStarted | FinalExecutionStatus::Started => {
248                panic!(
249                    "called `into_result()` on a transaction that is still pending \
250                     (status: {:?}). Use `is_pending()` to check before calling this method, \
251                     or use `Transaction::status_with_options()` with a `wait_until` value \
252                     of `ExecutedOptimistic` or higher.",
253                    self.status
254                )
255            }
256        }
257    }
258
259    /// Returns the contained Ok value, consuming the self value.
260    ///
261    /// Because this function may panic, its use is generally discouraged. Instead, prefer
262    /// to call into [`into_result`](ExecutionFinalResult::into_result) then pattern matching and handle the Err case explicitly.
263    #[track_caller]
264    pub fn assert_success(self) -> ExecutionSuccess {
265        #[allow(clippy::unwrap_used)]
266        self.into_result().unwrap()
267    }
268
269    #[track_caller]
270    pub fn assert_failure(self) -> ExecutionResult<TxExecutionError> {
271        #[allow(clippy::unwrap_used)]
272        self.into_result().unwrap_err()
273    }
274
275    /// Deserialize an instance of type `T` from bytes of JSON text sourced from the
276    /// execution result of this call. This conversion can fail if the structure of
277    /// the internal state does not meet up with [`serde::de::DeserializeOwned`]'s
278    /// requirements.
279    pub fn json<T: serde::de::DeserializeOwned>(self) -> Result<T, ExecutionError> {
280        if self.is_pending() {
281            return Err(ExecutionError::ExecutionPendingOrUnknown);
282        }
283
284        let val = self.into_result()?;
285        match val.json() {
286            Err(err) => {
287                // This catches the case: `EOF while parsing a value at line 1 column 0`
288                // for a function that doesn't return anything; this is a more descriptive error.
289                if matches!(
290                    err,
291                    ExecutionError::DataConversionError(
292                        DataConversionError::JsonDeserializationError(_)
293                    )
294                ) && val.value.repr.is_empty()
295                {
296                    return Err(ExecutionError::EofWhileParsingValue);
297                }
298
299                Err(err)
300            }
301            ok => ok,
302        }
303    }
304
305    /// Deserialize an instance of type `T` from bytes sourced from the execution
306    /// result. This conversion can fail if the structure of the internal state does
307    /// not meet up with [`borsh::BorshDeserialize`]'s requirements.
308    pub fn borsh<T: borsh::BorshDeserialize>(self) -> Result<T, ExecutionError> {
309        if self.is_pending() {
310            return Err(ExecutionError::ExecutionPendingOrUnknown);
311        }
312
313        self.into_result()?.borsh()
314    }
315
316    /// Grab the underlying raw bytes returned from calling into a contract's function.
317    /// If we want to deserialize these bytes into a rust datatype, use [`ExecutionResult::json`]
318    /// or [`ExecutionResult::borsh`] instead.
319    pub fn raw_bytes(self) -> Result<Vec<u8>, ExecutionError> {
320        if self.is_pending() {
321            return Err(ExecutionError::ExecutionPendingOrUnknown);
322        }
323
324        self.into_result()?.raw_bytes()
325    }
326
327    /// Checks whether the transaction was successful. Returns true if
328    /// the transaction has a status of [`FinalExecutionStatus::SuccessValue`].
329    pub const fn is_success(&self) -> bool {
330        matches!(self.status, FinalExecutionStatus::SuccessValue(_))
331    }
332
333    /// Checks whether the transaction has failed. Returns true if
334    /// the transaction has a status of [`FinalExecutionStatus::Failure`].
335    pub const fn is_failure(&self) -> bool {
336        matches!(self.status, FinalExecutionStatus::Failure(_))
337    }
338
339    /// Checks whether the transaction execution is still pending (not started or in progress).
340    ///
341    /// Returns `true` if the status is [`FinalExecutionStatus::NotStarted`] or
342    /// [`FinalExecutionStatus::Started`]. When this returns `true`, calling
343    /// [`into_result`](Self::into_result), [`json`](Self::json), [`borsh`](Self::borsh),
344    /// or [`raw_bytes`](Self::raw_bytes) will fail.
345    pub const fn is_pending(&self) -> bool {
346        matches!(
347            self.status,
348            FinalExecutionStatus::NotStarted | FinalExecutionStatus::Started
349        )
350    }
351
352    /// Returns just the transaction outcome.
353    pub const fn outcome(&self) -> &ExecutionOutcome {
354        self.details.outcome()
355    }
356
357    /// Returns the transaction that was executed.
358    pub const fn transaction(&self) -> &Transaction {
359        self.details.transaction()
360    }
361
362    /// Grab all outcomes after the execution of the transaction. This includes outcomes
363    /// from the transaction and all the receipts it generated.
364    pub fn outcomes(&self) -> Vec<&ExecutionOutcome> {
365        self.details.outcomes()
366    }
367
368    /// Grab all outcomes after the execution of the transaction. This includes outcomes
369    /// only from receipts generated by this transaction.
370    pub fn receipt_outcomes(&self) -> &[ExecutionOutcome] {
371        self.details.receipt_outcomes()
372    }
373
374    /// Grab all outcomes that did not succeed the execution of this transaction. This
375    /// will also include the failures from receipts as well.
376    pub fn failures(&self) -> Vec<&ExecutionOutcome> {
377        self.details.failures()
378    }
379
380    /// Just like `failures`, grab only failed receipt outcomes.
381    pub fn receipt_failures(&self) -> Vec<&ExecutionOutcome> {
382        self.details.receipt_failures()
383    }
384
385    /// Grab all logs from both the transaction and receipt outcomes.
386    pub fn logs(&self) -> Vec<&str> {
387        self.details.logs()
388    }
389}
390
391/// The result of sending a transaction to the network.
392///
393/// Depending on the [`TxExecutionStatus`] used with `wait_until`, the RPC may return
394/// either a full execution result or just a confirmation that the transaction was received.
395///
396/// - `wait_until(TxExecutionStatus::None)` or `wait_until(TxExecutionStatus::Included)` will
397///   return [`TransactionResult::Pending`] since the transaction hasn't been executed yet.
398/// - Higher finality levels (`ExecutedOptimistic`, `Final`, etc.) will return
399///   [`TransactionResult::Full`] with the full execution outcome.
400#[derive(Clone, Debug)]
401#[must_use = "use `into_result()` to handle potential execution errors and cases when transaction is pending"]
402pub enum TransactionResult {
403    /// Transaction was submitted but execution results are not yet available.
404    ///
405    /// This is returned when `wait_until` is set to `None` or `Included`.
406    /// The `status` field indicates how far the transaction has progressed.
407    Pending { status: TxExecutionStatus },
408    /// Full execution result is available.
409    Full(Box<ExecutionFinalResult>),
410}
411
412impl TransactionResult {
413    /// Returns the full execution result if available, or an error if the transaction is still pending.
414    #[allow(clippy::result_large_err)]
415    pub fn into_result(self) -> Result<ExecutionSuccess, TransactionResultError> {
416        match self {
417            Self::Full(result) => result
418                .into_result()
419                .map_err(|e| TransactionResultError::Failure(Box::new(e))),
420            Self::Pending { status } => Err(TransactionResultError::Pending(status)),
421        }
422    }
423
424    /// Unwraps the full execution result, panicking if the transaction is pending or failed.
425    #[track_caller]
426    pub fn assert_success(self) -> ExecutionSuccess {
427        match self {
428            Self::Full(result) => result.assert_success(),
429            Self::Pending { status } => panic!(
430                "called `assert_success()` on a pending transaction (status: {status:?}). \
431                 Use wait_until(TxExecutionStatus::Final) or handle the pending case."
432            ),
433        }
434    }
435
436    /// Returns `true` if the transaction has a full execution result.
437    pub const fn is_full(&self) -> bool {
438        matches!(self, Self::Full(_))
439    }
440
441    /// Returns `true` if the transaction is still pending.
442    pub const fn is_pending(&self) -> bool {
443        matches!(self, Self::Pending { .. })
444    }
445
446    /// Returns the full execution result, if available.
447    pub fn into_full(self) -> Option<ExecutionFinalResult> {
448        match self {
449            Self::Full(result) => Some(*result),
450            Self::Pending { .. } => None,
451        }
452    }
453
454    /// Returns the pending status, if the transaction is still pending.
455    pub fn pending_status(self) -> Option<TxExecutionStatus> {
456        match self {
457            Self::Pending { status } => Some(status),
458            Self::Full(_) => None,
459        }
460    }
461
462    /// Unwraps the execution failure, panicking if the transaction is pending or succeeded.
463    #[track_caller]
464    pub fn assert_failure(self) -> ExecutionResult<TxExecutionError> {
465        match self {
466            Self::Full(result) => result.assert_failure(),
467            Self::Pending { status } => panic!(
468                "called `assert_failure()` on a pending transaction (status: {status:?}). \
469                 Use wait_until(TxExecutionStatus::Final) or handle the pending case."
470            ),
471        }
472    }
473
474    /// Checks whether the transaction has failed. Returns `false` if the transaction
475    /// is still pending or succeeded.
476    pub const fn is_failure(&self) -> bool {
477        match self {
478            Self::Full(result) => result.is_failure(),
479            Self::Pending { .. } => false,
480        }
481    }
482
483    /// Checks whether the transaction was successful. Returns `false` if the transaction
484    /// is still pending or failed.
485    pub const fn is_success(&self) -> bool {
486        match self {
487            Self::Full(result) => result.is_success(),
488            Self::Pending { .. } => false,
489        }
490    }
491
492    /// Returns the transaction that was executed.
493    ///
494    /// # Panics
495    ///
496    /// Panics if the transaction is still pending.
497    #[track_caller]
498    pub fn transaction(&self) -> &Transaction {
499        match self {
500            Self::Full(result) => result.transaction(),
501            Self::Pending { status } => panic!(
502                "called `transaction()` on a pending transaction (status: {status:?}). \
503                 Use wait_until(TxExecutionStatus::Final) or handle the pending case."
504            ),
505        }
506    }
507
508    /// Grab all logs from both the transaction and receipt outcomes.
509    ///
510    /// # Panics
511    ///
512    /// Panics if the transaction is still pending.
513    #[track_caller]
514    pub fn logs(&self) -> Vec<&str> {
515        match self {
516            Self::Full(result) => result.logs(),
517            Self::Pending { status } => panic!(
518                "called `logs()` on a pending transaction (status: {status:?}). \
519                 Use wait_until(TxExecutionStatus::Final) or handle the pending case."
520            ),
521        }
522    }
523}
524
525/// Error type for [`TransactionResult::into_result`].
526#[derive(Debug)]
527pub enum TransactionResultError {
528    /// The transaction failed execution.
529    Failure(Box<ExecutionFailure>),
530    /// The transaction is still pending (was sent with `wait_until` set to `None` or `Included`).
531    Pending(TxExecutionStatus),
532}
533
534impl fmt::Display for TransactionResultError {
535    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
536        match self {
537            Self::Failure(err) => write!(f, "Transaction failed: {err}"),
538            Self::Pending(status) => write!(
539                f,
540                "Transaction is pending (status: {status:?}). \
541                 Execution results are not yet available."
542            ),
543        }
544    }
545}
546
547impl std::error::Error for TransactionResultError {}
548
549impl ExecutionSuccess {
550    /// Deserialize an instance of type `T` from bytes of JSON text sourced from the
551    /// execution result of this call. This conversion can fail if the structure of
552    /// the internal state does not meet up with [`serde::de::DeserializeOwned`]'s
553    /// requirements.
554    pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T, ExecutionError> {
555        Ok(self.value.json()?)
556    }
557
558    /// Deserialize an instance of type `T` from bytes sourced from the execution
559    /// result. This conversion can fail if the structure of the internal state does
560    /// not meet up with [`borsh::BorshDeserialize`]'s requirements.
561    pub fn borsh<T: borsh::BorshDeserialize>(&self) -> Result<T, ExecutionError> {
562        Ok(self.value.borsh()?)
563    }
564
565    /// Grab the underlying raw bytes returned from calling into a contract's function.
566    /// If we want to deserialize these bytes into a rust datatype, use [`ExecutionResult::json`]
567    /// or [`ExecutionResult::borsh`] instead.
568    pub fn raw_bytes(&self) -> Result<Vec<u8>, ExecutionError> {
569        Ok(self.value.raw_bytes()?)
570    }
571}
572
573impl<T> ExecutionResult<T> {
574    /// Returns just the transaction outcome.
575    pub const fn outcome(&self) -> &ExecutionOutcome {
576        self.details.outcome()
577    }
578
579    /// Returns the transaction that was executed.
580    pub const fn transaction(&self) -> &Transaction {
581        self.details.transaction()
582    }
583
584    pub const fn signature(&self) -> &Signature {
585        self.details.signature()
586    }
587
588    /// Grab all outcomes after the execution of the transaction. This includes outcomes
589    /// from the transaction and all the receipts it generated.
590    pub fn outcomes(&self) -> Vec<&ExecutionOutcome> {
591        self.details.outcomes()
592    }
593
594    /// Grab all outcomes after the execution of the transaction. This includes outcomes
595    /// only from receipts generated by this transaction.
596    pub fn receipt_outcomes(&self) -> &[ExecutionOutcome] {
597        self.details.receipt_outcomes()
598    }
599
600    /// Grab all outcomes that did not succeed the execution of this transaction. This
601    /// will also include the failures from receipts as well.
602    pub fn failures(&self) -> Vec<&ExecutionOutcome> {
603        self.details.failures()
604    }
605
606    /// Just like `failures`, grab only failed receipt outcomes.
607    pub fn receipt_failures(&self) -> Vec<&ExecutionOutcome> {
608        self.details.receipt_failures()
609    }
610
611    /// Grab all logs from both the transaction and receipt outcomes.
612    pub fn logs(&self) -> Vec<&str> {
613        self.details.logs()
614    }
615}
616
617/// The result from a call into a View function. This contains the contents or
618/// the results from the view function call itself. The consumer of this object
619/// can choose how to deserialize its contents.
620#[derive(PartialEq, Eq, Clone, Debug)]
621#[non_exhaustive]
622pub struct ViewResultDetails {
623    /// Our result from our call into a view function.
624    pub result: Vec<u8>,
625    /// Logs generated from the view function.
626    pub logs: Vec<String>,
627}
628
629impl ViewResultDetails {
630    /// Deserialize an instance of type `T` from bytes of JSON text sourced from the
631    /// execution result of this call. This conversion can fail if the structure of
632    /// the internal state does not meet up with [`serde::de::DeserializeOwned`]'s
633    /// requirements.
634    pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T, DataConversionError> {
635        Ok(serde_json::from_slice(&self.result)?)
636    }
637
638    /// Deserialize an instance of type `T` from bytes sourced from this view call's
639    /// result. This conversion can fail if the structure of the internal state does
640    /// not meet up with [`borsh::BorshDeserialize`]'s requirements.
641    pub fn borsh<T: borsh::BorshDeserialize>(&self) -> Result<T, DataConversionError> {
642        Ok(borsh::BorshDeserialize::try_from_slice(&self.result)?)
643    }
644}
645
646impl From<CallResult> for ViewResultDetails {
647    fn from(result: CallResult) -> Self {
648        Self {
649            result: result.result,
650            logs: result.logs,
651        }
652    }
653}
654
655/// The execution outcome of a transaction. This type contains all data relevant to
656/// calling into a function, and getting the results back.
657#[derive(Clone, Debug)]
658#[non_exhaustive]
659pub struct ExecutionOutcome {
660    /// The hash of the transaction that generated this outcome.
661    pub transaction_hash: CryptoHash,
662    /// The hash of the block that generated this outcome.
663    pub block_hash: CryptoHash,
664    /// Logs from this transaction or receipt.
665    pub logs: Vec<String>,
666    /// Receipt IDs generated by this transaction or receipt.
667    pub receipt_ids: Vec<CryptoHash>,
668    /// The amount of the gas burnt by the given transaction or receipt.
669    pub gas_burnt: NearGas,
670    /// The amount of tokens burnt corresponding to the burnt gas amount.
671    /// This value doesn't always equal to the `gas_burnt` multiplied by the gas price, because
672    /// the prepaid gas price might be lower than the actual gas price and it creates a deficit.
673    pub tokens_burnt: NearToken,
674    /// The id of the account on which the execution happens. For transaction this is signer_id,
675    /// for receipt this is receiver_id.
676    pub executor_id: AccountId,
677
678    /// Execution status. Contains the result in case of successful execution.
679    pub(crate) status: ExecutionStatusView,
680}
681
682impl ExecutionOutcome {
683    /// Checks whether this execution outcome was a success. Returns true if a success value or
684    /// receipt id is present.
685    pub const fn is_success(&self) -> bool {
686        matches!(
687            self.status,
688            ExecutionStatusView::SuccessValue(_) | ExecutionStatusView::SuccessReceiptId(_)
689        )
690    }
691
692    /// Checks whether this execution outcome was a failure. Returns true if it failed with
693    /// an error or the execution state was unknown or pending.
694    pub const fn is_failure(&self) -> bool {
695        matches!(
696            self.status,
697            ExecutionStatusView::Failure(_) | ExecutionStatusView::Unknown
698        )
699    }
700
701    /// Converts this [`ExecutionOutcome`] into a Result type to match against whether the
702    /// particular outcome has failed or not.
703    pub fn into_result(self) -> Result<ValueOrReceiptId, ExecutionError> {
704        match self.status {
705            ExecutionStatusView::SuccessValue(value) => {
706                Ok(ValueOrReceiptId::Value(Value::from_string(value)))
707            }
708            ExecutionStatusView::SuccessReceiptId(hash) => {
709                Ok(ValueOrReceiptId::ReceiptId(hash.into()))
710            }
711            ExecutionStatusView::Failure(err) => {
712                Err(ExecutionError::TransactionExecutionFailed(Box::new(err)))
713            }
714            ExecutionStatusView::Unknown => Err(ExecutionError::ExecutionPendingOrUnknown),
715        }
716    }
717}
718
719/// Value or ReceiptId from a successful execution.
720#[derive(Debug)]
721pub enum ValueOrReceiptId {
722    /// The final action succeeded and returned some value or an empty vec encoded in base64.
723    Value(Value),
724    /// The final action of the receipt returned a promise or the signed transaction was converted
725    /// to a receipt. Contains the receipt_id of the generated receipt.
726    ReceiptId(CryptoHash),
727}
728
729/// Value type returned from an [`ExecutionOutcome`] or receipt result. This value
730/// can be converted into the underlying Rust datatype, or directly grab the raw
731/// bytes associated to the value.
732#[derive(Debug, Clone)]
733pub struct Value {
734    repr: String,
735}
736
737impl Value {
738    const fn from_string(value: String) -> Self {
739        Self { repr: value }
740    }
741
742    /// Deserialize an instance of type `T` from bytes of JSON text sourced from the
743    /// execution result of this call. This conversion can fail if the structure of
744    /// the internal state does not meet up with [`serde::de::DeserializeOwned`]'s
745    /// requirements.
746    pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T, DataConversionError> {
747        let buf = self.raw_bytes()?;
748        Ok(serde_json::from_slice(&buf)?)
749    }
750
751    /// Deserialize an instance of type `T` from bytes sourced from the execution
752    /// result. This conversion can fail if the structure of the internal state does
753    /// not meet up with [`borsh::BorshDeserialize`]'s requirements.
754    pub fn borsh<T: borsh::BorshDeserialize>(&self) -> Result<T, DataConversionError> {
755        let buf = self.raw_bytes()?;
756        Ok(borsh::BorshDeserialize::try_from_slice(&buf)?)
757    }
758
759    /// Grab the underlying raw bytes returned from calling into a contract's function.
760    /// If we want to deserialize these bytes into a rust datatype, use [`json`]
761    /// or [`borsh`] instead.
762    ///
763    /// [`json`]: Value::json
764    /// [`borsh`]: Value::borsh
765    pub fn raw_bytes(&self) -> Result<Vec<u8>, DataConversionError> {
766        Ok(general_purpose::STANDARD.decode(&self.repr)?)
767    }
768}
769
770impl From<near_openapi_types::ExecutionOutcomeWithIdView> for ExecutionOutcome {
771    fn from(view: near_openapi_types::ExecutionOutcomeWithIdView) -> Self {
772        let near_openapi_types::ExecutionOutcomeWithIdView {
773            id,
774            block_hash,
775            outcome,
776            proof: _, // TODO: research if we need this
777        } = view;
778
779        Self {
780            transaction_hash: id.into(),
781            block_hash: block_hash.into(),
782            logs: outcome.logs,
783            receipt_ids: outcome
784                .receipt_ids
785                .into_iter()
786                .map(CryptoHash::from)
787                .collect(),
788            gas_burnt: outcome.gas_burnt,
789            tokens_burnt: outcome.tokens_burnt,
790            executor_id: outcome.executor_id,
791            status: outcome.status,
792        }
793    }
794}