use crate::primitives::to_hex;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExecutionContext {
UnlockingScript,
LockingScript,
}
impl fmt::Display for ExecutionContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ExecutionContext::UnlockingScript => write!(f, "UnlockingScript"),
ExecutionContext::LockingScript => write!(f, "LockingScript"),
}
}
}
#[derive(Debug, Clone)]
pub struct ScriptEvaluationError {
pub message: String,
pub source_txid: String,
pub source_output_index: u32,
pub context: ExecutionContext,
pub program_counter: usize,
pub stack: Vec<Vec<u8>>,
pub alt_stack: Vec<Vec<u8>>,
pub if_stack: Vec<bool>,
pub stack_mem: usize,
pub alt_stack_mem: usize,
}
impl ScriptEvaluationError {
#[allow(clippy::too_many_arguments)]
pub fn new(
message: impl Into<String>,
source_txid: impl Into<String>,
source_output_index: u32,
context: ExecutionContext,
program_counter: usize,
stack: Vec<Vec<u8>>,
alt_stack: Vec<Vec<u8>>,
if_stack: Vec<bool>,
stack_mem: usize,
alt_stack_mem: usize,
) -> Self {
Self {
message: message.into(),
source_txid: source_txid.into(),
source_output_index,
context,
program_counter,
stack,
alt_stack,
if_stack,
stack_mem,
alt_stack_mem,
}
}
fn format_stack(stack: &[Vec<u8>]) -> String {
let hex_items: Vec<String> = stack
.iter()
.map(|item| {
if item.is_empty() {
"[]".to_string()
} else {
to_hex(item)
}
})
.collect();
format!("[{}]", hex_items.join(", "))
}
fn format_if_stack(if_stack: &[bool]) -> String {
let items: Vec<&str> = if_stack
.iter()
.map(|&b| if b { "true" } else { "false" })
.collect();
format!("[{}]", items.join(", "))
}
}
impl fmt::Display for ScriptEvaluationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Script evaluation error: {}\n\
TXID: {}, OutputIdx: {}\n\
Context: {}, PC: {}\n\
Stack: {} (len: {}, mem: {})\n\
AltStack: {} (len: {}, mem: {})\n\
IfStack: {}",
self.message,
self.source_txid,
self.source_output_index,
self.context,
self.program_counter,
Self::format_stack(&self.stack),
self.stack.len(),
self.stack_mem,
Self::format_stack(&self.alt_stack),
self.alt_stack.len(),
self.alt_stack_mem,
Self::format_if_stack(&self.if_stack),
)
}
}
impl std::error::Error for ScriptEvaluationError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let error = ScriptEvaluationError::new(
"Stack underflow",
"abc123",
0,
ExecutionContext::LockingScript,
5,
vec![vec![1, 2, 3], vec![]],
vec![],
vec![true, false],
3,
0,
);
let display = format!("{}", error);
assert!(display.contains("Stack underflow"));
assert!(display.contains("abc123"));
assert!(display.contains("LockingScript"));
assert!(display.contains("PC: 5"));
assert!(display.contains("010203"));
assert!(display.contains("[]"));
}
#[test]
fn test_execution_context_display() {
assert_eq!(
format!("{}", ExecutionContext::UnlockingScript),
"UnlockingScript"
);
assert_eq!(
format!("{}", ExecutionContext::LockingScript),
"LockingScript"
);
}
#[test]
fn test_format_stack() {
let stack = vec![vec![0x01, 0x02], vec![], vec![0xff]];
let formatted = ScriptEvaluationError::format_stack(&stack);
assert_eq!(formatted, "[0102, [], ff]");
}
#[test]
fn test_format_if_stack() {
let if_stack = vec![true, false, true];
let formatted = ScriptEvaluationError::format_if_stack(&if_stack);
assert_eq!(formatted, "[true, false, true]");
}
}