1use tracing::debug;
32
33use rand::Rng;
34use serde::{Deserialize, Serialize};
35use traverse_solidity::ast::*;
36use traverse_solidity::{
37 format_value_for_expression, parse_expression, write_expression_to_string, SolidityInterpreter,
38 Value,
39};
40use std::collections::HashMap;
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct InvariantBreakerResult {
44 pub success: bool,
45 pub error: Option<String>,
46 pub entries: Vec<InvariantBreakerEntry>,
47 pub original_expression: String,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct InvariantBreakerEntry {
52 pub variables: HashMap<String, InvariantBreakerValue>,
53 pub concrete_expression: String,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub enum InvariantBreakerValue {
58 Bool(bool),
59 UInt(u64),
60 Int(i64),
61 String(String),
62 Address(String),
63 Bytes(Vec<u8>),
64}
65
66impl From<Value> for InvariantBreakerValue {
67 fn from(value: Value) -> Self {
68 match value {
69 Value::Bool(b) => InvariantBreakerValue::Bool(b),
70 Value::UInt(n) => InvariantBreakerValue::UInt(n),
71 Value::Int(n) => InvariantBreakerValue::Int(n),
72 Value::String(s) => InvariantBreakerValue::String(s),
73 Value::Address(addr) => InvariantBreakerValue::Address(addr),
74 Value::Bytes(b) => InvariantBreakerValue::Bytes(b),
75 Value::Null => InvariantBreakerValue::Bool(false), }
77 }
78}
79
80impl From<InvariantBreakerValue> for Value {
81 fn from(value: InvariantBreakerValue) -> Self {
82 match value {
83 InvariantBreakerValue::Bool(b) => Value::Bool(b),
84 InvariantBreakerValue::UInt(n) => Value::UInt(n),
85 InvariantBreakerValue::Int(n) => Value::Int(n),
86 InvariantBreakerValue::String(s) => Value::String(s),
87 InvariantBreakerValue::Address(addr) => Value::Address(addr),
88 InvariantBreakerValue::Bytes(b) => Value::Bytes(b),
89 }
90 }
91}
92
93impl std::fmt::Display for InvariantBreakerValue {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 match self {
96 InvariantBreakerValue::Bool(b) => write!(f, "{}", b),
97 InvariantBreakerValue::UInt(n) => write!(f, "{}", n),
98 InvariantBreakerValue::Int(n) => write!(f, "{}", n),
99 InvariantBreakerValue::String(s) => write!(f, "\"{}\"", s),
100 InvariantBreakerValue::Address(addr) => write!(f, "{}", addr),
101 InvariantBreakerValue::Bytes(b) => write!(f, "0x{}", hex::encode(b)),
102 }
103 }
104}
105
106#[derive(Debug, Clone)]
107pub struct InvariantBreakerConfig {
108 pub max_attempts: usize,
109 pub max_results: usize,
110 pub seed: Option<u64>,
111}
112
113impl Default for InvariantBreakerConfig {
114 fn default() -> Self {
115 Self {
116 max_attempts: 1000,
117 max_results: 10,
118 seed: None,
119 }
120 }
121}
122
123pub async fn break_invariant(
124 expression: &str,
125) -> Result<InvariantBreakerResult, Box<dyn std::error::Error + Send + Sync>> {
126 break_invariant_with_config(expression, InvariantBreakerConfig::default()).await
127}
128
129pub async fn break_invariant_with_config(
130 expression: &str,
131 config: InvariantBreakerConfig,
132) -> Result<InvariantBreakerResult, Box<dyn std::error::Error + Send + Sync>> {
133 let parsed_expr = match parse_expression(expression) {
134 Ok(expr) => expr,
135 Err(e) => {
136 return Ok(InvariantBreakerResult {
137 success: false,
138 error: Some(format!("Failed to parse expression: {}", e)),
139 entries: vec![],
140 original_expression: expression.to_string(),
141 });
142 }
143 };
144
145 let variables = extract_variables(&parsed_expr);
146
147 if variables.is_empty() {
148 let interpreter = SolidityInterpreter::new();
149 match interpreter.evaluate_predicate(&parsed_expr) {
150 Ok(true) => {
151 return Ok(InvariantBreakerResult {
152 success: false,
153 error: Some("Expression is always true (no variables to modify)".to_string()),
154 entries: vec![],
155 original_expression: expression.to_string(),
156 });
157 }
158 Ok(false) => {
159 return Ok(InvariantBreakerResult {
160 success: true,
161 error: None,
162 entries: vec![InvariantBreakerEntry {
163 variables: HashMap::new(),
164 concrete_expression: expression.to_string(),
165 }],
166 original_expression: expression.to_string(),
167 });
168 }
169 Err(e) => {
170 return Ok(InvariantBreakerResult {
171 success: false,
172 error: Some(format!("Failed to evaluate expression: {}", e)),
173 entries: vec![],
174 original_expression: expression.to_string(),
175 });
176 }
177 }
178 }
179
180 let mut rng = if let Some(seed) = config.seed {
181 use rand::SeedableRng;
182 rand::rngs::StdRng::seed_from_u64(seed)
183 } else {
184 use rand::SeedableRng;
185 rand::rngs::StdRng::from_entropy()
186 };
187
188 let mut uint_candidates = std::collections::HashSet::new();
190 collect_comparison_vars_recursive(&parsed_expr, &variables, &mut uint_candidates);
191
192 debug!("[INV_BREAK DEBUG] Original Expression: {}", expression);
193 debug!("[INV_BREAK DEBUG] Extracted Variables: {:?}", variables);
194 debug!("[INV_BREAK DEBUG] Uint Candidates: {:?}", uint_candidates);
195
196 let mut entries = Vec::new();
197 let mut attempts = 0;
198
199 while attempts < config.max_attempts && entries.len() < config.max_results {
200 attempts += 1;
201
202 let mut variable_assignments = HashMap::new();
203 for var_name in &variables {
204 let value = if uint_candidates.contains(var_name) {
205 let choice = rng.gen_range(0..10); if choice == 0 {
208 Value::UInt(0)
209 } else if choice == 1 {
210 Value::UInt(1)
211 } else {
212 Value::UInt(rng.gen_range(2..5000))
213 } } else {
215 generate_random_value(&mut rng) };
217 variable_assignments.insert(var_name.clone(), value);
218 }
219
220 let mut interpreter = SolidityInterpreter::new();
221 for (name, value) in &variable_assignments {
222 interpreter
223 .context_mut()
224 .set_variable(name.clone(), value.clone());
225 }
226
227 debug!(
228 "[INV_BREAK DEBUG] Attempt {}: Variable Assignments: {:?}",
229 attempts,
230 variable_assignments
231 .iter()
232 .map(|(k, v)| (k, format!("{}", v)))
233 .collect::<HashMap<_, _>>()
234 );
235
236 match interpreter.evaluate_predicate(&parsed_expr) {
237 Ok(false) => {
238 debug!(
239 "[INV_BREAK DEBUG] Attempt {}: Found counterexample (predicate returned false)",
240 attempts
241 );
242 let concrete_expr =
243 substitute_variables_in_expression(&parsed_expr, &variable_assignments)?;
244 entries.push(InvariantBreakerEntry {
245 variables: variable_assignments
246 .into_iter()
247 .map(|(k, v)| (k, v.into()))
248 .collect(),
249 concrete_expression: concrete_expr,
250 });
251 }
252 Ok(true) => {
253 debug!(
254 "[INV_BREAK DEBUG] Attempt {}: Predicate returned true",
255 attempts
256 );
257 }
259 Err(e) => {
260 debug!(
261 "[INV_BREAK DEBUG] Attempt {}: Error evaluating predicate: {}. Skipping.",
262 attempts, e
263 );
264 continue;
266 }
267 }
268 }
269
270 if entries.is_empty() {
271 debug!(
272 "[INV_BREAK DEBUG] No counterexamples found after {} attempts for expression: '{}'",
273 attempts, expression
274 );
275 } else {
276 debug!(
277 "[INV_BREAK DEBUG] Found {} counterexamples for expression: '{}'",
278 entries.len(),
279 expression
280 );
281 }
282
283 Ok(InvariantBreakerResult {
284 success: !entries.is_empty(),
285 error: if entries.is_empty() {
286 Some(format!(
287 "No counterexamples found after {} attempts",
288 attempts
289 ))
290 } else {
291 None
292 },
293 entries,
294 original_expression: expression.to_string(),
295 })
296}
297
298fn extract_variables(expr: &Expression) -> Vec<String> {
299 let mut variables = Vec::new();
300 extract_variables_recursive(expr, &mut variables);
301 variables.sort();
302 variables.dedup();
303 variables
304}
305
306fn collect_comparison_vars_recursive(
307 expr: &Expression,
308 all_vars: &Vec<String>,
309 candidates: &mut std::collections::HashSet<String>,
310) {
311 match expr {
312 Expression::Binary(bin_expr) => {
313 if matches!(
315 bin_expr.operator,
316 BinaryOperator::GreaterThan
317 | BinaryOperator::GreaterThanOrEqual
318 | BinaryOperator::LessThan
319 | BinaryOperator::LessThanOrEqual
320 | BinaryOperator::Equal
321 | BinaryOperator::NotEqual
322 ) {
323 if let Expression::Identifier(ref var_name) = *bin_expr.left {
325 if all_vars.contains(var_name) {
326 if let Expression::Literal(Literal::Number(ref num_lit)) = *bin_expr.right {
327 if num_lit.value.parse::<u64>().is_ok()
328 || num_lit.value.parse::<i64>().is_ok_and(|n| n >= 0)
329 {
330 candidates.insert(var_name.clone());
331 }
332 }
333 }
334 }
335 if let Expression::Identifier(ref var_name) = *bin_expr.right {
337 if all_vars.contains(var_name) {
338 if let Expression::Literal(Literal::Number(ref num_lit)) = *bin_expr.left {
339 if num_lit.value.parse::<u64>().is_ok()
340 || num_lit.value.parse::<i64>().is_ok_and(|n| n >= 0)
341 {
342 candidates.insert(var_name.clone());
343 }
344 }
345 }
346 }
347 }
348
349 collect_comparison_vars_recursive(&bin_expr.left, all_vars, candidates);
351 collect_comparison_vars_recursive(&bin_expr.right, all_vars, candidates);
352 }
353 Expression::Unary(unary_expr) => {
354 collect_comparison_vars_recursive(&unary_expr.operand, all_vars, candidates);
355 }
356 Expression::FunctionCall(call_expr) => {
357 for arg in &call_expr.arguments {
360 collect_comparison_vars_recursive(arg, all_vars, candidates);
361 }
362 }
363 Expression::Conditional(cond_expr) => {
364 collect_comparison_vars_recursive(&cond_expr.condition, all_vars, candidates);
365 collect_comparison_vars_recursive(&cond_expr.true_expr, all_vars, candidates);
366 collect_comparison_vars_recursive(&cond_expr.false_expr, all_vars, candidates);
367 }
368 Expression::Assignment(assign_expr) => {
369 collect_comparison_vars_recursive(&assign_expr.left, all_vars, candidates);
371 collect_comparison_vars_recursive(&assign_expr.right, all_vars, candidates);
372 }
373 Expression::Tuple(tuple_expr) => {
374 for element in &tuple_expr.elements {
375 if let Some(expr_element) = element {
376 collect_comparison_vars_recursive(expr_element, all_vars, candidates);
377 }
378 }
379 }
380 Expression::Array(array_expr) => {
381 for element in &array_expr.elements {
382 collect_comparison_vars_recursive(element, all_vars, candidates);
383 }
384 }
385 Expression::TypeConversion(conv_expr) => {
386 collect_comparison_vars_recursive(&conv_expr.expression, all_vars, candidates);
387 }
388 _ => {}
390 }
391}
392
393fn extract_variables_recursive(expr: &Expression, variables: &mut Vec<String>) {
394 match expr {
395 Expression::Identifier(name) => {
396 if !is_builtin_identifier(name) {
397 variables.push(name.clone());
398 }
399 }
400 Expression::Binary(bin_expr) => {
401 extract_variables_recursive(&bin_expr.left, variables);
402 extract_variables_recursive(&bin_expr.right, variables);
403 }
404 Expression::Unary(unary_expr) => {
405 extract_variables_recursive(&unary_expr.operand, variables);
406 }
407 Expression::FunctionCall(call_expr) => {
408 extract_variables_recursive(&call_expr.function, variables);
409 for arg in &call_expr.arguments {
410 extract_variables_recursive(arg, variables);
411 }
412 }
413 Expression::MemberAccess(member_expr) => {
414 extract_variables_recursive(&member_expr.object, variables);
415 }
416 Expression::IndexAccess(index_expr) => {
417 extract_variables_recursive(&index_expr.object, variables);
418 if let Some(index) = &index_expr.index {
419 extract_variables_recursive(index, variables);
420 }
421 }
422 Expression::Conditional(cond_expr) => {
423 extract_variables_recursive(&cond_expr.condition, variables);
424 extract_variables_recursive(&cond_expr.true_expr, variables);
425 extract_variables_recursive(&cond_expr.false_expr, variables);
426 }
427 Expression::Assignment(assign_expr) => {
428 extract_variables_recursive(&assign_expr.left, variables);
429 extract_variables_recursive(&assign_expr.right, variables);
430 }
431 Expression::Tuple(tuple_expr) => {
432 for element in &tuple_expr.elements {
433 if let Some(expr) = element {
434 extract_variables_recursive(expr, variables);
435 }
436 }
437 }
438 Expression::Array(array_expr) => {
439 for element in &array_expr.elements {
440 extract_variables_recursive(element, variables);
441 }
442 }
443 Expression::TypeConversion(conv_expr) => {
444 extract_variables_recursive(&conv_expr.expression, variables);
445 }
446 Expression::New(_new_expr) => {
447 }
449 Expression::Literal(_) => {
450 }
452 }
453}
454
455fn is_builtin_identifier(name: &str) -> bool {
456 matches!(
457 name,
458 "true"
459 | "false"
460 | "msg"
461 | "block"
462 | "tx"
463 | "now"
464 | "keccak256"
465 | "sha256"
466 | "ripemd160"
467 | "ecrecover"
468 | "this"
469 | "super"
470 | "selfdestruct"
471 | "revert"
472 | "require"
473 | "assert"
474 | "address"
476 | "bool"
477 | "string"
478 | "bytes"
479 | "byte"
480 | "int"
481 | "uint"
482 | "bytes1" | "bytes2" | "bytes3" | "bytes4" | "bytes5" | "bytes6" | "bytes7" | "bytes8"
484 | "bytes9" | "bytes10" | "bytes11" | "bytes12" | "bytes13" | "bytes14" | "bytes15" | "bytes16"
485 | "bytes17" | "bytes18" | "bytes19" | "bytes20" | "bytes21" | "bytes22" | "bytes23" | "bytes24"
486 | "bytes25" | "bytes26" | "bytes27" | "bytes28" | "bytes29" | "bytes30" | "bytes31" | "bytes32"
487 | "uint8" | "uint16" | "uint32" | "uint64" | "uint128" | "uint256"
490 | "int8" | "int16" | "int32" | "int64" | "int128" | "int256"
491 | "abi" | "type" )
495}
496
497fn generate_random_value(rng: &mut impl Rng) -> Value {
498 match rng.gen_range(0..6) {
499 0 => Value::Bool(rng.gen()),
500 1 => Value::UInt(rng.gen_range(0..5000)), 2 => Value::Int(rng.gen_range(-500..500)),
502 3 => {
503 let strings = ["", "hello", "world", "test", "value"];
504 Value::String(strings[rng.gen_range(0..strings.len())].to_string())
505 }
506 4 => {
507 let addresses = [
508 "0x0000000000000000000000000000000000000000",
509 "0x1234567890123456789012345678901234567890",
510 "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef",
511 ];
512 Value::Address(addresses[rng.gen_range(0..addresses.len())].to_string())
513 }
514 5 => {
515 let byte_arrays = [vec![], vec![0x12, 0x34], vec![0xff, 0x00, 0xaa, 0xbb]];
516 Value::Bytes(byte_arrays[rng.gen_range(0..byte_arrays.len())].clone())
517 }
518 _ => Value::Bool(false),
519 }
520}
521
522fn substitute_variables_in_expression(
523 expr: &Expression,
524 assignments: &HashMap<String, Value>,
525) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
526 Ok(substitute_expression_recursive(expr, assignments))
527}
528
529fn substitute_expression_recursive(
530 expr: &Expression,
531 assignments: &HashMap<String, Value>,
532) -> String {
533 match expr {
534 Expression::Identifier(name) => {
535 if let Some(value) = assignments.get(name) {
536 format_value_for_expression(value)
537 } else {
538 name.clone()
539 }
540 }
541 Expression::Binary(bin_expr) => {
542 let left = substitute_expression_recursive(&bin_expr.left, assignments);
543 let right = substitute_expression_recursive(&bin_expr.right, assignments);
544 let op = bin_expr.operator.to_string();
545 format!("({} {} {})", left, op, right)
546 }
547 Expression::Unary(unary_expr) => {
548 let operand = substitute_expression_recursive(&unary_expr.operand, assignments);
549 let op = unary_expr.operator.to_string();
550 if unary_expr.is_prefix {
551 format!("({}{})", op, operand)
552 } else {
553 format!("({}{})", operand, op)
554 }
555 }
556 _ => {
557 write_expression_to_string(expr)
559 }
560 }
561}
562
563#[cfg(test)]
564mod tests {
565 use super::*;
566 use tokio;
567
568 #[tokio::test]
569 async fn test_break_invariant_newvalue_gt_zero() {
570 let expression = "_newValue > 0";
571 let config = InvariantBreakerConfig {
572 max_attempts: 250, max_results: 1,
574 seed: None, };
576
577 let result = break_invariant_with_config(expression, config)
578 .await
579 .unwrap();
580
581 assert!(
582 result.success,
583 "Invariant breaker should succeed for '{}'. Error: {:?}",
584 expression, result.error
585 );
586 assert!(
587 !result.entries.is_empty(),
588 "Should find at least one counterexample for '{}'",
589 expression
590 );
591
592 let entry = &result.entries[0];
593 assert!(
594 entry.variables.contains_key("_newValue"),
595 "Counterexample should contain '_newValue'. Variables: {:?}",
596 entry.variables
597 );
598
599 let new_value_opt = entry.variables.get("_newValue");
600 assert!(new_value_opt.is_some(), "_newValue not found in variables");
601
602 match new_value_opt.unwrap() {
603 InvariantBreakerValue::UInt(val) => {
604 assert_eq!(
605 *val, 0,
606 "For '_newValue > 0', the counterexample _newValue should be 0. Got {}",
607 val
608 );
609 assert!(
610 entry.concrete_expression.contains("0 > 0")
611 || entry.concrete_expression.contains("0>0"),
612 "Concrete expression '{}' did not match expected pattern for _newValue = 0.",
613 entry.concrete_expression
614 );
615 }
616 other_type => {
617 panic!(
618 "'_newValue' should be a UInt, but got {:?}. Variables: {:?}",
619 other_type, entry.variables
620 );
621 }
622 }
623 }
624
625 #[tokio::test]
626 async fn test_break_invariant_simple_expression() {
627 let result = break_invariant("x > 10").await.unwrap();
628 assert!(result.success);
629 assert!(!result.entries.is_empty());
630
631 let has_counterexample = result.entries.iter().any(|entry| {
633 if let Some(InvariantBreakerValue::UInt(x)) = entry.variables.get("x") {
634 *x <= 10
635 } else if let Some(InvariantBreakerValue::Int(x)) = entry.variables.get("x") {
636 *x <= 10
637 } else {
638 false
639 }
640 });
641 assert!(has_counterexample);
642 }
643
644 #[tokio::test]
645 async fn test_break_invariant_boolean_expression() {
646 let result = break_invariant("isActive && hasPermission").await.unwrap();
647 assert!(result.success);
648 assert!(!result.entries.is_empty());
649
650 let has_counterexample = result.entries.iter().any(|entry| {
652 let is_active = entry.variables.get("isActive");
653 let has_permission = entry.variables.get("hasPermission");
654
655 matches!(is_active, Some(InvariantBreakerValue::Bool(false)))
656 || matches!(has_permission, Some(InvariantBreakerValue::Bool(false)))
657 });
658 assert!(has_counterexample);
659 }
660
661 #[tokio::test]
662 async fn test_break_invariant_always_true_expression() {
663 let result = break_invariant("true").await.unwrap();
664 assert!(!result.success);
665 assert!(result.error.is_some());
666 }
667
668 #[tokio::test]
669 async fn test_break_invariant_always_false_expression() {
670 let result = break_invariant("false").await.unwrap();
671 assert!(result.success);
672 assert_eq!(result.entries.len(), 1);
673 assert!(result.entries[0].variables.is_empty());
674 }
675
676 #[tokio::test]
677 async fn test_extract_variables() {
678 let expr = Expression::Binary(BinaryExpression {
680 left: Box::new(Expression::Identifier("x".to_string())),
681 operator: BinaryOperator::GreaterThan,
682 right: Box::new(Expression::Identifier("y".to_string())),
683 });
684
685 let variables = extract_variables(&expr);
686 assert_eq!(variables, vec!["x", "y"]);
687 }
688
689 #[tokio::test]
690 async fn test_substitute_variables() {
691 let expr = Expression::Binary(BinaryExpression {
692 left: Box::new(Expression::Identifier("x".to_string())),
693 operator: BinaryOperator::GreaterThan,
694 right: Box::new(Expression::Identifier("y".to_string())),
695 });
696
697 let mut assignments = HashMap::new();
698 assignments.insert("x".to_string(), Value::UInt(15));
699 assignments.insert("y".to_string(), Value::UInt(10));
700
701 let result = substitute_variables_in_expression(&expr, &assignments).unwrap();
702 assert_eq!(result, "(15 > 10)");
703 }
704}