synor/crates/synor-verifier/src/parser.rs
Gulshan Yadav 8b152a5a23 feat(tooling): add Phase 14 M4 - Developer Tooling
Adds formal verification DSL, multi-sig contract, and Hardhat plugin:

synor-verifier crate:
- Verification DSL for contract invariants and properties
- SMT solver integration (Z3 backend optional)
- Symbolic execution engine for path exploration
- Automatic vulnerability detection (reentrancy, overflow, etc.)
- 29 tests passing

contracts/multi-sig:
- M-of-N multi-signature wallet contract
- Transaction proposals with timelock
- Owner management (add/remove)
- Emergency pause functionality
- Native token and contract call support

apps/hardhat-plugin (@synor/hardhat-plugin):
- Network configuration for mainnet/testnet/devnet
- Contract deployment with gas estimation
- Contract verification on explorer
- WASM compilation support
- TypeScript type generation
- Testing utilities (fork, impersonate, time manipulation)
- Synor-specific RPC methods (quantum status, shard info, DAG)
2026-01-19 20:55:56 +05:30

616 lines
19 KiB
Rust

//! Parser for the verification DSL.
//!
//! Parses specification files and Solidity-style annotations.
use crate::ast::*;
use crate::error::{VerifierError, VerifierResult};
use crate::{FunctionSig, Mutability, VarType, VerificationContext, Visibility};
use std::collections::HashMap;
/// Specification parser.
pub struct SpecParser;
impl SpecParser {
/// Parses a contract from source code with embedded specifications.
pub fn parse_contract(source: &str) -> VerifierResult<VerificationContext> {
let mut ctx = VerificationContext {
contract_name: String::new(),
source: source.to_string(),
specs: Vec::new(),
state_vars: HashMap::new(),
functions: HashMap::new(),
};
let lines: Vec<&str> = source.lines().collect();
let mut i = 0;
while i < lines.len() {
let line = lines[i].trim();
// Parse contract declaration
if line.starts_with("contract ") || line.starts_with("contract\t") {
ctx.contract_name = Self::parse_contract_name(line)?;
}
// Parse specification comments
if line.starts_with("/// @") || line.starts_with("// @") {
let spec = Self::parse_spec_comment(line, &lines, &mut i)?;
ctx.specs.push(spec);
}
// Parse state variable
if Self::is_state_var_line(line) {
let (name, var_type) = Self::parse_state_var(line)?;
ctx.state_vars.insert(name, var_type);
}
// Parse function
if line.starts_with("function ") {
let func = Self::parse_function_sig(line)?;
ctx.functions.insert(func.name.clone(), func);
}
i += 1;
}
Ok(ctx)
}
/// Parses contract name from declaration line.
fn parse_contract_name(line: &str) -> VerifierResult<String> {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
Ok(parts[1].trim_end_matches('{').to_string())
} else {
Err(VerifierError::ParseError {
line: 0,
message: "Invalid contract declaration".to_string(),
})
}
}
/// Parses a specification comment.
fn parse_spec_comment(
line: &str,
_lines: &[&str],
_i: &mut usize,
) -> VerifierResult<Specification> {
let content = line
.trim_start_matches("///")
.trim_start_matches("//")
.trim();
if content.starts_with("@invariant") {
Self::parse_invariant(content)
} else if content.starts_with("@property") {
Self::parse_property(content)
} else if content.starts_with("@requires") || content.starts_with("@ensures") {
Self::parse_annotation(content)
} else {
Err(VerifierError::ParseError {
line: 0,
message: format!("Unknown specification: {}", content),
})
}
}
/// Parses an invariant specification.
fn parse_invariant(content: &str) -> VerifierResult<Specification> {
let parts: Vec<&str> = content.splitn(2, ' ').collect();
let expr_str = if parts.len() > 1 { parts[1] } else { "true" };
// Extract name if provided: @invariant name: expr
let (name, expr_str) = if let Some(idx) = expr_str.find(':') {
let name = expr_str[..idx].trim().to_string();
let expr = expr_str[idx + 1..].trim();
(name, expr)
} else {
("unnamed".to_string(), expr_str)
};
let expr = Self::parse_expression(expr_str)?;
Ok(Specification::Invariant(Invariant {
name,
expr,
description: None,
}))
}
/// Parses a property specification.
fn parse_property(content: &str) -> VerifierResult<Specification> {
let parts: Vec<&str> = content.splitn(2, ' ').collect();
let expr_str = if parts.len() > 1 { parts[1] } else { "true" };
let (name, kind, expr_str) = Self::parse_property_header(expr_str)?;
let expr = Self::parse_expression(expr_str)?;
Ok(Specification::Property(Property {
name,
kind,
expr,
description: None,
}))
}
/// Parses property header to extract name and kind.
fn parse_property_header(s: &str) -> VerifierResult<(String, PropertyKind, &str)> {
// Format: [safety|liveness] name: expr
let kind = if s.starts_with("safety") {
PropertyKind::Safety
} else if s.starts_with("liveness") {
PropertyKind::Liveness
} else if s.starts_with("reachability") {
PropertyKind::Reachability
} else {
PropertyKind::Custom
};
// Skip kind keyword
let s = s
.trim_start_matches("safety")
.trim_start_matches("liveness")
.trim_start_matches("reachability")
.trim();
// Extract name
if let Some(idx) = s.find(':') {
let name = s[..idx].trim().to_string();
let expr = s[idx + 1..].trim();
Ok((name, kind, expr))
} else {
Ok(("unnamed".to_string(), kind, s))
}
}
/// Parses a function annotation.
fn parse_annotation(content: &str) -> VerifierResult<Specification> {
let (kind, rest) = if content.starts_with("@requires") {
("requires", content.trim_start_matches("@requires").trim())
} else {
("ensures", content.trim_start_matches("@ensures").trim())
};
let expr = Self::parse_expression(rest)?;
let annotation = Annotation {
function: String::new(), // Will be filled in by context
requires: if kind == "requires" {
vec![expr.clone()]
} else {
vec![]
},
ensures: if kind == "ensures" {
vec![expr]
} else {
vec![]
},
modifies: vec![],
};
Ok(Specification::Annotation(annotation))
}
/// Parses an expression from string.
pub fn parse_expression(s: &str) -> VerifierResult<Expression> {
let s = s.trim();
// Handle parentheses
if s.starts_with('(') && s.ends_with(')') {
return Self::parse_expression(&s[1..s.len() - 1]);
}
// Handle quantifiers
if s.starts_with("forall") || s.starts_with("exists") {
return Self::parse_quantifier(s);
}
// Handle old()
if s.starts_with("old(") && s.ends_with(')') {
let inner = &s[4..s.len() - 1];
return Ok(Expression::Old(Box::new(Self::parse_expression(inner)?)));
}
// Handle binary operators (lowest precedence first)
for (op_str, op) in [
("==>", BinaryOperator::Implies),
("<==>", BinaryOperator::Iff),
("||", BinaryOperator::Or),
("&&", BinaryOperator::And),
("==", BinaryOperator::Eq),
("!=", BinaryOperator::Ne),
("<=", BinaryOperator::Le),
(">=", BinaryOperator::Ge),
("<", BinaryOperator::Lt),
(">", BinaryOperator::Gt),
("+", BinaryOperator::Add),
("-", BinaryOperator::Sub),
("*", BinaryOperator::Mul),
("/", BinaryOperator::Div),
("%", BinaryOperator::Mod),
] {
if let Some(idx) = Self::find_operator(s, op_str) {
let left = Self::parse_expression(&s[..idx])?;
let right = Self::parse_expression(&s[idx + op_str.len()..])?;
return Ok(Expression::BinaryOp {
left: Box::new(left),
op,
right: Box::new(right),
});
}
}
// Handle unary operators
if s.starts_with('!') {
let operand = Self::parse_expression(&s[1..])?;
return Ok(Expression::UnaryOp {
op: UnaryOperator::Not,
operand: Box::new(operand),
});
}
// Handle literals
if s == "true" {
return Ok(Expression::Literal(Literal::Bool(true)));
}
if s == "false" {
return Ok(Expression::Literal(Literal::Bool(false)));
}
if let Ok(n) = s.parse::<u128>() {
return Ok(Expression::Literal(Literal::Uint(n)));
}
// Handle special keywords
if s == "result" {
return Ok(Expression::Result);
}
// Handle member access
if let Some(idx) = s.rfind('.') {
let object = Self::parse_expression(&s[..idx])?;
let member = s[idx + 1..].to_string();
return Ok(Expression::MemberAccess {
object: Box::new(object),
member,
});
}
// Handle array index
if s.ends_with(']') {
if let Some(idx) = s.rfind('[') {
let array = Self::parse_expression(&s[..idx])?;
let index = Self::parse_expression(&s[idx + 1..s.len() - 1])?;
return Ok(Expression::Index {
array: Box::new(array),
index: Box::new(index),
});
}
}
// Handle function calls
if s.ends_with(')') {
if let Some(idx) = s.find('(') {
let function = s[..idx].to_string();
let args_str = &s[idx + 1..s.len() - 1];
let args = Self::parse_args(args_str)?;
return Ok(Expression::FunctionCall { function, args });
}
}
// Default: variable reference
Ok(Expression::Variable(s.to_string()))
}
/// Finds an operator in string, respecting parentheses.
fn find_operator(s: &str, op: &str) -> Option<usize> {
let mut depth = 0;
let chars: Vec<char> = s.chars().collect();
let op_chars: Vec<char> = op.chars().collect();
for i in 0..chars.len() {
match chars[i] {
'(' | '[' => depth += 1,
')' | ']' => depth -= 1,
_ => {}
}
if depth == 0 && i + op_chars.len() <= chars.len() {
let slice: String = chars[i..i + op_chars.len()].iter().collect();
if slice == op {
return Some(i);
}
}
}
None
}
/// Parses a quantifier expression.
fn parse_quantifier(s: &str) -> VerifierResult<Expression> {
let kind = if s.starts_with("forall") {
QuantifierKind::ForAll
} else {
QuantifierKind::Exists
};
// Format: forall x: uint256 :: expr
let rest = s
.trim_start_matches("forall")
.trim_start_matches("exists")
.trim();
// Find variable and type
let parts: Vec<&str> = rest.splitn(2, "::").collect();
if parts.len() != 2 {
return Err(VerifierError::ParseError {
line: 0,
message: "Invalid quantifier syntax".to_string(),
});
}
let var_part = parts[0].trim();
let body_str = parts[1].trim();
let (var, var_type) = Self::parse_typed_var(var_part)?;
let body = Self::parse_expression(body_str)?;
Ok(Expression::Quantifier {
kind,
var,
var_type,
body: Box::new(body),
})
}
/// Parses a typed variable declaration.
fn parse_typed_var(s: &str) -> VerifierResult<(String, VarType)> {
let parts: Vec<&str> = s.split(':').collect();
if parts.len() != 2 {
return Err(VerifierError::ParseError {
line: 0,
message: "Invalid typed variable".to_string(),
});
}
let var = parts[0].trim().to_string();
let type_str = parts[1].trim();
let var_type = Self::parse_type(type_str)?;
Ok((var, var_type))
}
/// Parses a type string.
pub fn parse_type(s: &str) -> VerifierResult<VarType> {
let s = s.trim();
if s.starts_with("uint") {
let bits: u16 = s[4..].parse().unwrap_or(256);
return Ok(VarType::Uint(bits));
}
if s.starts_with("int") {
let bits: u16 = s[3..].parse().unwrap_or(256);
return Ok(VarType::Int(bits));
}
if s == "bool" {
return Ok(VarType::Bool);
}
if s == "address" {
return Ok(VarType::Address);
}
if s.starts_with("bytes") && s.len() > 5 {
let n: u16 = s[5..].parse().unwrap_or(32);
return Ok(VarType::Bytes(n));
}
if s == "bytes" {
return Ok(VarType::DynBytes);
}
if s == "string" {
return Ok(VarType::String);
}
// Array type
if s.ends_with("[]") {
let inner = Self::parse_type(&s[..s.len() - 2])?;
return Ok(VarType::Array(Box::new(inner)));
}
// Mapping type
if s.starts_with("mapping(") && s.ends_with(')') {
let inner = &s[8..s.len() - 1];
if let Some(idx) = inner.find("=>") {
let key = Self::parse_type(&inner[..idx].trim())?;
let value = Self::parse_type(&inner[idx + 2..].trim())?;
return Ok(VarType::Mapping(Box::new(key), Box::new(value)));
}
}
Err(VerifierError::ParseError {
line: 0,
message: format!("Unknown type: {}", s),
})
}
/// Parses function arguments.
fn parse_args(s: &str) -> VerifierResult<Vec<Expression>> {
if s.trim().is_empty() {
return Ok(vec![]);
}
let mut args = Vec::new();
let mut current = String::new();
let mut depth = 0;
for c in s.chars() {
match c {
'(' | '[' => {
depth += 1;
current.push(c);
}
')' | ']' => {
depth -= 1;
current.push(c);
}
',' if depth == 0 => {
args.push(Self::parse_expression(current.trim())?);
current.clear();
}
_ => current.push(c),
}
}
if !current.trim().is_empty() {
args.push(Self::parse_expression(current.trim())?);
}
Ok(args)
}
/// Checks if a line declares a state variable.
fn is_state_var_line(line: &str) -> bool {
// Simple heuristic: type followed by visibility/name
let keywords = ["uint", "int", "bool", "address", "bytes", "string", "mapping"];
keywords.iter().any(|k| line.starts_with(k))
}
/// Parses a state variable declaration.
fn parse_state_var(line: &str) -> VerifierResult<(String, VarType)> {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 2 {
return Err(VerifierError::ParseError {
line: 0,
message: "Invalid state variable".to_string(),
});
}
let var_type = Self::parse_type(parts[0])?;
let name = parts
.last()
.unwrap()
.trim_end_matches(';')
.trim_end_matches('=')
.to_string();
Ok((name, var_type))
}
/// Parses a function signature.
fn parse_function_sig(line: &str) -> VerifierResult<FunctionSig> {
let line = line.trim_start_matches("function").trim();
// Extract function name
let name_end = line.find('(').unwrap_or(line.len());
let name = line[..name_end].trim().to_string();
// Extract parameters
let params_start = line.find('(').unwrap_or(0);
let params_end = line.find(')').unwrap_or(line.len());
let params_str = &line[params_start + 1..params_end];
let params = Self::parse_params(params_str)?;
// Extract return type
let returns = if let Some(idx) = line.find("returns") {
let ret_start = line[idx..].find('(').map(|i| idx + i + 1);
let ret_end = line[idx..].find(')').map(|i| idx + i);
if let (Some(start), Some(end)) = (ret_start, ret_end) {
Some(Self::parse_type(&line[start..end])?)
} else {
None
}
} else {
None
};
// Extract visibility and mutability
let visibility = if line.contains("external") {
Visibility::External
} else if line.contains("internal") {
Visibility::Internal
} else if line.contains("private") {
Visibility::Private
} else {
Visibility::Public
};
let mutability = if line.contains("pure") {
Mutability::Pure
} else if line.contains("view") {
Mutability::View
} else if line.contains("payable") {
Mutability::Payable
} else {
Mutability::Nonpayable
};
Ok(FunctionSig {
name,
params,
returns,
visibility,
mutability,
preconditions: vec![],
postconditions: vec![],
})
}
/// Parses function parameters.
fn parse_params(s: &str) -> VerifierResult<Vec<(String, VarType)>> {
if s.trim().is_empty() {
return Ok(vec![]);
}
let mut params = Vec::new();
for param in s.split(',') {
let parts: Vec<&str> = param.split_whitespace().collect();
if parts.len() >= 2 {
let var_type = Self::parse_type(parts[0])?;
let name = parts.last().unwrap().to_string();
params.push((name, var_type));
}
}
Ok(params)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_type() {
assert_eq!(SpecParser::parse_type("uint256").unwrap(), VarType::Uint(256));
assert_eq!(SpecParser::parse_type("int128").unwrap(), VarType::Int(128));
assert_eq!(SpecParser::parse_type("bool").unwrap(), VarType::Bool);
assert_eq!(SpecParser::parse_type("address").unwrap(), VarType::Address);
}
#[test]
fn test_parse_expression() {
let expr = SpecParser::parse_expression("x + 1").unwrap();
if let Expression::BinaryOp { op, .. } = expr {
assert_eq!(op, BinaryOperator::Add);
} else {
panic!("Expected BinaryOp");
}
}
#[test]
fn test_parse_comparison() {
let expr = SpecParser::parse_expression("balance >= 0").unwrap();
if let Expression::BinaryOp { op, .. } = expr {
assert_eq!(op, BinaryOperator::Ge);
} else {
panic!("Expected BinaryOp");
}
}
#[test]
fn test_parse_invariant() {
let spec = SpecParser::parse_invariant("@invariant positive: balance >= 0").unwrap();
if let Specification::Invariant(inv) = spec {
assert_eq!(inv.name, "positive");
} else {
panic!("Expected Invariant");
}
}
}