1use crate::primitives::to_hex;
7use std::fmt;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum ExecutionContext {
12 UnlockingScript,
14 LockingScript,
16}
17
18impl fmt::Display for ExecutionContext {
19 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20 match self {
21 ExecutionContext::UnlockingScript => write!(f, "UnlockingScript"),
22 ExecutionContext::LockingScript => write!(f, "LockingScript"),
23 }
24 }
25}
26
27#[derive(Debug, Clone)]
32pub struct ScriptEvaluationError {
33 pub message: String,
35 pub source_txid: String,
37 pub source_output_index: u32,
39 pub context: ExecutionContext,
41 pub program_counter: usize,
43 pub stack: Vec<Vec<u8>>,
45 pub alt_stack: Vec<Vec<u8>>,
47 pub if_stack: Vec<bool>,
49 pub stack_mem: usize,
51 pub alt_stack_mem: usize,
53}
54
55impl ScriptEvaluationError {
56 #[allow(clippy::too_many_arguments)]
58 pub fn new(
59 message: impl Into<String>,
60 source_txid: impl Into<String>,
61 source_output_index: u32,
62 context: ExecutionContext,
63 program_counter: usize,
64 stack: Vec<Vec<u8>>,
65 alt_stack: Vec<Vec<u8>>,
66 if_stack: Vec<bool>,
67 stack_mem: usize,
68 alt_stack_mem: usize,
69 ) -> Self {
70 Self {
71 message: message.into(),
72 source_txid: source_txid.into(),
73 source_output_index,
74 context,
75 program_counter,
76 stack,
77 alt_stack,
78 if_stack,
79 stack_mem,
80 alt_stack_mem,
81 }
82 }
83
84 fn format_stack(stack: &[Vec<u8>]) -> String {
86 let hex_items: Vec<String> = stack
87 .iter()
88 .map(|item| {
89 if item.is_empty() {
90 "[]".to_string()
91 } else {
92 to_hex(item)
93 }
94 })
95 .collect();
96 format!("[{}]", hex_items.join(", "))
97 }
98
99 fn format_if_stack(if_stack: &[bool]) -> String {
101 let items: Vec<&str> = if_stack
102 .iter()
103 .map(|&b| if b { "true" } else { "false" })
104 .collect();
105 format!("[{}]", items.join(", "))
106 }
107}
108
109impl fmt::Display for ScriptEvaluationError {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 write!(
112 f,
113 "Script evaluation error: {}\n\
114 TXID: {}, OutputIdx: {}\n\
115 Context: {}, PC: {}\n\
116 Stack: {} (len: {}, mem: {})\n\
117 AltStack: {} (len: {}, mem: {})\n\
118 IfStack: {}",
119 self.message,
120 self.source_txid,
121 self.source_output_index,
122 self.context,
123 self.program_counter,
124 Self::format_stack(&self.stack),
125 self.stack.len(),
126 self.stack_mem,
127 Self::format_stack(&self.alt_stack),
128 self.alt_stack.len(),
129 self.alt_stack_mem,
130 Self::format_if_stack(&self.if_stack),
131 )
132 }
133}
134
135impl std::error::Error for ScriptEvaluationError {}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn test_error_display() {
143 let error = ScriptEvaluationError::new(
144 "Stack underflow",
145 "abc123",
146 0,
147 ExecutionContext::LockingScript,
148 5,
149 vec![vec![1, 2, 3], vec![]],
150 vec![],
151 vec![true, false],
152 3,
153 0,
154 );
155
156 let display = format!("{}", error);
157 assert!(display.contains("Stack underflow"));
158 assert!(display.contains("abc123"));
159 assert!(display.contains("LockingScript"));
160 assert!(display.contains("PC: 5"));
161 assert!(display.contains("010203"));
162 assert!(display.contains("[]"));
163 }
164
165 #[test]
166 fn test_execution_context_display() {
167 assert_eq!(
168 format!("{}", ExecutionContext::UnlockingScript),
169 "UnlockingScript"
170 );
171 assert_eq!(
172 format!("{}", ExecutionContext::LockingScript),
173 "LockingScript"
174 );
175 }
176
177 #[test]
178 fn test_format_stack() {
179 let stack = vec![vec![0x01, 0x02], vec![], vec![0xff]];
180 let formatted = ScriptEvaluationError::format_stack(&stack);
181 assert_eq!(formatted, "[0102, [], ff]");
182 }
183
184 #[test]
185 fn test_format_if_stack() {
186 let if_stack = vec![true, false, true];
187 let formatted = ScriptEvaluationError::format_if_stack(&if_stack);
188 assert_eq!(formatted, "[true, false, true]");
189 }
190}