1#![feature(os_str_display)]
2#![feature(duration_millis_float)]
3#![recursion_limit = "1024"]
4
5extern crate core;
6use crate::{
7 cli::{
8 config::Configuration,
9 env::{
10 PhinkEnv,
11 PhinkEnv::FromZiggy,
12 },
13 format_error,
14 ziggy::ZiggyConfig,
15 },
16 cover::report::CoverageTracker,
17 fuzzer::fuzz::{
18 Fuzzer,
19 FuzzingMode::{
20 ExecuteOneInput,
21 Fuzz,
22 },
23 },
24 instrumenter::{
25 instrumentation::Instrumenter,
26 seedgen::generator::SeedExtractInjector,
27 traits::visitor::ContractVisitor,
28 },
29};
30use anyhow::{
31 bail,
32 Context,
33};
34use clap::Parser;
35use std::{
36 env::var,
37 path::PathBuf,
38};
39use PhinkEnv::FuzzingWithConfig;
40
41pub mod cli;
42pub mod contract;
43pub mod cover;
44pub mod fuzzer;
45pub mod instrumenter;
46
47pub type EmptyResult = anyhow::Result<()>;
50pub type ResultOf<T> = anyhow::Result<T>;
51
52#[derive(Parser, Debug)]
54#[clap(
55 author,
56 version,
57 about = "š Phink: An ink! smart-contract property-based and coverage-guided fuzzer",
58 long_about = None
59)]
60#[command(
61 help_template = "{before-help}{about-with-newline}š§āšØ {author-with-newline}\n{usage-heading}\n {usage}\n\n{all-args}{after-help}"
62)]
63struct Cli {
64 #[clap(subcommand)]
66 command: Commands,
67 #[clap(long, short, value_parser, default_value = "phink.toml")]
69 config: PathBuf,
70}
71
72#[derive(clap::Subcommand, Debug)]
73#[allow(deprecated)]
74enum Commands {
75 Fuzz,
77 GenerateSeed {
81 contract: PathBuf,
84 compiled_directory: Option<PathBuf>,
87 },
88 Instrument(Contract),
90 Run,
92 HarnessCover,
95 Coverage(Contract),
98 Execute {
100 seed: PathBuf,
102 },
103 Minimize,
105}
106
107#[derive(clap::Args, Debug, Clone)]
108struct Contract {
109 #[clap(value_parser)]
112 contract_path: PathBuf,
113}
114pub fn main() {
115 if let Ok(config_str) = var(FuzzingWithConfig.to_string()) {
117 if var(FromZiggy.to_string()).is_ok() {
118 let config = ZiggyConfig::parse(config_str);
119 match Fuzzer::new(config) {
120 Ok(fuzzer) => {
121 if let Err(e) = fuzzer.execute_harness(Fuzz) {
122 eprintln!("{}", format_error(e));
123 }
124 }
125 Err(e) => {
126 eprintln!("{}", format_error(e));
127 }
128 }
129 }
130 } else if let Err(e) = handle_cli() {
131 eprintln!("{}", format_error(e));
132 }
133}
134
135fn handle_cli() -> EmptyResult {
136 let cli = Cli::parse();
137 let conf = &cli.config;
138 if !conf.exists() {
139 bail!(format!(
140 "No configuration found at {}, please create a phink.toml. You can get a sample at https://github.com/srlabs/phink/blob/main/phink.toml\
141 \nFeel free to `wget https://raw.githubusercontent.com/srlabs/phink/refs/heads/main/phink.toml` and customize it as you wish",
142 conf.to_str().unwrap(),
143 ))
144 }
145 let config: Configuration = Configuration::try_from(conf)?;
146
147 match cli.command {
148 Commands::Instrument(contract_path) => {
149 let z_config: ZiggyConfig = ZiggyConfig::new_with_contract(
150 config.to_owned(),
151 contract_path.contract_path.to_owned(),
152 )
153 .context("Couldn't generate handle the ZiggyConfig")?;
154
155 let engine = Instrumenter::new(z_config.to_owned());
156 engine
157 .to_owned()
158 .instrument()
159 .context("Couldn't instrument")?;
160
161 engine.build().context("Couldn't run the build")?;
162
163 println!(
164 "\nš¤ Contract '{}' has been instrumented and compiled.\nš¤ You can find the instrumented contract in `{}`",
165 z_config.contract_path()?.display(),
166 z_config.config().instrumented_contract().display()
167 );
168 Ok(())
169 }
170 Commands::Fuzz => {
171 ZiggyConfig::new(config)
172 .context("Couldn't generate handle the ZiggyConfig")?
173 .ziggy_fuzz()
174 }
175 Commands::Run => {
176 ZiggyConfig::new(config)
177 .context("Couldn't generate handle the ZiggyConfig")?
178 .ziggy_run()
179 }
180 Commands::Minimize => {
181 ZiggyConfig::new(config)
182 .context("Couldn't generate handle the ZiggyConfig")?
183 .ziggy_minimize()
184 }
185 Commands::Execute { seed } => {
186 let fuzzer = Fuzzer::new(ZiggyConfig::new(config))
187 .context("Creating a new fuzzer instance faled")?;
188 fuzzer.execute_harness(ExecuteOneInput(seed))
189 }
190 Commands::HarnessCover => {
191 ZiggyConfig::new(config)
192 .context("Couldn't generate handle the ZiggyConfig")?
193 .ziggy_cover()
194 }
195 Commands::Coverage(contract_path) => {
196 CoverageTracker::generate(
197 ZiggyConfig::new_with_contract(config, contract_path.contract_path)
198 .context("Couldn't generate handle the ZiggyConfig")?,
199 )
200 }
201 Commands::GenerateSeed {
202 contract,
203 compiled_directory,
204 } => {
205 let mut seeder = SeedExtractInjector::new(&contract, compiled_directory)?;
206 seeder
207 .extract(&config.fuzz_output.unwrap_or_default())
208 .context(format!("Couldn't extract the seed from {contract:?}"))?;
209 Ok(())
210 }
211 }
212}