//! Symbolic execution engine. //! //! Explores all possible execution paths through a contract. use serde::{Deserialize, Serialize}; use std::collections::HashMap; use crate::ast::{BinaryOperator, Expression, Literal, Statement, UnaryOperator}; use crate::error::{VerifierError, VerifierResult}; use crate::prover::ProverConfig; use crate::{VarType, VerificationContext}; /// Symbolic value representing an unknown or computed value. #[derive(Clone, Debug, Serialize, Deserialize)] pub enum SymbolicValue { /// Concrete literal value. Concrete(Literal), /// Symbolic variable. Symbol(String, VarType), /// Binary operation on symbolic values. BinaryOp { left: Box, op: BinaryOperator, right: Box, }, /// Unary operation. UnaryOp { op: UnaryOperator, operand: Box, }, /// Conditional value (ITE = if-then-else). Ite { condition: Box, then_val: Box, else_val: Box, }, /// Array/mapping read. Select { array: Box, index: Box, }, /// Array/mapping write. Store { array: Box, index: Box, value: Box, }, } impl SymbolicValue { /// Creates a new symbolic variable. pub fn symbol(name: &str, var_type: VarType) -> Self { SymbolicValue::Symbol(name.to_string(), var_type) } /// Creates a concrete boolean value. pub fn bool_const(v: bool) -> Self { SymbolicValue::Concrete(Literal::Bool(v)) } /// Creates a concrete uint value. pub fn uint_const(v: u128) -> Self { SymbolicValue::Concrete(Literal::Uint(v)) } /// Creates a binary operation. pub fn binary(left: SymbolicValue, op: BinaryOperator, right: SymbolicValue) -> Self { SymbolicValue::BinaryOp { left: Box::new(left), op, right: Box::new(right), } } /// Creates a conditional value. pub fn ite(condition: SymbolicValue, then_val: SymbolicValue, else_val: SymbolicValue) -> Self { SymbolicValue::Ite { condition: Box::new(condition), then_val: Box::new(then_val), else_val: Box::new(else_val), } } /// Attempts to evaluate to a concrete value. pub fn try_evaluate(&self) -> Option { match self { SymbolicValue::Concrete(lit) => Some(lit.clone()), SymbolicValue::BinaryOp { left, op, right } => { let l = left.try_evaluate()?; let r = right.try_evaluate()?; Self::evaluate_binary(&l, *op, &r) } SymbolicValue::UnaryOp { op, operand } => { let v = operand.try_evaluate()?; Self::evaluate_unary(*op, &v) } _ => None, } } fn evaluate_binary(left: &Literal, op: BinaryOperator, right: &Literal) -> Option { match (left, op, right) { (Literal::Uint(a), BinaryOperator::Add, Literal::Uint(b)) => { Some(Literal::Uint(a.wrapping_add(*b))) } (Literal::Uint(a), BinaryOperator::Sub, Literal::Uint(b)) => { Some(Literal::Uint(a.wrapping_sub(*b))) } (Literal::Uint(a), BinaryOperator::Mul, Literal::Uint(b)) => { Some(Literal::Uint(a.wrapping_mul(*b))) } (Literal::Uint(a), BinaryOperator::Div, Literal::Uint(b)) if *b != 0 => { Some(Literal::Uint(a / b)) } (Literal::Uint(a), BinaryOperator::Eq, Literal::Uint(b)) => Some(Literal::Bool(a == b)), (Literal::Uint(a), BinaryOperator::Lt, Literal::Uint(b)) => Some(Literal::Bool(a < b)), (Literal::Uint(a), BinaryOperator::Le, Literal::Uint(b)) => Some(Literal::Bool(a <= b)), (Literal::Uint(a), BinaryOperator::Gt, Literal::Uint(b)) => Some(Literal::Bool(a > b)), (Literal::Uint(a), BinaryOperator::Ge, Literal::Uint(b)) => Some(Literal::Bool(a >= b)), (Literal::Bool(a), BinaryOperator::And, Literal::Bool(b)) => { Some(Literal::Bool(*a && *b)) } (Literal::Bool(a), BinaryOperator::Or, Literal::Bool(b)) => { Some(Literal::Bool(*a || *b)) } _ => None, } } fn evaluate_unary(op: UnaryOperator, val: &Literal) -> Option { match (op, val) { (UnaryOperator::Not, Literal::Bool(b)) => Some(Literal::Bool(!b)), (UnaryOperator::Neg, Literal::Int(i)) => Some(Literal::Int(-i)), _ => None, } } } /// Symbolic execution state. #[derive(Clone, Debug)] pub struct SymbolicState { /// Variable bindings. variables: HashMap, /// Path condition (constraints on this path). path_condition: Vec, /// Execution depth. depth: usize, /// Whether this path is still feasible. feasible: bool, } impl SymbolicState { /// Creates a new empty symbolic state. pub fn new() -> Self { Self { variables: HashMap::new(), path_condition: Vec::new(), depth: 0, feasible: true, } } /// Gets a variable's symbolic value. pub fn get(&self, name: &str) -> Option<&SymbolicValue> { self.variables.get(name) } /// Sets a variable's symbolic value. pub fn set(&mut self, name: &str, value: SymbolicValue) { self.variables.insert(name.to_string(), value); } /// Adds a path constraint. pub fn assume(&mut self, constraint: SymbolicValue) { self.path_condition.push(constraint); } /// Returns the path condition. pub fn path_condition(&self) -> &[SymbolicValue] { &self.path_condition } /// Returns all variable bindings. pub fn variables(&self) -> &HashMap { &self.variables } /// Forks the state for branching. pub fn fork(&self) -> Self { Self { variables: self.variables.clone(), path_condition: self.path_condition.clone(), depth: self.depth, feasible: self.feasible, } } /// Increments depth. pub fn increment_depth(&mut self) { self.depth += 1; } /// Returns current depth. pub fn depth(&self) -> usize { self.depth } /// Marks path as infeasible. pub fn mark_infeasible(&mut self) { self.feasible = false; } /// Checks if path is still feasible. pub fn is_feasible(&self) -> bool { self.feasible } } impl Default for SymbolicState { fn default() -> Self { Self::new() } } /// Symbolic execution engine. pub struct SymbolicExecutor { config: ProverConfig, } impl SymbolicExecutor { /// Creates a new symbolic executor. pub fn new(config: ProverConfig) -> Self { Self { config } } /// Creates an initial symbolic state from contract context. pub fn create_initial_state(&self, ctx: &VerificationContext) -> VerifierResult { let mut state = SymbolicState::new(); // Create symbolic values for state variables for (name, var_type) in &ctx.state_vars { let sym = SymbolicValue::symbol(name, var_type.clone()); state.set(name, sym); } // Add msg.sender, msg.value, etc. state.set( "msg.sender", SymbolicValue::symbol("msg.sender", VarType::Address), ); state.set( "msg.value", SymbolicValue::symbol("msg.value", VarType::Uint(256)), ); state.set( "block.timestamp", SymbolicValue::symbol("block.timestamp", VarType::Uint(256)), ); state.set( "block.number", SymbolicValue::symbol("block.number", VarType::Uint(256)), ); Ok(state) } /// Explores all possible execution paths. pub fn explore_all_paths( &self, ctx: &VerificationContext, ) -> VerifierResult> { let initial = self.create_initial_state(ctx)?; let mut completed = Vec::new(); let mut worklist = vec![initial]; while let Some(state) = worklist.pop() { if state.depth() >= self.config.max_depth { // Depth limit reached completed.push(state); continue; } if !state.is_feasible() { // Path is infeasible, skip continue; } // For now, just return the initial state // Full implementation would execute each function completed.push(state); } Ok(completed) } /// Symbolically executes a statement. pub fn execute_statement( &self, stmt: &Statement, state: &mut SymbolicState, ) -> VerifierResult> { state.increment_depth(); if state.depth() >= self.config.max_depth { return Err(VerifierError::Internal("Max depth exceeded".to_string())); } match stmt { Statement::Assign { target, value } => { let sym_value = self.evaluate_expression(value, state)?; state.set(target, sym_value); Ok(vec![state.clone()]) } Statement::If { condition, then_branch, else_branch, } => { let cond = self.evaluate_expression(condition, state)?; // Fork state for both branches let mut then_state = state.fork(); then_state.assume(cond.clone()); let mut else_state = state.fork(); else_state.assume(SymbolicValue::UnaryOp { op: UnaryOperator::Not, operand: Box::new(cond), }); // Execute branches let mut results = Vec::new(); for stmt in then_branch { let states = self.execute_statement(stmt, &mut then_state)?; results.extend(states); } for stmt in else_branch { let states = self.execute_statement(stmt, &mut else_state)?; results.extend(states); } Ok(results) } Statement::While { condition, invariant: _, body, } => { // Bounded loop unrolling let mut current_states = vec![state.clone()]; let mut exit_states = Vec::new(); for _ in 0..self.config.max_loop_unroll { let mut next_states = Vec::new(); for s in current_states.drain(..) { let cond = self.evaluate_expression(condition, &s)?; // Continue if condition might be true let mut continue_state = s.fork(); continue_state.assume(cond.clone()); for stmt in body { let results = self.execute_statement(stmt, &mut continue_state)?; next_states.extend(results); } // Exit if condition might be false let mut exit_state = s; exit_state.assume(SymbolicValue::UnaryOp { op: UnaryOperator::Not, operand: Box::new(cond), }); exit_states.push(exit_state); } if next_states.is_empty() { break; } current_states = next_states; } // Combine remaining loop states with exit states exit_states.extend(current_states); Ok(exit_states) } Statement::Require(expr, _msg) => { let cond = self.evaluate_expression(expr, state)?; state.assume(cond); Ok(vec![state.clone()]) } Statement::Assert(expr) => { let cond = self.evaluate_expression(expr, state)?; state.assume(cond); Ok(vec![state.clone()]) } Statement::Assume(expr) => { let cond = self.evaluate_expression(expr, state)?; state.assume(cond); Ok(vec![state.clone()]) } Statement::Return(_) => { // End of path Ok(vec![state.clone()]) } Statement::Block(stmts) => { let mut current = state.clone(); for stmt in stmts { let results = self.execute_statement(stmt, &mut current)?; if results.is_empty() { return Ok(vec![]); } // Take first result (simplification) current = results.into_iter().next().unwrap(); } Ok(vec![current]) } _ => { // Other statements not yet implemented Ok(vec![state.clone()]) } } } /// Evaluates an expression to a symbolic value. pub fn evaluate_expression( &self, expr: &Expression, state: &SymbolicState, ) -> VerifierResult { match expr { Expression::Literal(lit) => Ok(SymbolicValue::Concrete(lit.clone())), Expression::Variable(name) => { if let Some(val) = state.get(name) { Ok(val.clone()) } else { // Unknown variable - create fresh symbol Ok(SymbolicValue::symbol(name, VarType::Uint(256))) } } Expression::BinaryOp { left, op, right } => { let l = self.evaluate_expression(left, state)?; let r = self.evaluate_expression(right, state)?; Ok(SymbolicValue::binary(l, *op, r)) } Expression::UnaryOp { op, operand } => { let v = self.evaluate_expression(operand, state)?; Ok(SymbolicValue::UnaryOp { op: *op, operand: Box::new(v), }) } Expression::Ternary { condition, then_expr, else_expr, } => { let c = self.evaluate_expression(condition, state)?; let t = self.evaluate_expression(then_expr, state)?; let e = self.evaluate_expression(else_expr, state)?; Ok(SymbolicValue::ite(c, t, e)) } Expression::MemberAccess { object, member } => { // Simplified: create compound symbol name let obj = self.evaluate_expression(object, state)?; let name = format!("{:?}.{}", obj, member); Ok(SymbolicValue::symbol(&name, VarType::Uint(256))) } Expression::Index { array, index } => { let arr = self.evaluate_expression(array, state)?; let idx = self.evaluate_expression(index, state)?; Ok(SymbolicValue::Select { array: Box::new(arr), index: Box::new(idx), }) } Expression::Old(inner) => { // Old values are just the initial symbolic values self.evaluate_expression(inner, state) } Expression::Result => { // Result is a special symbol Ok(SymbolicValue::symbol("__result", VarType::Uint(256))) } Expression::FunctionCall { function, args: _ } => { // Model function call result as uninterpreted function let name = format!("{}(...)", function); Ok(SymbolicValue::symbol(&name, VarType::Uint(256))) } _ => { // Other expressions Ok(SymbolicValue::symbol("unknown", VarType::Uint(256))) } } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_symbolic_value_concrete() { let v = SymbolicValue::uint_const(42); assert!(matches!(v.try_evaluate(), Some(Literal::Uint(42)))); } #[test] fn test_symbolic_value_binary() { let a = SymbolicValue::uint_const(10); let b = SymbolicValue::uint_const(5); let sum = SymbolicValue::binary(a, BinaryOperator::Add, b); assert!(matches!(sum.try_evaluate(), Some(Literal::Uint(15)))); } #[test] fn test_symbolic_state() { let mut state = SymbolicState::new(); state.set("x", SymbolicValue::uint_const(100)); assert!(state.get("x").is_some()); assert!(state.get("y").is_none()); } #[test] fn test_state_fork() { let mut state = SymbolicState::new(); state.set("x", SymbolicValue::uint_const(1)); let forked = state.fork(); assert!(forked.get("x").is_some()); } #[test] fn test_executor_creation() { let executor = SymbolicExecutor::new(ProverConfig::default()); assert_eq!(executor.config.max_depth, 100); } }