Files
237-combo/src/strat/core/fuzzer.ts
2026-02-09 21:51:30 -08:00

145 lines
4.6 KiB
TypeScript

import type { Scenario, ScenarioStep } from '../types.js';
import { ScenarioRunner } from './scenario-runner.js';
import type { ForkOrchestrator } from './fork-orchestrator.js';
import type { ProtocolAdapter, Network } from '../types.js';
import type { RunReport } from '../types.js';
/**
* Fuzzer
* Runs scenarios with parameterized inputs
*/
export class Fuzzer {
constructor(
private fork: ForkOrchestrator,
private adapters: Map<string, ProtocolAdapter>,
private network: Network
) {}
/**
* Fuzz test a scenario with parameterized inputs
*/
async fuzz(
scenario: Scenario,
options: {
iterations: number;
seed?: number;
parameterRanges?: Record<string, { min: number; max: number; step?: number }>;
}
): Promise<RunReport[]> {
const results: RunReport[] = [];
const runner = new ScenarioRunner(this.fork, this.adapters, this.network);
// Simple seeded RNG
let rngSeed = options.seed || Math.floor(Math.random() * 1000000);
const rng = () => {
rngSeed = (rngSeed * 9301 + 49297) % 233280;
return rngSeed / 233280;
};
console.log(`Fuzzing scenario with ${options.iterations} iterations (seed: ${options.seed || 'random'})`);
for (let i = 0; i < options.iterations; i++) {
console.log(`\n=== Iteration ${i + 1}/${options.iterations} ===`);
// Create a mutated scenario
const mutatedScenario = this.mutateScenario(scenario, options.parameterRanges || {}, rng);
try {
// Create a snapshot before running
const snapshotId = await this.fork.snapshot(`fuzz_${i}`);
// Run the scenario
const report = await runner.run(mutatedScenario);
results.push(report);
// Revert to snapshot for next iteration
await this.fork.revert(snapshotId);
console.log(` Result: ${report.passed ? 'PASSED' : 'FAILED'}`);
if (!report.passed) {
console.log(` Error: ${report.error}`);
}
} catch (error: any) {
console.error(` Error in iteration ${i + 1}: ${error.message}`);
// Continue with next iteration
}
}
// Summary
const passed = results.filter(r => r.passed).length;
const failed = results.filter(r => !r.passed).length;
console.log(`\n=== Fuzzing Summary ===`);
console.log(`Total iterations: ${options.iterations}`);
console.log(`Passed: ${passed}`);
console.log(`Failed: ${failed}`);
console.log(`Success rate: ${((passed / options.iterations) * 100).toFixed(2)}%`);
return results;
}
/**
* Mutate a scenario with random parameter values
*/
private mutateScenario(
scenario: Scenario,
parameterRanges: Record<string, { min: number; max: number; step?: number }>,
rng: () => number
): Scenario {
const mutated = JSON.parse(JSON.stringify(scenario)) as Scenario;
// Mutate step arguments
for (const step of mutated.steps) {
this.mutateStep(step, parameterRanges, rng);
}
return mutated;
}
/**
* Mutate a single step
*/
private mutateStep(
step: ScenarioStep,
parameterRanges: Record<string, { min: number; max: number; step?: number }>,
rng: () => number
): void {
// Mutate amount parameters
if (step.args.amount && typeof step.args.amount === 'string') {
const amountNum = parseFloat(step.args.amount);
if (!isNaN(amountNum)) {
// Apply random variation (±20%)
const variation = (rng() - 0.5) * 0.4; // -0.2 to 0.2
const newAmount = amountNum * (1 + variation);
step.args.amount = newAmount.toFixed(6);
}
}
// Mutate percentage-based parameters
if (step.args.pctDelta !== undefined && typeof step.args.pctDelta === 'number') {
if (parameterRanges.pctDelta) {
const { min, max, step: stepSize } = parameterRanges.pctDelta;
const range = max - min;
const steps = stepSize ? Math.floor(range / stepSize) : 100;
const randomStep = Math.floor(rng() * steps);
step.args.pctDelta = min + (stepSize || (range / steps)) * randomStep;
} else {
// Default: vary between -20% and 20%
step.args.pctDelta = (rng() - 0.5) * 40;
}
}
// Mutate fee parameters (for Uniswap)
if (step.args.fee !== undefined && typeof step.args.fee === 'number') {
const fees = [100, 500, 3000, 10000]; // Common Uniswap fees
step.args.fee = fees[Math.floor(rng() * fees.length)];
}
// Mutate slippage
if (step.args.slippageBps !== undefined && typeof step.args.slippageBps === 'number') {
// Vary slippage between 10 and 100 bps (0.1% to 1%)
step.args.slippageBps = Math.floor(10 + rng() * 90);
}
}
}