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}