foundry2echidna/cli/
mod.rs

1use crate::deserialization::deserialize_broadcast;
2use crate::file_handling::{read_broadcast_file, write_transformed_broadcast_to_file};
3use crate::serialization::{add_account_created_events, serialize_broadcast};
4use clap::Parser;
5use glob::glob;
6use std::{fs::create_dir_all, path::Path};
7
8/// Transforms a Foundry broadcast file into a format that is compatible with Echidna.
9///
10/// # Arguments
11/// * `input_path` - Path to the Foundry broadcast file to be transformed.
12/// * `output_path` - Path to a file where you want to save the transformed broadcast.
13///
14/// If `input_path` is not provided, the default path is `broadcast/*.s.sol/31337/run-latest.json`.
15/// If `output_path` is not provided, the default path is `src/crytic/init.json`.
16///
17/// # Examples
18///
19/// ```
20/// use foundry2echidna::cli::transform_broadcast;
21/// transform_broadcast("tests/data/input.json", "tests/data/output.json").unwrap();
22/// ```
23pub fn transform_broadcast(input_path: &str, output_path: &str) -> Result<(), String> {
24    let broadcast_to_deserialize = read_broadcast_file(input_path)?;
25    let broadcast = deserialize_broadcast(&broadcast_to_deserialize)?;
26    let broadcast = serialize_broadcast(broadcast)?;
27    let etheno_like_broadcast = add_account_created_events(broadcast)?;
28    write_transformed_broadcast_to_file(&etheno_like_broadcast, output_path)?;
29    Ok(())
30}
31
32#[derive(Parser, Debug)]
33#[command(author, version, about, long_about = None )]
34pub struct Args {
35    #[clap(
36        short,
37        long,
38        help = r#"Path to the Foundry broadcast file to be transformed.
39If not provided, the default path is `broadcast/*.s.sol/31337/run-latest.json`. 
40Please note that if you have a couple of directories in the `broadcast` dir, 
41the first one found will be used by default."#
42    )]
43    pub input_path: Option<String>,
44
45    #[clap(
46        short,
47        long,
48        help = "Path to a file where you want to save the transformed broadcast. If not provided, the default path is `src/crytic/init.json`."
49    )]
50    pub output_path: Option<String>,
51}
52
53impl Args {
54    pub fn new() -> Result<Self, String> {
55        let mut args = Self::parse();
56        if args.input_path.is_none() {
57            let glob_pattern = "broadcast/*.s.sol/31337/run-latest.json";
58            let mut paths = match glob(glob_pattern) {
59                Ok(paths) => paths,
60                Err(e) => return Err(e.to_string()),
61            };
62            let path = match paths.next() {
63                Some(path) => path,
64                None => return Err("No matching input paths found".to_string()),
65            };
66            args.input_path = Some(match path.unwrap().to_str() {
67                Some(s) => s.to_string(),
68                None => return Err("Failed to convert input path to string".to_string()),
69            });
70        }
71        if args.output_path.is_none() {
72            let output_dir = Path::new("src/crytic");
73            match create_dir_all(output_dir) {
74                Ok(_) => {}
75                Err(e) => return Err(e.to_string()),
76            };
77            args.output_path = Some(match output_dir.join("init.json").to_str() {
78                Some(s) => s.to_string(),
79                None => return Err("Failed to convert output path to string".to_string()),
80            });
81        } else {
82            let output_path = match Path::new(args.output_path.as_ref().unwrap()).parent() {
83                Some(p) => p,
84                None => {
85                    return Err("Failed to extract parent directory from output path".to_string())
86                }
87            };
88            match create_dir_all(output_path) {
89                Ok(_) => {}
90                Err(e) => return Err(e.to_string()),
91            };
92        }
93        Ok(args)
94    }
95}