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