use crate::CodeFuzzer;
pub struct SyntheticMintFuzzer {
_fuzzer: CodeFuzzer,
}
impl SyntheticMintFuzzer {
pub fn new(seed: Option<u64>) -> Self {
Self {
_fuzzer: CodeFuzzer::new(seed),
}
}
fn gen_vulnerable_pattern(&self) -> String {
r#"contract SyntheticToken {
mapping(address => uint256) collateral;
uint256 public totalMinted;
uint256 constant MINT_RATIO = 2; // 2:1 ratio required
function mint(address user, uint256 amount) public {
// Vulnerable: No collateral verification
totalMinted += amount;
balances[user] += amount;
}
function addCollateral(address user, uint256 amount) public {
collateral[user] += amount;
}
}"#
.to_string()
}
fn gen_safe_pattern(&self) -> String {
r#"contract SyntheticToken {
mapping(address => uint256) collateral;
uint256 public totalMinted;
uint256 constant MINT_RATIO = 2;
function mint(address user, uint256 amount) public {
// Safe: Validates collateral backing
uint256 requiredCollateral = amount / MINT_RATIO;
require(
collateral[user] >= requiredCollateral,
"Insufficient collateral"
);
totalMinted += amount;
balances[user] += amount;
// Verify conservation invariant
assert(totalMinted <= getTotalCollateral() / MINT_RATIO);
}
function addCollateral(address user, uint256 amount) public {
require(amount > 0, "Invalid amount");
collateral[user] += amount;
}
}"#
.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 has_mint = pattern.contains("function mint");
let has_collateral_check =
pattern.contains("require(") && pattern.contains("collateral");
let detected = has_mint && !has_collateral_check;
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_unbacked_minting() {
let mut fuzzer = SyntheticMintFuzzer::new(Some(42));
let result = fuzzer.fuzz(100);
assert!(result.true_positives > 0);
}
#[test]
fn fuzz_prevents_false_positives() {
let mut fuzzer = SyntheticMintFuzzer::new(Some(99));
let result = fuzzer.fuzz(100);
assert!(result.false_positives < 10);
}
}