near-workspaces 0.7.0

Library for automating workflows and testing NEAR smart contracts.
Documentation
//! Result and execution types from results of RPC calls to the network.

use std::fmt;

use near_account_id::AccountId;
use near_primitives::errors::TxExecutionError;
use near_primitives::views::{
    CallResult, ExecutionOutcomeWithIdView, ExecutionStatusView, FinalExecutionOutcomeView,
    FinalExecutionStatus,
};

use crate::error::ErrorKind;
use crate::types::{Balance, CryptoHash, Gas};

pub type Result<T, E = crate::error::Error> = core::result::Result<T, E>;

/// Execution related info as a result of performing a successful transaction
/// execution on the network. This value can be converted into the returned
/// value of the transaction via [`ExecutionSuccess::json`] or [`ExecutionSuccess::borsh`]
pub type ExecutionSuccess = ExecutionResult<Value>;

/// Execution related info as a result of performing a failed transaction
/// execution on the network. The related error message can be retrieved
/// from this object or can be forwarded.
pub type ExecutionFailure = ExecutionResult<TxExecutionError>;

/// Struct to hold a type we want to return along w/ the execution result view.
/// This view has extra info about the execution, such as gas usage and whether
/// the transaction failed to be processed on the chain.
#[non_exhaustive]
#[must_use = "use `into_result()` to handle potential execution errors"]
pub struct Execution<T> {
    pub result: T,
    pub details: ExecutionFinalResult,
}

impl<T> Execution<T> {
    pub fn unwrap(self) -> T {
        self.into_result().unwrap()
    }

    #[allow(clippy::result_large_err)]
    pub fn into_result(self) -> Result<T, ExecutionFailure> {
        self.details.into_result()?;
        Ok(self.result)
    }

    /// Checks whether the transaction was successful. Returns true if
    /// the transaction has a status of FinalExecutionStatus::Success.
    pub fn is_success(&self) -> bool {
        self.details.is_success()
    }

    /// Checks whether the transaction has failed. Returns true if
    /// the transaction has a status of FinalExecutionStatus::Failure.
    pub fn is_failure(&self) -> bool {
        self.details.is_failure()
    }
}

/// The transaction/receipt details of a transaction execution. This object
/// can be used to retrieve data such as logs and gas burnt per transaction
/// or receipt.
#[derive(PartialEq, Eq, Clone)]
pub(crate) struct ExecutionDetails {
    pub(crate) transaction: ExecutionOutcome,
    pub(crate) receipts: Vec<ExecutionOutcome>,
}

impl ExecutionDetails {
    /// Returns just the transaction outcome.
    pub fn outcome(&self) -> &ExecutionOutcome {
        &self.transaction
    }

    /// Grab all outcomes after the execution of the transaction. This includes outcomes
    /// from the transaction and all the receipts it generated.
    pub fn outcomes(&self) -> Vec<&ExecutionOutcome> {
        let mut outcomes = vec![&self.transaction];
        outcomes.extend(self.receipt_outcomes());
        outcomes
    }

    /// Grab all outcomes after the execution of the transaction. This includes outcomes
    /// only from receipts generated by this transaction.
    pub fn receipt_outcomes(&self) -> &[ExecutionOutcome] {
        &self.receipts
    }

    /// Grab all outcomes that did not succeed the execution of this transaction. This
    /// will also include the failures from receipts as well.
    pub fn failures(&self) -> Vec<&ExecutionOutcome> {
        let mut failures = Vec::new();
        if matches!(self.transaction.status, ExecutionStatusView::Failure(_)) {
            failures.push(&self.transaction);
        }
        failures.extend(self.receipt_failures());
        failures
    }

    /// Just like `failures`, grab only failed receipt outcomes.
    pub fn receipt_failures(&self) -> Vec<&ExecutionOutcome> {
        self.receipts
            .iter()
            .filter(|receipt| matches!(receipt.status, ExecutionStatusView::Failure(_)))
            .collect()
    }

    /// Grab all logs from both the transaction and receipt outcomes.
    pub fn logs(&self) -> Vec<&str> {
        self.outcomes()
            .iter()
            .flat_map(|outcome| &outcome.logs)
            .map(String::as_str)
            .collect()
    }
}

/// The result after evaluating the status of an execution. This can be [`ExecutionSuccess`]
/// for successful executions or a [`ExecutionFailure`] for failed ones.
#[derive(PartialEq, Eq, Clone)]
#[non_exhaustive]
pub struct ExecutionResult<T> {
    /// Total gas burnt by the execution
    pub total_gas_burnt: Gas,

    /// Value returned from an execution. This is a base64 encoded str for a successful
    /// execution or a `TxExecutionError` if a failed one.
    pub(crate) value: T,
    // pub(crate) transaction: ExecutionOutcome,
    // pub(crate) receipts: Vec<ExecutionOutcome>,
    pub(crate) details: ExecutionDetails,
}

impl<T: fmt::Debug> fmt::Debug for ExecutionResult<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("ExecutionResult")
            .field("total_gas_burnt", &self.total_gas_burnt)
            .field("transaction", &self.details.transaction)
            .field("receipts", &self.details.receipts)
            .field("value", &self.value)
            .finish()
    }
}

/// Execution related info found after performing a transaction. Can be converted
/// into [`ExecutionSuccess`] or [`ExecutionFailure`] through [`into_result`]
///
/// [`into_result`]: crate::result::ExecutionFinalResult::into_result
#[derive(PartialEq, Eq, Clone)]
#[must_use = "use `into_result()` to handle potential execution errors"]
pub struct ExecutionFinalResult {
    /// Total gas burnt by the execution
    pub total_gas_burnt: Gas,

    pub(crate) status: FinalExecutionStatus,
    pub(crate) details: ExecutionDetails,
}

impl fmt::Debug for ExecutionFinalResult {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("ExecutionFinalResult")
            .field("total_gas_burnt", &self.total_gas_burnt)
            .field("transaction", &self.details.transaction)
            .field("receipts", &self.details.receipts)
            .field("status", &self.status)
            .finish()
    }
}

impl ExecutionFinalResult {
    pub(crate) fn from_view(view: FinalExecutionOutcomeView) -> Self {
        let total_gas_burnt = view.transaction_outcome.outcome.gas_burnt
            + view
                .receipts_outcome
                .iter()
                .map(|t| t.outcome.gas_burnt)
                .sum::<u64>();

        let transaction = view.transaction_outcome.into();
        let receipts = view
            .receipts_outcome
            .into_iter()
            .map(ExecutionOutcome::from)
            .collect();

        Self {
            total_gas_burnt,
            status: view.status,
            details: ExecutionDetails {
                transaction,
                receipts,
            },
        }
    }

    /// Converts this object into a [`Result`] holding either [`ExecutionSuccess`] or [`ExecutionFailure`].
    #[allow(clippy::result_large_err)]
    pub fn into_result(self) -> Result<ExecutionSuccess, ExecutionFailure> {
        match self.status {
            FinalExecutionStatus::SuccessValue(value) => Ok(ExecutionResult {
                total_gas_burnt: self.total_gas_burnt,
                value: Value::from_string(base64::encode(value)),
                details: self.details,
            }),
            FinalExecutionStatus::Failure(tx_error) => Err(ExecutionResult {
                total_gas_burnt: self.total_gas_burnt,
                value: tx_error,
                details: self.details,
            }),
            _ => unreachable!(),
        }
    }

    /// Returns the contained Ok value, consuming the self value.
    ///
    /// Because this function may panic, its use is generally discouraged. Instead, prefer
    /// to call into [`into_result`] then pattern matching and handle the Err case explicitly.
    ///
    /// [`into_result`]: crate::result::ExecutionFinalResult::into_result
    pub fn unwrap(self) -> ExecutionSuccess {
        self.into_result().unwrap()
    }

    /// Deserialize an instance of type `T` from bytes of JSON text sourced from the
    /// execution result of this call. This conversion can fail if the structure of
    /// the internal state does not meet up with [`serde::de::DeserializeOwned`]'s
    /// requirements.
    pub fn json<T: serde::de::DeserializeOwned>(self) -> Result<T> {
        self.into_result()?.json()
    }

    /// Deserialize an instance of type `T` from bytes sourced from the execution
    /// result. This conversion can fail if the structure of the internal state does
    /// not meet up with [`borsh::BorshDeserialize`]'s requirements.
    pub fn borsh<T: borsh::BorshDeserialize>(self) -> Result<T> {
        self.into_result()?.borsh()
    }

    /// Grab the underlying raw bytes returned from calling into a contract's function.
    /// If we want to deserialize these bytes into a rust datatype, use [`ExecutionResult::json`]
    /// or [`ExecutionResult::borsh`] instead.
    pub fn raw_bytes(self) -> Result<Vec<u8>> {
        self.into_result()?.raw_bytes()
    }

    /// Checks whether the transaction was successful. Returns true if
    /// the transaction has a status of [`FinalExecutionStatus::SuccessValue`].
    pub fn is_success(&self) -> bool {
        matches!(self.status, FinalExecutionStatus::SuccessValue(_))
    }

    /// Checks whether the transaction has failed. Returns true if
    /// the transaction has a status of [`FinalExecutionStatus::Failure`].
    pub fn is_failure(&self) -> bool {
        matches!(self.status, FinalExecutionStatus::Failure(_))
    }

    /// Returns just the transaction outcome.
    pub fn outcome(&self) -> &ExecutionOutcome {
        self.details.outcome()
    }

    /// Grab all outcomes after the execution of the transaction. This includes outcomes
    /// from the transaction and all the receipts it generated.
    pub fn outcomes(&self) -> Vec<&ExecutionOutcome> {
        self.details.outcomes()
    }

    /// Grab all outcomes after the execution of the transaction. This includes outcomes
    /// only from receipts generated by this transaction.
    pub fn receipt_outcomes(&self) -> &[ExecutionOutcome] {
        self.details.receipt_outcomes()
    }

    /// Grab all outcomes that did not succeed the execution of this transaction. This
    /// will also include the failures from receipts as well.
    pub fn failures(&self) -> Vec<&ExecutionOutcome> {
        self.details.failures()
    }

    /// Just like `failures`, grab only failed receipt outcomes.
    pub fn receipt_failures(&self) -> Vec<&ExecutionOutcome> {
        self.details.receipt_failures()
    }

    /// Grab all logs from both the transaction and receipt outcomes.
    pub fn logs(&self) -> Vec<&str> {
        self.details.logs()
    }
}

impl ExecutionSuccess {
    /// Deserialize an instance of type `T` from bytes of JSON text sourced from the
    /// execution result of this call. This conversion can fail if the structure of
    /// the internal state does not meet up with [`serde::de::DeserializeOwned`]'s
    /// requirements.
    pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T> {
        self.value.json()
    }

    /// Deserialize an instance of type `T` from bytes sourced from the execution
    /// result. This conversion can fail if the structure of the internal state does
    /// not meet up with [`borsh::BorshDeserialize`]'s requirements.
    pub fn borsh<T: borsh::BorshDeserialize>(&self) -> Result<T> {
        self.value.borsh()
    }

    /// Grab the underlying raw bytes returned from calling into a contract's function.
    /// If we want to deserialize these bytes into a rust datatype, use [`ExecutionResult::json`]
    /// or [`ExecutionResult::borsh`] instead.
    pub fn raw_bytes(&self) -> Result<Vec<u8>> {
        self.value.raw_bytes()
    }
}

impl<T> ExecutionResult<T> {
    /// Returns just the transaction outcome.
    pub fn outcome(&self) -> &ExecutionOutcome {
        self.details.outcome()
    }

    /// Grab all outcomes after the execution of the transaction. This includes outcomes
    /// from the transaction and all the receipts it generated.
    pub fn outcomes(&self) -> Vec<&ExecutionOutcome> {
        self.details.outcomes()
    }

    /// Grab all outcomes after the execution of the transaction. This includes outcomes
    /// only from receipts generated by this transaction.
    pub fn receipt_outcomes(&self) -> &[ExecutionOutcome] {
        self.details.receipt_outcomes()
    }

    /// Grab all outcomes that did not succeed the execution of this transaction. This
    /// will also include the failures from receipts as well.
    pub fn failures(&self) -> Vec<&ExecutionOutcome> {
        self.details.failures()
    }

    /// Just like `failures`, grab only failed receipt outcomes.
    pub fn receipt_failures(&self) -> Vec<&ExecutionOutcome> {
        self.details.receipt_failures()
    }

    /// Grab all logs from both the transaction and receipt outcomes.
    pub fn logs(&self) -> Vec<&str> {
        self.details.logs()
    }
}

/// The result from a call into a View function. This contains the contents or
/// the results from the view function call itself. The consumer of this object
/// can choose how to deserialize its contents.
#[derive(PartialEq, Eq, Clone, Debug)]
#[non_exhaustive]
pub struct ViewResultDetails {
    /// Our result from our call into a view function.
    pub result: Vec<u8>,
    /// Logs generated from the view function.
    pub logs: Vec<String>,
}

impl ViewResultDetails {
    /// Deserialize an instance of type `T` from bytes of JSON text sourced from the
    /// execution result of this call. This conversion can fail if the structure of
    /// the internal state does not meet up with [`serde::de::DeserializeOwned`]'s
    /// requirements.
    pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T> {
        serde_json::from_slice(&self.result).map_err(|e| ErrorKind::DataConversion.custom(e))
    }

    /// Deserialize an instance of type `T` from bytes sourced from this view call's
    /// result. This conversion can fail if the structure of the internal state does
    /// not meet up with [`borsh::BorshDeserialize`]'s requirements.
    pub fn borsh<T: borsh::BorshDeserialize>(&self) -> Result<T> {
        borsh::BorshDeserialize::try_from_slice(&self.result)
            .map_err(|e| ErrorKind::DataConversion.custom(e))
    }
}

impl From<CallResult> for ViewResultDetails {
    fn from(result: CallResult) -> Self {
        ViewResultDetails {
            result: result.result,
            logs: result.logs,
        }
    }
}

/// The execution outcome of a transaction. This type contains all data relevant to
/// calling into a function, and getting the results back.
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct ExecutionOutcome {
    pub block_hash: CryptoHash,
    /// Logs from this transaction or receipt.
    pub logs: Vec<String>,
    /// Receipt IDs generated by this transaction or receipt.
    pub receipt_ids: Vec<CryptoHash>,
    /// The amount of the gas burnt by the given transaction or receipt.
    pub gas_burnt: Gas,
    /// The amount of tokens burnt corresponding to the burnt gas amount.
    /// This value doesn't always equal to the `gas_burnt` multiplied by the gas price, because
    /// the prepaid gas price might be lower than the actual gas price and it creates a deficit.
    pub tokens_burnt: Balance,
    /// The id of the account on which the execution happens. For transaction this is signer_id,
    /// for receipt this is receiver_id.
    pub executor_id: AccountId,
    /// Execution status. Contains the result in case of successful execution.
    pub(crate) status: ExecutionStatusView,
}

impl ExecutionOutcome {
    /// Checks whether this execution outcome was a success. Returns true if a success value or
    /// receipt id is present.
    pub fn is_success(&self) -> bool {
        matches!(
            self.status,
            ExecutionStatusView::SuccessValue(_) | ExecutionStatusView::SuccessReceiptId(_)
        )
    }

    /// Checks whether this execution outcome was a failure. Returns true if it failed with
    /// an error or the execution state was unknown or pending.
    pub fn is_failure(&self) -> bool {
        matches!(
            self.status,
            ExecutionStatusView::Failure(_) | ExecutionStatusView::Unknown
        )
    }

    /// Converts this [`ExecutionOutcome`] into a Result type to match against whether the
    /// particular outcome has failed or not.
    pub fn into_result(self) -> Result<ValueOrReceiptId> {
        match self.status {
            ExecutionStatusView::SuccessValue(value) => Ok(ValueOrReceiptId::Value(
                Value::from_string(base64::encode(value)),
            )),
            ExecutionStatusView::SuccessReceiptId(hash) => {
                Ok(ValueOrReceiptId::ReceiptId(CryptoHash(hash.0)))
            }
            ExecutionStatusView::Failure(err) => Err(ErrorKind::Execution.custom(err)),
            ExecutionStatusView::Unknown => {
                Err(ErrorKind::Execution.message("Execution pending or unknown"))
            }
        }
    }
}

/// Value or ReceiptId from a successful execution.
#[derive(Debug)]
pub enum ValueOrReceiptId {
    /// The final action succeeded and returned some value or an empty vec encoded in base64.
    Value(Value),
    /// The final action of the receipt returned a promise or the signed transaction was converted
    /// to a receipt. Contains the receipt_id of the generated receipt.
    ReceiptId(CryptoHash),
}

/// Value type returned from an [`ExecutionOutcome`] or receipt result. This value
/// can be converted into the underlying Rust datatype, or directly grab the raw
/// bytes associated to the value.
#[derive(Debug)]
pub struct Value {
    repr: String,
}

impl Value {
    fn from_string(value: String) -> Self {
        Self { repr: value }
    }

    /// Deserialize an instance of type `T` from bytes of JSON text sourced from the
    /// execution result of this call. This conversion can fail if the structure of
    /// the internal state does not meet up with [`serde::de::DeserializeOwned`]'s
    /// requirements.
    pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T> {
        let buf = self.raw_bytes()?;
        serde_json::from_slice(&buf).map_err(|e| ErrorKind::DataConversion.custom(e))
    }

    /// Deserialize an instance of type `T` from bytes sourced from the execution
    /// result. This conversion can fail if the structure of the internal state does
    /// not meet up with [`borsh::BorshDeserialize`]'s requirements.
    pub fn borsh<T: borsh::BorshDeserialize>(&self) -> Result<T> {
        let buf = self.raw_bytes()?;
        borsh::BorshDeserialize::try_from_slice(&buf)
            .map_err(|e| ErrorKind::DataConversion.custom(e))
    }

    /// Grab the underlying raw bytes returned from calling into a contract's function.
    /// If we want to deserialize these bytes into a rust datatype, use [`json`]
    /// or [`borsh`] instead.
    ///
    /// [`json`]: Value::json
    /// [`borsh`]: Value::borsh
    pub fn raw_bytes(&self) -> Result<Vec<u8>> {
        base64::decode(&self.repr).map_err(|e| ErrorKind::DataConversion.custom(e))
    }
}

impl From<ExecutionOutcomeWithIdView> for ExecutionOutcome {
    fn from(view: ExecutionOutcomeWithIdView) -> Self {
        ExecutionOutcome {
            block_hash: CryptoHash(view.block_hash.0),
            logs: view.outcome.logs,
            receipt_ids: view
                .outcome
                .receipt_ids
                .into_iter()
                .map(|c| CryptoHash(c.0))
                .collect(),
            gas_burnt: view.outcome.gas_burnt,
            tokens_burnt: view.outcome.tokens_burnt,
            executor_id: view.outcome.executor_id,
            status: view.outcome.status,
        }
    }
}