use crate::CodeFuzzer;
pub struct DVNSinglePointFuzzer {
_fuzzer: CodeFuzzer,
}
impl DVNSinglePointFuzzer {
pub fn new(seed: Option<u64>) -> Self {
Self {
_fuzzer: CodeFuzzer::new(seed),
}
}
fn gen_vulnerable_pattern(&self) -> String {
r#"contract DVNConfig {
address[] public dvns;
function setDVNs(address[] calldata _dvns) public {
// Vulnerable: No minimum count validation
dvns = _dvns;
}
function sendMessage(bytes calldata message) public {
// Single DVN is insufficient
require(dvns.length > 0, "DVN needed");
(bool success,) = dvns[0].call(message);
require(success, "Call failed");
}
}"#
.to_string()
}
fn gen_safe_pattern(&self) -> String {
r#"contract DVNConfig {
address[] public dvns;
uint256 public constant MIN_DVNS = 3;
function setDVNs(address[] calldata _dvns) public {
// Safe: Enforces minimum DVN count
require(_dvns.length >= MIN_DVNS, "Insufficient DVNs");
dvns = _dvns;
}
function sendMessage(bytes calldata message) public {
// Requires quorum
require(dvns.length >= MIN_DVNS, "Not ready");
uint256 successes = 0;
for (uint i = 0; i < dvns.length; i++) {
(bool success,) = dvns[i].call(message);
if (success) successes++;
}
require(successes >= MIN_DVNS, "Quorum failed");
}
}"#
.to_string()
}
pub fn fuzz(&mut self, iterations: usize) -> FuzzResult {
let mut detections = 0;
let mut false_positives = 0;
let mut false_negatives = 0;
for i in 0..iterations {
let vulnerable = i % 2 == 0;
let pattern = if vulnerable {
self.gen_vulnerable_pattern()
} else {
self.gen_safe_pattern()
};
let detected = pattern.contains("require(dvns.length > 0")
&& !pattern.contains("MIN_DVNS")
&& !pattern.contains("require(_dvns.length >=");
if vulnerable && detected {
detections += 1;
} else if vulnerable && !detected {
false_negatives += 1;
} else if !vulnerable && detected {
false_positives += 1;
}
}
FuzzResult {
true_positives: detections,
false_positives,
false_negatives,
total: iterations,
}
}
}
#[derive(Debug, Clone)]
pub struct FuzzResult {
pub true_positives: usize,
pub false_positives: usize,
pub false_negatives: usize,
pub total: usize,
}
impl FuzzResult {
pub fn precision(&self) -> f64 {
let total = self.true_positives + self.false_positives;
if total == 0 {
0.0
} else {
self.true_positives as f64 / total as f64
}
}
pub fn recall(&self) -> f64 {
let total = self.true_positives + self.false_negatives;
if total == 0 {
0.0
} else {
self.true_positives as f64 / total as f64
}
}
pub fn f1_score(&self) -> f64 {
let p = self.precision();
let r = self.recall();
if p + r == 0.0 {
0.0
} else {
2.0 * p * r / (p + r)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fuzzer_detects_single_point() {
let mut fuzzer = DVNSinglePointFuzzer::new(Some(42));
let result = fuzzer.fuzz(100);
assert!(result.true_positives > 0);
}
#[test]
fn fuzz_metrics_valid() {
let result = FuzzResult {
true_positives: 45,
false_positives: 3,
false_negatives: 5,
total: 100,
};
assert!(result.precision() > 0.9);
assert!(result.recall() > 0.8);
}
}