use crate::FuzzAccount;
use rand::Rng;
#[derive(Debug, Clone)]
pub struct MutationConfig {
pub flip_data: f64,
pub replace_owner: f64,
pub zero_lamports: f64,
pub clear_data: f64,
pub swap_signer: f64,
pub mutate_seeds: f64,
}
impl Default for MutationConfig {
fn default() -> Self {
Self {
flip_data: 0.40,
replace_owner: 0.10,
zero_lamports: 0.10,
clear_data: 0.05,
swap_signer: 0.15,
mutate_seeds: 0.10,
}
}
}
pub fn mutate_accounts(
accounts: &mut [FuzzAccount],
config: &MutationConfig,
rng: &mut impl Rng,
) -> usize {
let mut mutation_count = 0;
for account in accounts.iter_mut() {
if !account.is_writable {
continue;
}
if rng.gen_bool(config.flip_data) {
mutate_flip_data_bits(account, rng);
mutation_count += 1;
}
if rng.gen_bool(config.replace_owner) {
mutate_owner(account, rng);
mutation_count += 1;
}
if rng.gen_bool(config.zero_lamports) {
mutate_zero_lamports(account);
mutation_count += 1;
}
if rng.gen_bool(config.clear_data) {
mutate_clear_data(account);
mutation_count += 1;
}
if rng.gen_bool(config.swap_signer) {
mutate_swap_signer(account);
mutation_count += 1;
}
if rng.gen_bool(config.mutate_seeds) {
mutate_seeds(account, rng);
mutation_count += 1;
}
}
mutation_count
}
fn mutate_owner(account: &mut FuzzAccount, rng: &mut impl Rng) {
let roll = rng.gen_range(0.0..1.0);
if roll < 0.40 {
account.owner = crate::bs58_encode(&(0..32).map(|_| rng.gen()).collect::<Vec<u8>>());
} else if roll < 0.75 {
account.owner = "11111111111111111111111111111111".to_string();
} else {
account.owner = String::new();
}
}
fn mutate_zero_lamports(account: &mut FuzzAccount) {
account.lamports = 0;
account.rent_epoch = 0;
}
fn mutate_clear_data(account: &mut FuzzAccount) {
account.data.clear();
}
fn mutate_flip_data_bits(account: &mut FuzzAccount, rng: &mut impl Rng) {
if account.data.is_empty() {
return;
}
let bit_count = rng.gen_range(1..=usize::min(8, account.data.len() * 8));
for _ in 0..bit_count {
let byte_idx = rng.gen_range(0..account.data.len());
let bit_idx = rng.gen_range(0..8);
account.data[byte_idx] ^= 1 << bit_idx;
}
}
fn mutate_swap_signer(account: &mut FuzzAccount) {
account.is_signer = !account.is_signer;
}
fn mutate_seeds(account: &mut FuzzAccount, rng: &mut impl Rng) {
if let Some(ref mut seeds) = account.seeds {
if seeds.is_empty() {
return;
}
if rng.gen_bool(0.5) {
let idx = rng.gen_range(0..seeds.len());
for byte in seeds[idx].iter_mut() {
*byte ^= rng.gen::<u8>();
}
} else {
seeds.remove(rng.gen_range(0..seeds.len()));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::thread_rng;
#[test]
fn test_mutate_owner_changes_owner() {
let mut account = FuzzAccount::default();
let original = account.owner.clone();
for _ in 0..10 {
mutate_owner(&mut account, &mut thread_rng());
if account.owner != original {
return;
}
account.owner = original.clone(); }
panic!("mutate_owner failed to change owner after 10 attempts");
}
#[test]
fn test_zero_lamports() {
let mut account = FuzzAccount::default();
account.lamports = 500;
mutate_zero_lamports(&mut account);
assert_eq!(account.lamports, 0);
}
#[test]
fn test_clear_data() {
let mut account = FuzzAccount::default();
account.data = vec![1, 2, 3, 4];
mutate_clear_data(&mut account);
assert!(account.data.is_empty());
}
#[test]
fn test_mutate_accounts_returns_count() {
let mut accounts = vec![FuzzAccount::default(); 10];
let config = MutationConfig {
flip_data: 1.0,
replace_owner: 0.0,
zero_lamports: 0.0,
clear_data: 0.0,
swap_signer: 0.0,
mutate_seeds: 0.0,
};
let count = mutate_accounts(&mut accounts, &config, &mut thread_rng());
assert!(count > 0);
}
}