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>;
pub type ExecutionSuccess = ExecutionResult<Value>;
pub type ExecutionFailure = ExecutionResult<TxExecutionError>;
#[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)
}
pub fn is_success(&self) -> bool {
self.details.is_success()
}
pub fn is_failure(&self) -> bool {
self.details.is_failure()
}
}
#[derive(PartialEq, Eq, Clone)]
pub(crate) struct ExecutionDetails {
pub(crate) transaction: ExecutionOutcome,
pub(crate) receipts: Vec<ExecutionOutcome>,
}
impl ExecutionDetails {
pub fn outcome(&self) -> &ExecutionOutcome {
&self.transaction
}
pub fn outcomes(&self) -> Vec<&ExecutionOutcome> {
let mut outcomes = vec![&self.transaction];
outcomes.extend(self.receipt_outcomes());
outcomes
}
pub fn receipt_outcomes(&self) -> &[ExecutionOutcome] {
&self.receipts
}
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
}
pub fn receipt_failures(&self) -> Vec<&ExecutionOutcome> {
self.receipts
.iter()
.filter(|receipt| matches!(receipt.status, ExecutionStatusView::Failure(_)))
.collect()
}
pub fn logs(&self) -> Vec<&str> {
self.outcomes()
.iter()
.flat_map(|outcome| &outcome.logs)
.map(String::as_str)
.collect()
}
}
#[derive(PartialEq, Eq, Clone)]
#[non_exhaustive]
pub struct ExecutionResult<T> {
pub total_gas_burnt: Gas,
pub(crate) value: T,
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()
}
}
#[derive(PartialEq, Eq, Clone)]
#[must_use = "use `into_result()` to handle potential execution errors"]
pub struct ExecutionFinalResult {
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,
},
}
}
#[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!(),
}
}
pub fn unwrap(self) -> ExecutionSuccess {
self.into_result().unwrap()
}
pub fn json<T: serde::de::DeserializeOwned>(self) -> Result<T> {
self.into_result()?.json()
}
pub fn borsh<T: borsh::BorshDeserialize>(self) -> Result<T> {
self.into_result()?.borsh()
}
pub fn raw_bytes(self) -> Result<Vec<u8>> {
self.into_result()?.raw_bytes()
}
pub fn is_success(&self) -> bool {
matches!(self.status, FinalExecutionStatus::SuccessValue(_))
}
pub fn is_failure(&self) -> bool {
matches!(self.status, FinalExecutionStatus::Failure(_))
}
pub fn outcome(&self) -> &ExecutionOutcome {
self.details.outcome()
}
pub fn outcomes(&self) -> Vec<&ExecutionOutcome> {
self.details.outcomes()
}
pub fn receipt_outcomes(&self) -> &[ExecutionOutcome] {
self.details.receipt_outcomes()
}
pub fn failures(&self) -> Vec<&ExecutionOutcome> {
self.details.failures()
}
pub fn receipt_failures(&self) -> Vec<&ExecutionOutcome> {
self.details.receipt_failures()
}
pub fn logs(&self) -> Vec<&str> {
self.details.logs()
}
}
impl ExecutionSuccess {
pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T> {
self.value.json()
}
pub fn borsh<T: borsh::BorshDeserialize>(&self) -> Result<T> {
self.value.borsh()
}
pub fn raw_bytes(&self) -> Result<Vec<u8>> {
self.value.raw_bytes()
}
}
impl<T> ExecutionResult<T> {
pub fn outcome(&self) -> &ExecutionOutcome {
self.details.outcome()
}
pub fn outcomes(&self) -> Vec<&ExecutionOutcome> {
self.details.outcomes()
}
pub fn receipt_outcomes(&self) -> &[ExecutionOutcome] {
self.details.receipt_outcomes()
}
pub fn failures(&self) -> Vec<&ExecutionOutcome> {
self.details.failures()
}
pub fn receipt_failures(&self) -> Vec<&ExecutionOutcome> {
self.details.receipt_failures()
}
pub fn logs(&self) -> Vec<&str> {
self.details.logs()
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
#[non_exhaustive]
pub struct ViewResultDetails {
pub result: Vec<u8>,
pub logs: Vec<String>,
}
impl ViewResultDetails {
pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T> {
serde_json::from_slice(&self.result).map_err(|e| ErrorKind::DataConversion.custom(e))
}
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,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct ExecutionOutcome {
pub block_hash: CryptoHash,
pub logs: Vec<String>,
pub receipt_ids: Vec<CryptoHash>,
pub gas_burnt: Gas,
pub tokens_burnt: Balance,
pub executor_id: AccountId,
pub(crate) status: ExecutionStatusView,
}
impl ExecutionOutcome {
pub fn is_success(&self) -> bool {
matches!(
self.status,
ExecutionStatusView::SuccessValue(_) | ExecutionStatusView::SuccessReceiptId(_)
)
}
pub fn is_failure(&self) -> bool {
matches!(
self.status,
ExecutionStatusView::Failure(_) | ExecutionStatusView::Unknown
)
}
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"))
}
}
}
}
#[derive(Debug)]
pub enum ValueOrReceiptId {
Value(Value),
ReceiptId(CryptoHash),
}
#[derive(Debug)]
pub struct Value {
repr: String,
}
impl Value {
fn from_string(value: String) -> Self {
Self { repr: value }
}
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))
}
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))
}
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,
}
}
}