Skip to main content

avm_rs/cli/commands/
execute.rs

1//! Execute command implementation
2
3use 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
16/// Handle the execute command
17pub 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    // Determine input type and load bytecode
38    let bytecode = load_input(&cmd)?;
39
40    // Create VM with specified version
41    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    // Configure execution
51    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    // Setup mock ledger
75    let mut ledger = setup_ledger(&cmd)?;
76
77    // Execute the program
78    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
85/// Load input based on type
86fn 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
95/// Auto-detect input type and load accordingly
96fn auto_detect_and_load(input: &str) -> Result<Vec<u8>> {
97    // Check if it's a file path
98    if Path::new(input).exists() {
99        return load_from_file(input);
100    }
101
102    // Check if it looks like hex bytecode
103    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    // Treat as inline TEAL
114    assemble_inline(input)
115}
116
117/// Load bytecode from file
118fn 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    // Check if file contains TEAL source or bytecode
123    if content.trim_start().starts_with("#pragma") || content.contains("int ") {
124        // TEAL source file
125        assemble_inline(&content)
126    } else {
127        // Assume bytecode file
128        decode_bytecode(&content)
129    }
130}
131
132/// Decode hex bytecode string
133fn 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
141/// Assemble inline TEAL source
142fn 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
149/// Setup mock ledger with optional data
150fn setup_ledger(cmd: &ExecuteCommand) -> Result<MockLedger> {
151    let ledger = MockLedger::default();
152
153    // Load ledger data if provided
154    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        // TODO: Implement JSON deserialization for ledger data
159        // For now, use default ledger
160    }
161
162    // Load transaction data if provided
163    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        // TODO: Implement JSON deserialization for transaction data
168        // For now, use default transaction
169    }
170
171    Ok(ledger)
172}
173
174/// Execute program normally
175fn 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
216/// Execute program with step-by-step debugging
217fn 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    // Create evaluation context for stepping
235    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            // Display current state
245            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            // Display stack
258            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            // Read user input
277            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                    // Step (default action)
300                }
301                _ => {
302                    println!("Unknown command '{input}'. Type 'h' for help.");
303                    continue;
304                }
305            }
306        }
307
308        // Execute one step
309        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    // Extract final result
317    let result = if eval_ctx.is_finished() {
318        // Check final result
319        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/// Build tracing configuration from CLI options
365#[cfg(feature = "tracing")]
366fn build_tracing_config(cmd: &ExecuteCommand) -> Result<TracingConfig> {
367    // If trace-opcodes or trace-stack are specified but no explicit level, default to debug
368    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}