Improve / fix machine and uplc error reporting.

This commit is contained in:
KtorZ 2024-08-10 14:10:43 +02:00
parent f56b9bbbc7
commit b158469144
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
4 changed files with 76 additions and 85 deletions

View File

@ -149,7 +149,14 @@ pub fn exec(
); );
} }
Err(err) => { Err(err) => {
eprintln!("{}", display_tx_error(&err)); eprintln!(
"{} {}",
" Error"
.if_supports_color(Stderr, |s| s.red())
.if_supports_color(Stderr, |s| s.bold()),
err.red()
);
process::exit(1); process::exit(1);
} }
} }
@ -157,46 +164,3 @@ pub fn exec(
Ok(()) Ok(())
} }
fn display_tx_error(err: &tx::error::Error) -> String {
let mut msg = format!(
"{} {}",
" Error"
.if_supports_color(Stderr, |s| s.red())
.if_supports_color(Stderr, |s| s.bold()),
err.red()
);
match err {
tx::error::Error::RedeemerError { err, .. } => {
msg.push_str(&format!(
"\n{}",
display_tx_error(err)
.lines()
.skip(1)
.collect::<Vec<_>>()
.join("\n"),
));
msg
}
tx::error::Error::Machine(_, _, traces) => {
msg.push_str(
traces
.iter()
.map(|s| {
format!(
"\n{} {}",
" Trace"
.if_supports_color(Stderr, |s| s.yellow())
.if_supports_color(Stderr, |s| s.bold()),
s.if_supports_color(Stderr, |s| s.yellow())
)
})
.collect::<Vec<_>>()
.join("")
.as_str(),
);
msg
}
_ => msg,
}
}

View File

@ -11,7 +11,7 @@ pub enum Error {
InvalidStepKind(u8), InvalidStepKind(u8),
#[error("Cannot evaluate an open term:\\n\\n{}", .0.to_pretty())] #[error("Cannot evaluate an open term:\\n\\n{}", .0.to_pretty())]
OpenTermEvaluated(Term<NamedDeBruijn>), OpenTermEvaluated(Term<NamedDeBruijn>),
#[error("The provided Plutus code called 'error'.")] #[error("The validator crashed / exited prematurely")]
EvaluationFailure, EvaluationFailure,
#[error("Attempted to instantiate a non-polymorphic term:\n\n{0:#?}")] #[error("Attempted to instantiate a non-polymorphic term:\n\n{0:#?}")]
NonPolymorphicInstantiation(Value), NonPolymorphicInstantiation(Value),
@ -29,9 +29,13 @@ pub enum Error {
PairTypeMismatch(Type), PairTypeMismatch(Type),
#[error("Empty List:\n\n{0:#?}")] #[error("Empty List:\n\n{0:#?}")]
EmptyList(Value), EmptyList(Value),
#[error("A builtin received a term argument when something else was expected:\n\n{0}\n\nYou probably forgot to wrap the builtin with a force.")] #[error(
"A builtin received a term argument when something else was expected:\n\n{0}\n\nYou probably forgot to wrap the builtin with a force."
)]
UnexpectedBuiltinTermArgument(Term<NamedDeBruijn>), UnexpectedBuiltinTermArgument(Term<NamedDeBruijn>),
#[error("A builtin expected a term argument, but something else was received:\n\n{0}\n\nYou probably have an extra force wrapped around a builtin")] #[error(
"A builtin expected a term argument, but something else was received:\n\n{0}\n\nYou probably have an extra force wrapped around a builtin"
)]
BuiltinTermArgumentExpected(Term<NamedDeBruijn>), BuiltinTermArgumentExpected(Term<NamedDeBruijn>),
#[error("Unable to unlift value because it is not a constant:\n\n{0:#?}")] #[error("Unable to unlift value because it is not a constant:\n\n{0:#?}")]
NotAConstant(Value), NotAConstant(Value),

View File

@ -8,59 +8,93 @@ use pallas_primitives::conway::Language;
pub enum Error { pub enum Error {
#[error("{0}")] #[error("{0}")]
Address(#[from] pallas_addresses::Error), Address(#[from] pallas_addresses::Error),
#[error("Only shelley reward addresses can be a part of withdrawals")] #[error("only shelley reward addresses can be a part of withdrawals")]
BadWithdrawalAddress, BadWithdrawalAddress,
#[error("{0}")] #[error("{0}")]
FlatDecode(#[from] pallas_codec::flat::de::Error), FlatDecode(#[from] pallas_codec::flat::de::Error),
#[error("{0}")] #[error("{0}")]
FragmentDecode(#[from] pallas_primitives::Error), FragmentDecode(#[from] pallas_primitives::Error),
#[error("{}\n\n{:#?}\n\n{}", .0, .1, .2.join("\n"))] #[error("{}{}", .0, .2.iter()
.map(|trace| {
format!(
"\n{:>13} {}",
"Trace",
if trace.contains("\n") {
trace.lines()
.enumerate()
.map(|(ix, row)| {
if ix == 0 {
row.to_string()
} else {
format!("{:>13} {}", "",
row
)
}
})
.collect::<Vec<_>>()
.join("\n")
} else {
trace.to_string()
}
)
})
.collect::<Vec<_>>()
.join("")
.as_str()
)]
Machine(machine::Error, ExBudget, Vec<String>), Machine(machine::Error, ExBudget, Vec<String>),
#[error("Native script can't be executed in phase-two")]
#[error("native script can't be executed in phase-two")]
NativeScriptPhaseTwo, NativeScriptPhaseTwo,
#[error("Can't eval without redeemers")] #[error("can't eval without redeemers")]
NoRedeemers, NoRedeemers,
#[error("Mismatch in required redeemers: {} {}", .missing.join(" "), .extra.join(" "))] #[error(
"mismatch in expected redeemers\n{:>13} {}\n{:>13} {}",
"Missing",
if .missing.is_empty() { "ø".to_string() } else { .missing.join(&format!("\n{:>13}", "")) },
"Unexpected",
if .extra.is_empty() { "ø".to_string() } else { .extra.join(&format!("\n{:>13}", "")) },
)]
RequiredRedeemersMismatch { RequiredRedeemersMismatch {
missing: Vec<String>, missing: Vec<String>,
extra: Vec<String>, extra: Vec<String>,
}, },
#[error("Extraneous redeemer")] #[error("extraneous redeemer")]
ExtraneousRedeemer, ExtraneousRedeemer,
#[error("Resolved Input not found.")] #[error("resolved Input not found")]
ResolvedInputNotFound(TransactionInput), ResolvedInputNotFound(TransactionInput),
#[error("Redeemer points to a non-script withdrawal.")] #[error("redeemer points to a non-script withdrawal")]
NonScriptWithdrawal, NonScriptWithdrawal,
#[error("Stake credential points to a non-script withdrawal.")] #[error("stake credential points to a non-script withdrawal")]
NonScriptStakeCredential, NonScriptStakeCredential,
#[error("Cost model not found for language: {:?}.", .0)] #[error("cost model not found for language\n{:>13} {:?}", "Language", .0)]
CostModelNotFound(Language), CostModelNotFound(Language),
#[error("Wrong era, Please use Babbage or Alonzo: {0}")] #[error("unsupported era, please use Conway\n{:>13} {0}", "Decoder error")]
WrongEra(#[from] pallas_codec::minicbor::decode::Error), WrongEra(#[from] pallas_codec::minicbor::decode::Error),
#[error("Byron address not allowed in Plutus.")] #[error("byron address not allowed when PlutusV2 scripts are present")]
ByronAddressNotAllowed, ByronAddressNotAllowed,
#[error("Inline datum not allowed in PlutusV1.")] #[error("inline datum not allowed when PlutusV1 scripts are present")]
InlineDatumNotAllowed, InlineDatumNotAllowed,
#[error("Script and input reference not allowed in PlutusV1.")] #[error("script and input reference not allowed in PlutusV1")]
ScriptAndInputRefNotAllowed, ScriptAndInputRefNotAllowed,
#[error("Address doesn't contain a payment credential.")] #[error("address doesn't contain a payment credential")]
NoPaymentCredential, NoPaymentCredential,
#[error("Missing required datum: {}", hash)] #[error("missing required datum\n{:>13} {}", "Datum", hash)]
MissingRequiredDatum { hash: String }, MissingRequiredDatum { hash: String },
#[error("Missing required script: {}", hash)] #[error("missing required script\n{:>13} {}", "Script", hash)]
MissingRequiredScript { hash: String }, MissingRequiredScript { hash: String },
#[error("Missing required inline datum or datum hash in script input.")] #[error("missing required inline datum or datum hash in script input")]
MissingRequiredInlineDatumOrHash, MissingRequiredInlineDatumOrHash,
#[error("Redeemer points to an unsupported certificate type.")] #[error("redeemer points to an unsupported certificate type")]
UnsupportedCertificateType, UnsupportedCertificateType,
#[error("Redeemer ({}, {}): {}", tag, index, err)] #[error("failed script execution\n{:>13} {}", format!("{}({})", tag, index), err)]
RedeemerError { RedeemerError {
tag: String, tag: String,
index: u32, index: u32,
err: Box<Error>, err: Box<Error>,
}, },
#[error("Missing script for redeemer")] #[error("missing script for redeemer")]
MissingScriptForRedeemer, MissingScriptForRedeemer,
#[error("Failed to apply parameters to Plutus script.")] #[error("failed to apply parameters to Plutus script")]
ApplyParamsError, ApplyParamsError,
} }

View File

@ -39,23 +39,17 @@ pub fn validate_missing_scripts(
.clone() .clone()
.into_iter() .into_iter()
.filter(|x| !received_hashes.contains(x)) .filter(|x| !received_hashes.contains(x))
.map(|x| format!("[Missing (sh: {x})]")) .map(|x| x.to_string())
.collect(); .collect();
let extra: Vec<_> = received_hashes let extra: Vec<_> = received_hashes
.into_iter() .into_iter()
.filter(|x| !needed_hashes.contains(x)) .filter(|x| !needed_hashes.contains(x))
.map(|x| format!("[Extraneous (sh: {x:?})]")) .map(|x| x.to_string())
.collect(); .collect();
if !missing.is_empty() || !extra.is_empty() { if !missing.is_empty() || !extra.is_empty() {
let missing_errors = missing.join(" "); return Err(Error::RequiredRedeemersMismatch { missing, extra });
let extra_errors = extra.join(" ");
unreachable!(
"Mismatch in required scripts: {} {}",
missing_errors, extra_errors
);
} }
Ok(()) Ok(())
@ -191,18 +185,13 @@ pub fn has_exact_set_of_redeemers(
let missing: Vec<_> = redeemers_needed let missing: Vec<_> = redeemers_needed
.into_iter() .into_iter()
.filter(|x| !wits_redeemer_keys.contains(&&x.0)) .filter(|x| !wits_redeemer_keys.contains(&&x.0))
.map(|x| { .map(|x| format!("{} (key: {:?}, purpose: {:?})", x.2, x.0, x.1,))
format!(
"[Missing (redeemer_key: {:?}, script_purpose: {:?}, script_hash: {})]",
x.0, x.1, x.2,
)
})
.collect(); .collect();
let extra: Vec<_> = wits_redeemer_keys let extra: Vec<_> = wits_redeemer_keys
.into_iter() .into_iter()
.filter(|x| !needed_redeemer_keys.contains(x)) .filter(|x| !needed_redeemer_keys.contains(x))
.map(|x| format!("[Extraneous (redeemer_key: {x:?})]")) .map(|x| format!("{x:?}"))
.collect(); .collect();
if !missing.is_empty() || !extra.is_empty() { if !missing.is_empty() || !extra.is_empty() {