diff --git a/debugger/cli/src/main.rs b/debugger/cli/src/main.rs index 010652c8..47569b6d 100644 --- a/debugger/cli/src/main.rs +++ b/debugger/cli/src/main.rs @@ -5,12 +5,12 @@ use std::path::{Path, PathBuf}; use clap::Parser; use debugger_session::args::{parse_call_args, parse_ctor_args, parse_hex_bytes}; -use debugger_session::format_failure_report; use debugger_session::session::{DebugEngine, DebugSession, ShadowTxContext, Variable, VariableOrigin}; use debugger_session::test_runner::{ TestExpectation, TestTxInputScenarioResolved, TestTxOutputScenarioResolved, TestTxScenarioResolved, discover_sidecar_path, resolve_contract_test, }; +use debugger_session::{format_failure_report, format_value}; use kaspa_consensus_core::Hash; use kaspa_consensus_core::hashing::sighash::SigHashReusedValuesUnsync; use kaspa_consensus_core::tx::{ @@ -127,30 +127,25 @@ fn show_vars(session: &DebugSession<'_, '_>) { if variables.is_empty() { println!("No variables in scope."); } else { - print_variable_section(session, "Contract Constants", &variables, |origin| { + print_variable_section("Contract Constants", &variables, |origin| { matches!(origin, VariableOrigin::ConstructorArg | VariableOrigin::Constant) }); - print_variable_section(session, "Entrypoint Parameters", &variables, |origin| origin == VariableOrigin::Param); - print_variable_section(session, "Locals", &variables, |origin| origin == VariableOrigin::Local); + print_variable_section("Entrypoint Parameters", &variables, |origin| origin == VariableOrigin::Param); + print_variable_section("Locals", &variables, |origin| origin == VariableOrigin::Local); } } Err(err) => println!("ERROR: {err}"), } } -fn print_variable_section( - session: &DebugSession<'_, '_>, - title: &str, - variables: &[Variable], - matches_origin: impl Fn(VariableOrigin) -> bool, -) { +fn print_variable_section(title: &str, variables: &[Variable], matches_origin: impl Fn(VariableOrigin) -> bool) { let section_vars: Vec<_> = variables.iter().filter(|var| matches_origin(var.origin)).collect(); if section_vars.is_empty() { return; } println!("{title}:"); for var in section_vars { - println!(" {} ({}) = {}", var.name, var.type_name, session.format_value(&var.type_name, &var.value)); + println!(" {} ({}) = {}", var.name, var.type_name, format_value(&var.type_name, &var.value)); } } @@ -182,7 +177,7 @@ fn show_step_view(session: &DebugSession<'_, '_>, console_lines: &[String]) { fn print_failure(session: &DebugSession<'_, '_>, err: kaspa_txscript_errors::TxScriptError) { let report = session.build_failure_report(&err); - let formatted = format_failure_report(&report, &|type_name, value| session.format_value(type_name, value)); + let formatted = format_failure_report(&report, &format_value); eprintln!("{formatted}"); } @@ -310,8 +305,8 @@ fn run_repl(session: &mut DebugSession<'_, '_>) -> Result<(), Box"); } else { match session.evaluate_expression(rest) { - Ok(result) => { - println!("{rest} = ({}) {}", result.type_name, session.format_value(&result.type_name, &result.value)); + Ok((type_name, value)) => { + println!("{rest} = ({type_name}) {}", format_value(&type_name, &value)); } Err(err) => println!("ERROR: {err}"), } @@ -321,7 +316,7 @@ fn run_repl(session: &mut DebugSession<'_, '_>) -> Result<(), Box { - println!("{} ({}) = {}", var.name, var.type_name, session.format_value(&var.type_name, &var.value)); + println!("{} ({}) = {}", var.name, var.type_name, format_value(&var.type_name, &var.value)); } Err(err) => println!("ERROR: {err}"), } diff --git a/debugger/session/src/lib.rs b/debugger/session/src/lib.rs index a97f3ea6..a7d4923f 100644 --- a/debugger/session/src/lib.rs +++ b/debugger/session/src/lib.rs @@ -4,5 +4,5 @@ pub mod session; pub mod test_runner; pub mod util; -pub use presentation::format_failure_report; +pub use presentation::{format_failure_report, format_value}; pub use session::{CallStackEntry, FailureFrame, FailureReport}; diff --git a/debugger/session/src/session.rs b/debugger/session/src/session.rs index f9ce84b0..cee32149 100644 --- a/debugger/session/src/session.rs +++ b/debugger/session/src/session.rs @@ -68,7 +68,6 @@ pub struct Variable { pub name: String, pub type_name: String, pub value: DebugValue, - pub is_constant: bool, pub origin: VariableOrigin, } @@ -108,12 +107,6 @@ pub struct FailureReport { pub source_text: String, } -#[derive(Debug, Clone)] -pub struct EvaluatedExpression { - pub type_name: String, - pub value: DebugValue, -} - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct StackSnapshot { pub dstack: Vec, @@ -174,7 +167,6 @@ struct ScopeBinding<'i> { type_name: String, source: ScopeValueSource<'i>, origin: VariableOrigin, - is_constant: bool, hidden: bool, } @@ -298,7 +290,7 @@ impl<'a, 'i> DebugSession<'a, 'i> { loop { let Some(target_index) = self.next_steppable_step_index(search_from, |step| predicate(step.call_depth, current_depth)) else { - while self.step_opcode()?.is_some() {} + self.run_until_end()?; return Ok(None); }; @@ -312,6 +304,11 @@ impl<'a, 'i> DebugSession<'a, 'i> { } } + fn run_until_end(&mut self) -> Result<(), kaspa_txscript_errors::TxScriptError> { + while self.step_opcode()?.is_some() {} + Ok(()) + } + fn advance_to_step(&mut self, target_index: usize) -> Result { let Some(target) = self.step_at_order(target_index) else { return Ok(false); @@ -464,7 +461,7 @@ impl<'a, 'i> DebugSession<'a, 'i> { /// Returns all variables in scope at current execution point. /// Includes locals, params, constructor args, and contract constants. pub fn list_variables(&self) -> Result, String> { - self.collect_variables(self.active_scope_step_id()) + self.collect_variables(self.current_scope_step_id()) } pub fn list_variables_at_sequence(&self, sequence: u32, frame_id: u32) -> Result, String> { @@ -473,29 +470,23 @@ impl<'a, 'i> DebugSession<'a, 'i> { fn collect_variables(&self, step_id: StepId) -> Result, String> { let scope_state = self.scope_state(step_id)?; - let mut variables = self.collect_variables_map(&scope_state)?.into_values().collect::>(); + let mut variables = self.collect_variables_map(&scope_state).into_values().collect::>(); variables.sort_by(|a, b| a.name.cmp(&b.name)); Ok(variables) } /// Returns a specific variable by name, or error if not in scope. pub fn variable_by_name(&self, name: &str) -> Result { - let scope_state = self.scope_state(self.active_scope_step_id())?; - let variables = self.collect_variables_map(&scope_state)?; + let scope_state = self.current_scope_state()?; + let variables = self.collect_variables_map(&scope_state); variables.get(name).cloned().ok_or_else(|| format!("unknown variable '{name}'")) } - pub fn evaluate_expression(&self, expr_src: &str) -> Result { + pub fn evaluate_expression(&self, expr_src: &str) -> Result<(String, DebugValue), String> { let expr = parse_expression_ast(expr_src).map_err(|err| format!("parse error: {err}"))?; self.evaluate_parsed_expression(&expr) } - // --- DebugValue formatting --- - /// Formats a debug value for display based on its type. - pub fn format_value(&self, type_name: &str, value: &DebugValue) -> String { - format_debug_value(type_name, value) - } - /// Returns the debug step for the current bytecode position. pub fn current_step(&self) -> Option> { self.current_timeline_step().cloned().or_else(|| self.step_for_offset(self.current_byte_offset()).cloned()) @@ -513,13 +504,7 @@ impl<'a, 'i> DebugSession<'a, 'i> { pub fn call_stack(&self) -> Vec { let mut stack = Vec::new(); - let Some(current) = self.current_step_index else { - return stack; - }; - for order_index in 0..=current { - let Some(step) = self.step_at_order(order_index) else { - continue; - }; + for step in self.active_steps() { match &step.kind { StepKind::InlineCallEnter { callee } => stack.push(callee.clone()), StepKind::InlineCallExit { .. } => { @@ -534,13 +519,7 @@ impl<'a, 'i> DebugSession<'a, 'i> { /// Returns the active inline call stack with source spans and frame identity. pub fn call_stack_with_spans(&self) -> Vec { let mut stack = Vec::new(); - let Some(current) = self.current_step_index else { - return stack; - }; - for order_index in 0..=current { - let Some(step) = self.step_at_order(order_index) else { - continue; - }; + for step in self.active_steps() { match &step.kind { StepKind::InlineCallEnter { callee } => stack.push(CallStackEntry { callee_name: callee.clone(), @@ -592,14 +571,9 @@ impl<'a, 'i> DebugSession<'a, 'i> { }) } - fn visible_scope(&self, step_id: StepId) -> Result, String> { - let context = self.current_variable_context(step_id)?; - let updates = self.current_variable_updates(&context); - Ok(VisibleScope { context, updates }) - } - fn scope_state(&self, step_id: StepId) -> Result, String> { - let scope = self.visible_scope(step_id)?; + let context = self.current_variable_context(step_id)?; + let scope = VisibleScope { updates: self.current_variable_updates(&context), context }; Ok(self.scope_state_from_visible(&scope)) } @@ -611,13 +585,12 @@ impl<'a, 'i> DebugSession<'a, 'i> { type_name: param.type_name.clone(), source: ScopeValueSource::RuntimeSlot { from_top: param.stack_index }, origin: VariableOrigin::Param, - is_constant: false, hidden: false, }); } - record_debug_named_values(&mut bindings, &self.debug_info.constructor_args, VariableOrigin::ConstructorArg, false); - record_debug_named_values(&mut bindings, &self.debug_info.constants, VariableOrigin::Constant, true); + record_debug_named_values(&mut bindings, &self.debug_info.constructor_args, VariableOrigin::ConstructorArg); + record_debug_named_values(&mut bindings, &self.debug_info.constants, VariableOrigin::Constant); for (name, update) in &scope.updates { let source = match update.runtime_binding.as_ref() { @@ -635,7 +608,6 @@ impl<'a, 'i> DebugSession<'a, 'i> { type_name: update.type_name.clone(), source, origin: VariableOrigin::Local, - is_constant: false, hidden: is_inline_synthetic_name(name), }); } @@ -643,7 +615,7 @@ impl<'a, 'i> DebugSession<'a, 'i> { bindings } - fn collect_variables_map(&self, scope_state: &ScopeState<'i>) -> Result, String> { + fn collect_variables_map(&self, scope_state: &ScopeState<'i>) -> HashMap { let mut variables: HashMap = HashMap::new(); for (name, binding) in scope_state { @@ -653,17 +625,11 @@ impl<'a, 'i> DebugSession<'a, 'i> { let value = self.resolve_scope_binding(scope_state, binding).unwrap_or_else(DebugValue::Unknown); variables.insert( name.clone(), - Variable { - name: name.clone(), - type_name: binding.type_name.clone(), - value, - is_constant: binding.is_constant, - origin: binding.origin, - }, + Variable { name: name.clone(), type_name: binding.type_name.clone(), value, origin: binding.origin }, ); } - Ok(variables) + variables } fn step_updates_are_visible(&self, step: &DebugStep<'i>, context: &VariableContext<'_>) -> bool { @@ -685,7 +651,7 @@ impl<'a, 'i> DebugSession<'a, 'i> { let mut best: Option<&DebugStep<'i>> = None; let mut best_len = usize::MAX; for step in &self.debug_info.steps { - if step_matches_offset(step, offset) { + if range_matches_offset(step.bytecode_start, step.bytecode_end, offset) { let len = step.bytecode_end.saturating_sub(step.bytecode_start); if len < best_len { best = Some(step); @@ -705,16 +671,12 @@ impl<'a, 'i> DebugSession<'a, 'i> { self.current_step_index.and_then(|index| self.step_at_order(index)) } - fn current_step_id(&self) -> StepId { - self.current_timeline_step().map(DebugStep::id).unwrap_or(StepId::ROOT) - } - - fn active_scope_step_id(&self) -> StepId { + fn current_scope_step_id(&self) -> StepId { let Some(current_index) = self.current_step_index else { - return self.current_step_id(); + return self.current_timeline_step().map(DebugStep::id).unwrap_or(StepId::ROOT); }; let Some(current_step) = self.current_timeline_step() else { - return self.current_step_id(); + return StepId::ROOT; }; if !matches!(current_step.kind, StepKind::InlineCallEnter { .. }) { return current_step.id(); @@ -727,6 +689,15 @@ impl<'a, 'i> DebugSession<'a, 'i> { current_step.id() } + fn current_scope_state(&self) -> Result, String> { + self.scope_state(self.current_scope_step_id()) + } + + fn active_steps(&self) -> impl Iterator> + '_ { + let end = self.current_step_index.map(|index| index + 1).unwrap_or(0); + self.step_order[..end].iter().filter_map(|&step_index| self.debug_info.steps.get(step_index)) + } + fn mark_step_executed(&mut self, step_index: usize) { if let Some(step) = self.step_at_order(step_index).cloned() { self.executed_steps.insert(step.id()); @@ -743,8 +714,8 @@ impl<'a, 'i> DebugSession<'a, 'i> { step.console_args .iter() .map(|expr| match self.evaluate_parsed_expression(expr) { - Ok(result) => self.format_value(&result.type_name, &result.value), - Err(err) => self.format_value("", &DebugValue::Unknown(err)), + Ok((type_name, value)) => format_debug_value(&type_name, &value), + Err(err) => format_debug_value("", &DebugValue::Unknown(err)), }) .collect::>() .join(" "), @@ -798,7 +769,8 @@ impl<'a, 'i> DebugSession<'a, 'i> { } self.find_steppable_step_index(|step| { - step_matches_offset(step, offset) && min_sequence.is_none_or(|min_sequence| step.sequence >= min_sequence) + range_matches_offset(step.bytecode_start, step.bytecode_end, offset) + && min_sequence.is_none_or(|min_sequence| step.sequence >= min_sequence) }) } @@ -938,7 +910,8 @@ impl<'a, 'i> DebugSession<'a, 'i> { let failure_span = self.current_span(); let call_stack = self.call_stack_with_spans(); let innermost_function = self.current_function_name().unwrap_or("").to_string(); - let innermost_vars: Vec = self.list_variables().unwrap_or_default().into_iter().filter(|v| !v.is_constant).collect(); + let innermost_vars: Vec = + self.list_variables().unwrap_or_default().into_iter().filter(|v| v.origin != VariableOrigin::Constant).collect(); let mut frames = vec![FailureFrame { function_name: innermost_function.clone(), span: failure_span, variables: innermost_vars }]; @@ -950,7 +923,7 @@ impl<'a, 'i> DebugSession<'a, 'i> { .list_variables_at_sequence(entry.sequence, entry.frame_id) .unwrap_or_default() .into_iter() - .filter(|v| !v.is_constant) + .filter(|v| v.origin != VariableOrigin::Constant) .collect(); let caller_name = if idx == 0 { entry_name.clone() } else { call_stack[idx - 1].callee_name.clone() }; frames.push(FailureFrame { function_name: caller_name, span: entry.call_site_span, variables: caller_vars }); @@ -979,19 +952,19 @@ impl<'a, 'i> DebugSession<'a, 'i> { decode_value_by_type(type_name, bytes) } - fn evaluate_parsed_expression(&self, expr: &Expr<'i>) -> Result { - let scope_state = self.scope_state(self.active_scope_step_id())?; + fn evaluate_parsed_expression(&self, expr: &Expr<'i>) -> Result<(String, DebugValue), String> { + let scope_state = self.current_scope_state()?; self.evaluate_expr_in_scope(&scope_state, expr) } - fn evaluate_expr_in_scope(&self, scope_state: &ScopeState<'i>, expr: &Expr<'i>) -> Result { + fn evaluate_expr_in_scope(&self, scope_state: &ScopeState<'i>, expr: &Expr<'i>) -> Result<(String, DebugValue), String> { let (shadow_bindings, env, stack_bindings, eval_types) = self.scope_state_eval_context(scope_state)?; let (bytecode, type_name) = compile_debug_expr(expr, &env, &stack_bindings, &eval_types) .map_err(|err| format!("failed to compile debug expression: {err}"))?; let script = self.build_shadow_script(&shadow_bindings, &bytecode)?; let bytes = self.execute_shadow_script(&script)?; let value = decode_value_by_type(&type_name, bytes)?; - Ok(EvaluatedExpression { type_name, value }) + Ok((type_name, value)) } fn scope_state_eval_context(&self, scope_state: &ScopeState<'i>) -> Result, String> { @@ -1179,26 +1152,16 @@ fn range_matches_offset(bytecode_start: usize, bytecode_end: usize, offset: usiz if bytecode_start == bytecode_end { offset == bytecode_start } else { offset >= bytecode_start && offset < bytecode_end } } -fn step_matches_offset(step: &DebugStep<'_>, offset: usize) -> bool { - range_matches_offset(step.bytecode_start, step.bytecode_end, offset) -} - fn is_inline_synthetic_name(name: &str) -> bool { name.starts_with("__arg_") } -fn record_debug_named_values<'i>( - bindings: &mut ScopeState<'i>, - values: &[DebugNamedValue<'i>], - origin: VariableOrigin, - is_constant: bool, -) { +fn record_debug_named_values<'i>(bindings: &mut ScopeState<'i>, values: &[DebugNamedValue<'i>], origin: VariableOrigin) { for value in values { bindings.entry(value.name.clone()).or_insert_with(|| ScopeBinding { type_name: value.type_name.clone(), source: ScopeValueSource::Expr(value.value.clone()), origin, - is_constant, hidden: false, }); } @@ -1454,7 +1417,7 @@ mod tests { }; let session = DebugSession::full(&[], &[], "", Some(debug_info), engine).unwrap(); let scope_state = session.scope_state(StepId::ROOT).unwrap(); - let vars = session.collect_variables_map(&scope_state).unwrap(); + let vars = session.collect_variables_map(&scope_state); let pair = vars.get("DEFAULT_PAIR").expect("DEFAULT_PAIR variable"); match &pair.value { DebugValue::Object(fields) => { @@ -1569,7 +1532,7 @@ mod tests { session.current_step_index = Some(1); let x = session.variable_by_name("x").unwrap(); - assert_eq!(session.format_value(&x.type_name, &x.value), "5"); + assert_eq!(crate::presentation::format_value(&x.type_name, &x.value), "5"); } #[test] @@ -1618,16 +1581,16 @@ mod tests { session.current_step_index = Some(1); let literal = session.evaluate_expression("1 + 2").unwrap(); - assert_eq!(literal.type_name, "int"); - assert!(matches!(literal.value, DebugValue::Int(3))); + assert_eq!(literal.0, "int"); + assert!(matches!(literal.1, DebugValue::Int(3))); let scoped = session.evaluate_expression("x + 1").unwrap(); - assert_eq!(scoped.type_name, "int"); - assert!(matches!(scoped.value, DebugValue::Int(6))); + assert_eq!(scoped.0, "int"); + assert!(matches!(scoped.1, DebugValue::Int(6))); let constant = session.evaluate_expression("K + 1").unwrap(); - assert_eq!(constant.type_name, "int"); - assert!(matches!(constant.value, DebugValue::Int(8))); + assert_eq!(constant.0, "int"); + assert!(matches!(constant.1, DebugValue::Int(8))); let parse_err = session.evaluate_expression("1 +").unwrap_err(); assert!(parse_err.contains("parse error")); diff --git a/debugger/session/tests/debug_session_tests.rs b/debugger/session/tests/debug_session_tests.rs index 1879498f..3ca288d1 100644 --- a/debugger/session/tests/debug_session_tests.rs +++ b/debugger/session/tests/debug_session_tests.rs @@ -12,7 +12,10 @@ use kaspa_txscript::covenants::CovenantsContext; use kaspa_txscript::opcodes::codes::OpTrue; use kaspa_txscript::{EngineCtx, EngineFlags}; -use debugger_session::session::{DebugSession, DebugValue, ShadowTxContext}; +use debugger_session::{ + format_value, + session::{DebugSession, DebugValue, ShadowTxContext}, +}; use silverscript_lang::ast::{Expr, ExprKind, parse_contract_ast}; use silverscript_lang::compiler::{CompileOptions, compile_contract}; use silverscript_lang::debug_info::StepKind; @@ -209,8 +212,8 @@ contract Shadow(int x) { assert_eq!(x_count, 1, "expected a single visible x variable"); let x = session.variable_by_name("x")?; - assert!(!x.is_constant, "function parameter should shadow constructor constant"); - assert_eq!(session.format_value(&x.type_name, &x.value), "3"); + assert_eq!(x.origin.label(), "arg", "function parameter should shadow constructor constant"); + assert_eq!(format_value(&x.type_name, &x.value), "3"); Ok(()) }) } @@ -233,15 +236,15 @@ contract ShadowMath(int fee) { session.step_over()?; let local_after_init = session.variable_by_name("local")?; - assert_eq!(session.format_value(&local_after_init.type_name, &local_after_init.value), "4"); + assert_eq!(format_value(&local_after_init.type_name, &local_after_init.value), "4"); session.step_over()?; let local_after_update = session.variable_by_name("local")?; - assert_eq!(session.format_value(&local_after_update.type_name, &local_after_update.value), "7"); + assert_eq!(format_value(&local_after_update.type_name, &local_after_update.value), "7"); let fee = session.variable_by_name("fee")?; - assert!(!fee.is_constant); - assert_eq!(session.format_value(&fee.type_name, &fee.value), "3"); + assert_eq!(fee.origin.label(), "arg"); + assert_eq!(format_value(&fee.type_name, &fee.value), "3"); Ok(()) }) } @@ -263,10 +266,10 @@ contract FieldOffset(int c) { session.run_to_first_executed_statement()?; let a = session.variable_by_name("a")?; - assert_eq!(session.format_value(&a.type_name, &a.value), "5"); + assert_eq!(format_value(&a.type_name, &a.value), "5"); let x = session.variable_by_name("x")?; - assert_eq!(session.format_value(&x.type_name, &x.value), "7"); + assert_eq!(format_value(&x.type_name, &x.value), "7"); Ok(()) }) } @@ -290,7 +293,7 @@ contract FieldMath(int c) { for _ in 0..4 { if let Ok(z) = session.variable_by_name("z") { - assert_eq!(session.format_value(&z.type_name, &z.value), "14"); + assert_eq!(format_value(&z.type_name, &z.value), "14"); return Ok(()); } if session.step_over()?.is_none() { @@ -358,7 +361,7 @@ contract OpcodeCursor() { assert_ne!(after_si.line, start.line, "si should refresh statement cursor"); let x = session.variable_by_name("x")?; - assert_eq!(session.format_value(&x.type_name, &x.value), "1"); + assert_eq!(format_value(&x.type_name, &x.value), "1"); Ok(()) }) } @@ -406,11 +409,11 @@ contract LocalVars() { session.step_over()?; let x_after_init = session.variable_by_name("x")?; - assert_eq!(session.format_value(&x_after_init.type_name, &x_after_init.value), "4"); + assert_eq!(format_value(&x_after_init.type_name, &x_after_init.value), "4"); session.step_over()?; let x_after_assign = session.variable_by_name("x")?; - assert_eq!(session.format_value(&x_after_assign.type_name, &x_after_assign.value), "6"); + assert_eq!(format_value(&x_after_assign.type_name, &x_after_assign.value), "6"); Ok(()) }) } @@ -462,7 +465,7 @@ contract InlineCalls() { } assert_eq!(after_over.line, 11, "step_over should eventually move past inline call"); let b = session.variable_by_name("b")?; - assert_eq!(session.format_value(&b.type_name, &b.value), "4", "inline return should resolve against caller params"); + assert_eq!(format_value(&b.type_name, &b.value), "4", "inline return should resolve against caller params"); Ok(()) })?; @@ -670,7 +673,7 @@ contract InlineParams() { let in_callee = session.call_stack().iter().any(|name| name == "add1"); if in_callee { if let Ok(x) = session.variable_by_name("x") { - let rendered = session.format_value(&x.type_name, &x.value); + let rendered = format_value(&x.type_name, &x.value); assert_eq!(rendered, "4", "inline param x should reflect caller-provided value"); saw_inline_param = true; break; @@ -722,12 +725,9 @@ contract InlineEval() { _ => return Err("expected inline callee bindings x and y to be ints".into()), }; - let evaluated = session.evaluate_expression("((y * 2) + (x - 1)) - (y - x)")?; - assert_eq!(evaluated.type_name, "int"); - assert_eq!( - session.format_value(&evaluated.type_name, &evaluated.value), - ((y_value * 2) + (x_value - 1) - (y_value - x_value)).to_string() - ); + let (type_name, value) = session.evaluate_expression("((y * 2) + (x - 1)) - (y - x)")?; + assert_eq!(type_name, "int"); + assert_eq!(format_value(&type_name, &value), ((y_value * 2) + (x_value - 1) - (y_value - x_value)).to_string()); Ok(()) }) } @@ -752,15 +752,13 @@ contract ScopeKinds(int init_amount) { let vars = session.list_variables()?; let init_amount = vars.iter().find(|var| var.name == "init_amount").ok_or("missing ctor arg")?; assert_eq!(init_amount.origin.label(), "ctor"); - assert!(!init_amount.is_constant); let bonus = vars.iter().find(|var| var.name == "BONUS").ok_or("missing contract constant")?; assert_eq!(bonus.origin.label(), "const"); - assert!(bonus.is_constant); - let evaluated = session.evaluate_expression("init_amount + BONUS + delta")?; - assert_eq!(evaluated.type_name, "int"); - assert_eq!(session.format_value(&evaluated.type_name, &evaluated.value), "12"); + let (type_name, value) = session.evaluate_expression("init_amount + BONUS + delta")?; + assert_eq!(type_name, "int"); + assert_eq!(format_value(&type_name, &value), "12"); Ok(()) }) } @@ -799,7 +797,7 @@ contract StepVisibility(int init_amount) { session.current_span().ok_or("missing span after step")?; let base = session.variable_by_name("base")?; - assert_eq!(session.format_value(&base.type_name, &base.value), "11"); + assert_eq!(format_value(&base.type_name, &base.value), "11"); Ok(()) }, ) @@ -852,19 +850,19 @@ contract ShiftedBindings() { assert!(current_line > call_line, "expected to step past inline call"); let amount = session.variable_by_name("amount")?; - assert_eq!(session.format_value(&amount.type_name, &amount.value), "11"); + assert_eq!(format_value(&amount.type_name, &amount.value), "11"); let delta = session.variable_by_name("delta")?; - assert_eq!(session.format_value(&delta.type_name, &delta.value), "3"); + assert_eq!(format_value(&delta.type_name, &delta.value), "3"); let values = session.variable_by_name("values")?; - assert_eq!(session.format_value(&values.type_name, &values.value), "[4, 5]"); + assert_eq!(format_value(&values.type_name, &values.value), "[4, 5]"); let base = session.variable_by_name("base")?; - assert_eq!(session.format_value(&base.type_name, &base.value), "15"); + assert_eq!(format_value(&base.type_name, &base.value), "15"); let after = session.variable_by_name("after")?; - assert_eq!(session.format_value(&after.type_name, &after.value), "20"); + assert_eq!(format_value(&after.type_name, &after.value), "20"); Ok(()) }, @@ -930,7 +928,7 @@ contract LoopIndex() { for _ in 0..12 { if let Ok(i) = session.variable_by_name("i") { - assert_eq!(session.format_value(&i.type_name, &i.value), "0"); + assert_eq!(format_value(&i.type_name, &i.value), "0"); saw_loop_index = true; break; } @@ -998,7 +996,7 @@ contract CovLocal() { for _ in 0..4 { if let Ok(covid) = session.variable_by_name("covid") { - let rendered = session.format_value(&covid.type_name, &covid.value); + let rendered = format_value(&covid.type_name, &covid.value); assert_eq!(rendered, format!("0x{}", "11".repeat(32))); return Ok(()); } @@ -1061,8 +1059,8 @@ contract CovEval() { let mut session = DebugSession::full(&sigscript, &compiled.script, source, debug_info, engine)?.with_shadow_tx_context(shadow_ctx); session.run_to_first_executed_statement()?; - let evaluated = session.evaluate_expression("OpInputCovenantId(this.activeInputIndex)")?; - assert_eq!(evaluated.type_name, "byte[32]"); - assert_eq!(session.format_value(&evaluated.type_name, &evaluated.value), format!("0x{}", "22".repeat(32))); + let (type_name, value) = session.evaluate_expression("OpInputCovenantId(this.activeInputIndex)")?; + assert_eq!(type_name, "byte[32]"); + assert_eq!(format_value(&type_name, &value), format!("0x{}", "22".repeat(32))); Ok(()) }