Skip to main content

foundry_evm/executors/
corpus_io.rs

1//! Shared helpers for reading on-disk corpus directories.
2
3use eyre::{Result, eyre};
4use foundry_evm_fuzz::BasicTxDetails;
5use std::path::{Path, PathBuf};
6use uuid::Uuid;
7
8const WORKER_DIR_PREFIX: &str = "worker";
9const CORPUS_SUBDIR: &str = "corpus";
10
11/// Returns every `worker*/corpus/` under `root`, or `[root]` if none exist.
12pub fn canonical_replay_dirs(root: &Path) -> Vec<PathBuf> {
13    let mut dirs: Vec<PathBuf> = std::fs::read_dir(root)
14        .into_iter()
15        .flatten()
16        .flatten()
17        .filter_map(|e| {
18            let p = e.path();
19            let name = p.file_name()?.to_str()?;
20            (p.is_dir() && name.starts_with(WORKER_DIR_PREFIX))
21                .then(|| p.join(CORPUS_SUBDIR))
22                .filter(|d| d.is_dir())
23        })
24        .collect();
25    dirs.sort();
26    if dirs.is_empty() {
27        dirs.push(root.to_path_buf());
28    }
29    dirs
30}
31
32/// A single corpus file on disk.
33pub struct CorpusDirEntry {
34    pub path: PathBuf,
35    pub uuid: Uuid,
36    pub timestamp: u64,
37}
38
39impl CorpusDirEntry {
40    pub fn name(&self) -> &str {
41        self.path.file_name().unwrap().to_str().unwrap()
42    }
43
44    pub fn read_tx_seq(&self) -> foundry_common::fs::Result<Vec<BasicTxDetails>> {
45        if self.path.extension() == Some("gz".as_ref()) {
46            foundry_common::fs::read_json_gzip_file(&self.path)
47        } else {
48            foundry_common::fs::read_json_file(&self.path)
49        }
50    }
51}
52
53/// Iterate corpus files in `path`, ignoring entries with unparsable names.
54pub fn read_corpus_dir(path: &Path) -> impl Iterator<Item = CorpusDirEntry> {
55    let dir = match std::fs::read_dir(path) {
56        Ok(dir) => dir,
57        Err(err) => {
58            debug!(%err, ?path, "failed to read corpus directory");
59            return vec![].into_iter();
60        }
61    };
62
63    dir.filter_map(|res| {
64        let entry =
65            res.inspect_err(|err| debug!(%err, "failed to read corpus directory entry")).ok()?;
66        let path = entry.path();
67        if !path.is_file() {
68            return None;
69        }
70        let name = path.file_name()?.to_str()?;
71        match parse_corpus_filename(name) {
72            Ok((uuid, timestamp)) => Some(CorpusDirEntry { path, uuid, timestamp }),
73            Err(_) => {
74                debug!(target: "corpus", ?path, "failed to parse corpus filename");
75                None
76            }
77        }
78    })
79    .collect::<Vec<_>>()
80    .into_iter()
81}
82
83/// Parses a corpus filename of the form `<uuid>-<timestamp>.json[.gz]`.
84pub fn parse_corpus_filename(name: &str) -> Result<(Uuid, u64)> {
85    let name = name.trim_end_matches(".gz").trim_end_matches(".json");
86    let (uuid_str, timestamp_str) =
87        name.rsplit_once('-').ok_or_else(|| eyre!("invalid corpus filename format: {name}"))?;
88    Ok((Uuid::parse_str(uuid_str)?, timestamp_str.parse()?))
89}