synor/crates/synor-verifier/src/symbolic.rs
2026-02-02 05:58:22 +05:30

560 lines
18 KiB
Rust

//! 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<SymbolicValue>,
op: BinaryOperator,
right: Box<SymbolicValue>,
},
/// Unary operation.
UnaryOp {
op: UnaryOperator,
operand: Box<SymbolicValue>,
},
/// Conditional value (ITE = if-then-else).
Ite {
condition: Box<SymbolicValue>,
then_val: Box<SymbolicValue>,
else_val: Box<SymbolicValue>,
},
/// Array/mapping read.
Select {
array: Box<SymbolicValue>,
index: Box<SymbolicValue>,
},
/// Array/mapping write.
Store {
array: Box<SymbolicValue>,
index: Box<SymbolicValue>,
value: Box<SymbolicValue>,
},
}
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<Literal> {
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<Literal> {
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<Literal> {
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<String, SymbolicValue>,
/// Path condition (constraints on this path).
path_condition: Vec<SymbolicValue>,
/// 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<String, SymbolicValue> {
&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<SymbolicState> {
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<Vec<SymbolicState>> {
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<Vec<SymbolicState>> {
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<SymbolicValue> {
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);
}
}