pallas-validate 1.0.0

Utilities for validating transactions
Documentation
use amaru_uplc::flat::FlatDecodeError;
use pallas_primitives::{
    TransactionInput,
    conway::{ExUnits, Language},
};

#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error("address error: {0}")]
    Address(#[from] pallas_addresses::Error),
    #[error("only shelley reward addresses can be a part of withdrawals")]
    BadWithdrawalAddress,
    #[error("flat decode error: {0}")]
    FlatDecode(#[from] FlatDecodeError),
    #[error("fragment decode error: {0}")]
    FragmentDecode(#[from] pallas_primitives::Error),
    #[error(
        "{message}\n{:>13} {}\n{:>13} {}{}",
        "Mem",
        budget.mem,
        "CPU",
        budget.steps,
        format_machine_traces(logs),
    )]
    Machine {
        message: String,
        budget: ExUnits,
        logs: Vec<String>,
    },

    #[error("native script can't be executed in phase-two")]
    NativeScriptPhaseTwo,
    #[error("can't eval without redeemers")]
    NoRedeemers,
    #[error(
        "missing and/or unexpected validator(s) and/or redeemer(s)\n{:>13} {}\n{:>13} {}",
        "Missing",
        if .missing.is_empty() { "ø".to_string() } else { .missing.join(&format!("\n{:>14}", "")) },
        "Unexpected",
        if .extra.is_empty() { "ø".to_string() } else { .extra.join(&format!("\n{:>14}", "")) },
    )]
    RequiredRedeemersMismatch {
        missing: Vec<String>,
        extra: Vec<String>,
    },
    #[error("extraneous redeemer")]
    ExtraneousRedeemer,
    #[error("resolved Input not found")]
    ResolvedInputNotFound(TransactionInput),
    #[error("redeemer points to a non-script withdrawal")]
    NonScriptWithdrawal,
    #[error("stake credential points to a non-script withdrawal")]
    NonScriptStakeCredential,
    #[error("the designated procedure defines no guardrail script")]
    NoGuardrailScriptForProcedure,
    #[error("cost model not found for language\n{:>13} {:?}", "Language", .0)]
    CostModelNotFound(Language),
    #[error("unsupported era, please use Conway")]
    WrongEra(),
    #[error("decoding error\n{:>13} {}", "Decoder error", .0)]
    DecodeError(#[from] pallas_codec::minicbor::decode::Error),
    #[error("byron address not allowed when PlutusV2 scripts are present")]
    ByronAddressNotAllowed,
    #[error("inline datum not allowed when PlutusV1 scripts are present")]
    InlineDatumNotAllowed,
    #[error("script and input reference not allowed in PlutusV1")]
    ScriptAndInputRefNotAllowed,
    #[error("address doesn't contain a payment credential")]
    NoPaymentCredential,
    #[error("missing required datum\n{:>13} {}", "Datum", hash)]
    MissingRequiredDatum { hash: String },
    #[error("missing required script\n{:>13} {}", "Script", hash)]
    MissingRequiredScript { hash: String },
    #[error("missing required inline datum or datum hash in script input")]
    MissingRequiredInlineDatumOrHash,
    #[error("redeemer points to an unsupported certificate type")]
    UnsupportedCertificateType,
    #[error("failed script execution\n{:>13} {}", format!("{}[{}]", tag, index), err)]
    RedeemerError {
        tag: String,
        index: u32,
        err: Box<Error>,
    },
    #[error("missing script for redeemer")]
    MissingScriptForRedeemer,
    #[error("failed to apply parameters to Plutus script")]
    ApplyParamsError,
    #[error("validity start or end too far in the past")]
    SlotTooFarInThePast { oldest_allowed: u64 },
    #[error("could not build script context")]
    ScriptContextBuildError,
}

fn format_machine_traces(logs: &[String]) -> String {
    logs.iter()
        .map(|trace| format!("\n{:>13} {}", "Trace", indent_trace(trace)))
        .collect::<Vec<_>>()
        .join("")
}

fn indent_trace(trace: &str) -> String {
    if !trace.contains('\n') {
        return trace.to_string();
    }

    trace
        .lines()
        .enumerate()
        .map(|(ix, row)| {
            if ix == 0 {
                row.to_string()
            } else {
                format!("{:>13} {}", "", row)
            }
        })
        .collect::<Vec<_>>()
        .join("\n")
}