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, Copy, PartialEq, Eq)]
37pub enum ScriptResource {
38 Stack,
40 AltStack,
42 ElementSize,
45}
46
47impl fmt::Display for ScriptResource {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 match self {
50 ScriptResource::Stack => write!(f, "stack"),
51 ScriptResource::AltStack => write!(f, "alt-stack"),
52 ScriptResource::ElementSize => write!(f, "element-size"),
53 }
54 }
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub struct ScriptResourceLimit {
61 pub resource: ScriptResource,
62 pub limit: usize,
63 pub attempted: usize,
64}
65
66#[derive(Debug, Clone)]
67pub struct ScriptEvaluationError {
68 pub message: String,
70 pub source_txid: String,
72 pub source_output_index: u32,
74 pub context: ExecutionContext,
76 pub program_counter: usize,
78 pub stack: Vec<Vec<u8>>,
80 pub alt_stack: Vec<Vec<u8>>,
82 pub if_stack: Vec<bool>,
84 pub stack_mem: usize,
86 pub alt_stack_mem: usize,
88 pub resource_limit: Option<ScriptResourceLimit>,
93}
94
95impl ScriptEvaluationError {
96 #[allow(clippy::too_many_arguments)]
98 pub fn new(
99 message: impl Into<String>,
100 source_txid: impl Into<String>,
101 source_output_index: u32,
102 context: ExecutionContext,
103 program_counter: usize,
104 stack: Vec<Vec<u8>>,
105 alt_stack: Vec<Vec<u8>>,
106 if_stack: Vec<bool>,
107 stack_mem: usize,
108 alt_stack_mem: usize,
109 ) -> Self {
110 Self {
111 message: message.into(),
112 source_txid: source_txid.into(),
113 source_output_index,
114 context,
115 program_counter,
116 stack,
117 alt_stack,
118 if_stack,
119 stack_mem,
120 alt_stack_mem,
121 resource_limit: None,
122 }
123 }
124
125 pub fn with_resource_limit(mut self, limit: ScriptResourceLimit) -> Self {
129 self.resource_limit = Some(limit);
130 self
131 }
132
133 pub fn is_resource_limit(&self) -> bool {
136 self.resource_limit.is_some()
137 }
138
139 fn format_stack(stack: &[Vec<u8>]) -> String {
141 let hex_items: Vec<String> = stack
142 .iter()
143 .map(|item| {
144 if item.is_empty() {
145 "[]".to_string()
146 } else {
147 to_hex(item)
148 }
149 })
150 .collect();
151 format!("[{}]", hex_items.join(", "))
152 }
153
154 fn format_if_stack(if_stack: &[bool]) -> String {
156 let items: Vec<&str> = if_stack
157 .iter()
158 .map(|&b| if b { "true" } else { "false" })
159 .collect();
160 format!("[{}]", items.join(", "))
161 }
162}
163
164impl fmt::Display for ScriptEvaluationError {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 write!(
167 f,
168 "Script evaluation error: {}\n\
169 TXID: {}, OutputIdx: {}\n\
170 Context: {}, PC: {}\n\
171 Stack: {} (len: {}, mem: {})\n\
172 AltStack: {} (len: {}, mem: {})\n\
173 IfStack: {}",
174 self.message,
175 self.source_txid,
176 self.source_output_index,
177 self.context,
178 self.program_counter,
179 Self::format_stack(&self.stack),
180 self.stack.len(),
181 self.stack_mem,
182 Self::format_stack(&self.alt_stack),
183 self.alt_stack.len(),
184 self.alt_stack_mem,
185 Self::format_if_stack(&self.if_stack),
186 )
187 }
188}
189
190impl std::error::Error for ScriptEvaluationError {}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn test_error_display() {
198 let error = ScriptEvaluationError::new(
199 "Stack underflow",
200 "abc123",
201 0,
202 ExecutionContext::LockingScript,
203 5,
204 vec![vec![1, 2, 3], vec![]],
205 vec![],
206 vec![true, false],
207 3,
208 0,
209 );
210
211 let display = format!("{}", error);
212 assert!(display.contains("Stack underflow"));
213 assert!(display.contains("abc123"));
214 assert!(display.contains("LockingScript"));
215 assert!(display.contains("PC: 5"));
216 assert!(display.contains("010203"));
217 assert!(display.contains("[]"));
218 }
219
220 #[test]
221 fn test_execution_context_display() {
222 assert_eq!(
223 format!("{}", ExecutionContext::UnlockingScript),
224 "UnlockingScript"
225 );
226 assert_eq!(
227 format!("{}", ExecutionContext::LockingScript),
228 "LockingScript"
229 );
230 }
231
232 #[test]
233 fn test_format_stack() {
234 let stack = vec![vec![0x01, 0x02], vec![], vec![0xff]];
235 let formatted = ScriptEvaluationError::format_stack(&stack);
236 assert_eq!(formatted, "[0102, [], ff]");
237 }
238
239 #[test]
240 fn test_format_if_stack() {
241 let if_stack = vec![true, false, true];
242 let formatted = ScriptEvaluationError::format_if_stack(&if_stack);
243 assert_eq!(formatted, "[true, false, true]");
244 }
245}