avm_rs/cli/commands/
execute.rs1use crate::assembler::Assembler;
4#[cfg(feature = "tracing")]
5use crate::cli::TracingLevel;
6use crate::cli::{ExecuteCommand, ExecutionMode, GlobalOptions, InputType};
7use crate::state::MockLedger;
8#[cfg(feature = "tracing")]
9use crate::tracing::{TraceLevel, TracingConfig};
10use crate::types::TealVersion;
11use crate::{ExecutionConfig, VirtualMachine};
12use anyhow::{Context, Result, anyhow};
13use std::fs;
14use std::path::Path;
15
16pub fn handle(cmd: ExecuteCommand, global: &GlobalOptions) -> Result<()> {
18 if !global.quiet && global.verbose {
19 println!("🚀 Executing TEAL program...");
20 println!("Input: {}", cmd.input);
21 println!("Type: {:?}", cmd.input_type);
22 println!("Mode: {:?}", cmd.mode);
23
24 #[cfg(feature = "tracing")]
25 if cmd.trace_level.is_some() || cmd.trace_opcodes || cmd.trace_stack {
26 println!("Tracing: enabled");
27 if let Some(trace_level) = &cmd.trace_level {
28 println!("Trace level: {trace_level:?}");
29 } else if cmd.trace_opcodes || cmd.trace_stack {
30 println!("Trace level: Debug (auto-enabled by trace flags)");
31 }
32 println!("Trace opcodes: {}", cmd.trace_opcodes);
33 println!("Trace stack: {}", cmd.trace_stack);
34 }
35 }
36
37 let bytecode = load_input(&cmd)?;
39
40 let version = cmd
42 .version
43 .map(TealVersion::from_u8)
44 .transpose()
45 .context("Invalid TEAL version")?
46 .unwrap_or(TealVersion::latest());
47
48 let vm = VirtualMachine::with_version(version);
49
50 let run_mode = match cmd.mode {
52 ExecutionMode::Signature => crate::types::RunMode::Signature,
53 ExecutionMode::Application => crate::types::RunMode::Application,
54 };
55
56 #[cfg(feature = "tracing")]
57 let config = if cmd.trace_level.is_some() || cmd.trace_opcodes || cmd.trace_stack {
58 let tracing_config = build_tracing_config(&cmd)?;
59 ExecutionConfig::new(version)
60 .with_cost_budget(cmd.budget)
61 .with_run_mode(run_mode)
62 .with_tracing(tracing_config)
63 } else {
64 ExecutionConfig::new(version)
65 .with_cost_budget(cmd.budget)
66 .with_run_mode(run_mode)
67 };
68
69 #[cfg(not(feature = "tracing"))]
70 let config = ExecutionConfig::new(version)
71 .with_cost_budget(cmd.budget)
72 .with_run_mode(run_mode);
73
74 let mut ledger = setup_ledger(&cmd)?;
76
77 if cmd.step {
79 execute_with_stepping(&vm, &bytecode, config, &mut ledger, global)
80 } else {
81 execute_normal(&vm, &bytecode, config, &mut ledger, global)
82 }
83}
84
85fn load_input(cmd: &ExecuteCommand) -> Result<Vec<u8>> {
87 match cmd.input_type {
88 InputType::Auto => auto_detect_and_load(&cmd.input),
89 InputType::File => load_from_file(&cmd.input),
90 InputType::Bytecode => decode_bytecode(&cmd.input),
91 InputType::Inline => assemble_inline(&cmd.input),
92 }
93}
94
95fn auto_detect_and_load(input: &str) -> Result<Vec<u8>> {
97 if Path::new(input).exists() {
99 return load_from_file(input);
100 }
101
102 if input
104 .chars()
105 .all(|c| c.is_ascii_hexdigit() || c.is_whitespace())
106 && input.len() > 10
107 {
108 if let Ok(bytecode) = decode_bytecode(input) {
109 return Ok(bytecode);
110 }
111 }
112
113 assemble_inline(input)
115}
116
117fn load_from_file(path: &str) -> Result<Vec<u8>> {
119 let content =
120 fs::read_to_string(path).with_context(|| format!("Failed to read file: {path}"))?;
121
122 if content.trim_start().starts_with("#pragma") || content.contains("int ") {
124 assemble_inline(&content)
126 } else {
127 decode_bytecode(&content)
129 }
130}
131
132fn decode_bytecode(hex: &str) -> Result<Vec<u8>> {
134 let hex = hex
135 .chars()
136 .filter(|c| !c.is_whitespace())
137 .collect::<String>();
138 hex::decode(&hex).with_context(|| "Invalid hex bytecode")
139}
140
141fn assemble_inline(source: &str) -> Result<Vec<u8>> {
143 let mut assembler = Assembler::new();
144 assembler
145 .assemble(source)
146 .map_err(|e| anyhow!("Assembly failed: {}", e))
147}
148
149fn setup_ledger(cmd: &ExecuteCommand) -> Result<MockLedger> {
151 let ledger = MockLedger::default();
152
153 if let Some(ledger_file) = &cmd.ledger {
155 let _content = fs::read_to_string(ledger_file)
156 .with_context(|| format!("Failed to read ledger file: {ledger_file:?}"))?;
157
158 }
161
162 if let Some(txn_file) = &cmd.transaction {
164 let _content = fs::read_to_string(txn_file)
165 .with_context(|| format!("Failed to read transaction file: {txn_file:?}"))?;
166
167 }
170
171 Ok(ledger)
172}
173
174fn execute_normal(
176 vm: &VirtualMachine,
177 bytecode: &[u8],
178 config: ExecutionConfig,
179 ledger: &mut MockLedger,
180 global: &GlobalOptions,
181) -> Result<()> {
182 let start = std::time::Instant::now();
183
184 let result = vm
185 .execute(bytecode, config.clone(), ledger)
186 .map_err(|e| anyhow!("Execution failed: {}", e))?;
187
188 let duration = start.elapsed();
189
190 if !global.quiet {
191 match global.format {
192 crate::cli::OutputFormat::Text => {
193 println!("✅ Execution completed successfully");
194 println!("Result: {result}");
195
196 if global.verbose {
197 println!("Duration: {duration:?}");
198 println!("Cost budget: {}", config.cost_budget);
199 }
200 }
201 crate::cli::OutputFormat::Json => {
202 let output = serde_json::json!({
203 "success": true,
204 "result": result,
205 "duration_ms": duration.as_millis(),
206 "cost_budget": config.cost_budget
207 });
208 println!("{}", serde_json::to_string_pretty(&output)?);
209 }
210 }
211 }
212
213 Ok(())
214}
215
216fn execute_with_stepping(
218 vm: &VirtualMachine,
219 bytecode: &[u8],
220 config: ExecutionConfig,
221 ledger: &mut MockLedger,
222 global: &GlobalOptions,
223) -> Result<()> {
224 use std::io::{self, Write};
225
226 if !global.quiet {
227 println!("🔍 Step-by-step execution mode");
228 println!("Commands: [Enter] step, 'c' continue, 'q' quit, 'h' help");
229 println!("{}", "─".repeat(60));
230 }
231
232 let start = std::time::Instant::now();
233
234 let mut eval_ctx = vm
236 .create_eval_context(bytecode, config.clone(), ledger)
237 .map_err(|e| anyhow::anyhow!("Failed to create evaluation context: {}", e))?;
238
239 let mut step_count = 0;
240 let mut continue_mode = false;
241
242 while !eval_ctx.is_finished() {
243 if !continue_mode && !global.quiet {
244 let opcode_info = eval_ctx
246 .current_opcode_spec(vm)
247 .map(|spec| format!("{} (cost: {})", spec.name, spec.cost))
248 .unwrap_or_else(|_| "Invalid opcode".to_string());
249
250 println!(
251 "Step {}: PC={:04} | {}",
252 step_count,
253 eval_ctx.pc(),
254 opcode_info
255 );
256
257 let stack = eval_ctx.stack();
259 if stack.is_empty() {
260 println!("Stack: (empty)");
261 } else {
262 println!(
263 "Stack: [{}]",
264 stack
265 .iter()
266 .enumerate()
267 .map(|(i, val)| format!("{i}: {val:?}"))
268 .collect::<Vec<_>>()
269 .join(", ")
270 );
271 }
272
273 print!("vm> ");
274 io::stdout().flush().unwrap();
275
276 let mut input = String::new();
278 io::stdin().read_line(&mut input).unwrap();
279 let input = input.trim();
280
281 match input {
282 "q" | "quit" => {
283 println!("Execution interrupted by user");
284 return Ok(());
285 }
286 "c" | "continue" => {
287 continue_mode = true;
288 println!("Continuing execution...");
289 }
290 "h" | "help" => {
291 println!("Commands:");
292 println!(" [Enter] - Execute next instruction");
293 println!(" c - Continue execution without stepping");
294 println!(" q - Quit execution");
295 println!(" h - Show this help");
296 continue;
297 }
298 "" => {
299 }
301 _ => {
302 println!("Unknown command '{input}'. Type 'h' for help.");
303 continue;
304 }
305 }
306 }
307
308 eval_ctx
310 .step(vm, &config)
311 .map_err(|e| anyhow::anyhow!("Execution failed at step {}: {}", step_count, e))?;
312
313 step_count += 1;
314 }
315
316 let result = if eval_ctx.is_finished() {
318 let stack = eval_ctx.stack();
320 if stack.is_empty() {
321 return Err(anyhow::anyhow!(
322 "Program ended with 0 values on stack, expected 1"
323 ));
324 }
325 if stack.len() > 1 {
326 return Err(anyhow::anyhow!(
327 "Program ended with {} values on stack, expected 1",
328 stack.len()
329 ));
330 }
331 stack[0]
332 .as_bool()
333 .map_err(|e| anyhow::anyhow!("Invalid final result: {}", e))?
334 } else {
335 return Err(anyhow::anyhow!("Program execution incomplete"));
336 };
337
338 let duration = start.elapsed();
339
340 if !global.quiet {
341 println!("{}", "─".repeat(60));
342 match global.format {
343 crate::cli::OutputFormat::Text => {
344 println!("✅ Execution completed successfully");
345 println!("Result: {result}");
346 println!("Steps: {step_count}");
347 println!("Duration: {duration:?}");
348 }
349 crate::cli::OutputFormat::Json => {
350 let output = serde_json::json!({
351 "success": true,
352 "result": result,
353 "steps": step_count,
354 "duration_ms": duration.as_millis()
355 });
356 println!("{}", serde_json::to_string_pretty(&output)?);
357 }
358 }
359 }
360
361 Ok(())
362}
363
364#[cfg(feature = "tracing")]
366fn build_tracing_config(cmd: &ExecuteCommand) -> Result<TracingConfig> {
367 let default_level = if (cmd.trace_opcodes || cmd.trace_stack) && cmd.trace_level.is_none() {
369 TracingLevel::Debug
370 } else {
371 TracingLevel::Info
372 };
373
374 let level = match cmd.trace_level.as_ref().unwrap_or(&default_level) {
375 TracingLevel::Trace => TraceLevel::Trace,
376 TracingLevel::Debug => TraceLevel::Debug,
377 TracingLevel::Info => TraceLevel::Info,
378 TracingLevel::Warn => TraceLevel::Warn,
379 TracingLevel::Error => TraceLevel::Error,
380 };
381
382 Ok(TracingConfig::new()
383 .with_level(level)
384 .with_opcodes(cmd.trace_opcodes)
385 .with_stack(cmd.trace_stack))
386}