foundry_evm/executors/
corpus_io.rs1use 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
11pub 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
32pub 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
53pub 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
83pub 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}