#![feature(os_str_display)]
#![feature(duration_millis_float)]
#![recursion_limit = "1024"]
extern crate core;
use crate::{
cli::{
config::Configuration,
env::{
PhinkEnv,
PhinkEnv::FromZiggy,
},
format_error,
ziggy::ZiggyConfig,
},
cover::report::CoverageTracker,
fuzzer::fuzz::{
Fuzzer,
FuzzingMode::{
ExecuteOneInput,
Fuzz,
},
},
instrumenter::{
instrumentation::Instrumenter,
seedgen::generator::SeedExtractInjector,
traits::visitor::ContractVisitor,
},
};
use anyhow::{
bail,
Context,
};
use clap::Parser;
use std::{
env::var,
path::PathBuf,
};
use PhinkEnv::FuzzingWithConfig;
pub mod cli;
pub mod contract;
pub mod cover;
pub mod fuzzer;
pub mod instrumenter;
pub type EmptyResult = anyhow::Result<()>;
pub type ResultOf<T> = anyhow::Result<T>;
#[derive(Parser, Debug)]
#[clap(
author,
version,
about = "🐙 Phink: An ink! smart-contract property-based and coverage-guided fuzzer",
long_about = None
)]
#[command(
help_template = "{before-help}{about-with-newline}🧑🎨 {author-with-newline}\n{usage-heading}\n {usage}\n\n{all-args}{after-help}"
)]
struct Cli {
#[clap(subcommand)]
command: Commands,
#[clap(long, short, value_parser, default_value = "phink.toml")]
config: PathBuf,
}
#[derive(clap::Subcommand, Debug)]
#[allow(deprecated)]
enum Commands {
Fuzz,
GenerateSeed {
contract: PathBuf,
compiled_directory: Option<PathBuf>,
},
Instrument(Contract),
Run,
HarnessCover,
Coverage(Contract),
Execute {
seed: PathBuf,
},
Minimize,
}
#[derive(clap::Args, Debug, Clone)]
struct Contract {
#[clap(value_parser)]
contract_path: PathBuf,
}
pub fn main() {
if let Ok(config_str) = var(FuzzingWithConfig.to_string()) {
if var(FromZiggy.to_string()).is_ok() {
let config = ZiggyConfig::parse(config_str);
match Fuzzer::new(config) {
Ok(fuzzer) => {
if let Err(e) = fuzzer.execute_harness(Fuzz) {
eprintln!("{}", format_error(e));
}
}
Err(e) => {
eprintln!("{}", format_error(e));
}
}
}
} else if let Err(e) = handle_cli() {
eprintln!("{}", format_error(e));
}
}
fn handle_cli() -> EmptyResult {
let cli = Cli::parse();
let conf = &cli.config;
if !conf.exists() {
bail!(format!(
"No configuration found at {}, please create a phink.toml. You can get a sample at https://github.com/srlabs/phink/blob/main/phink.toml\
\nFeel free to `wget https://raw.githubusercontent.com/srlabs/phink/refs/heads/main/phink.toml` and customize it as you wish",
conf.to_str().unwrap(),
))
}
let config: Configuration = Configuration::try_from(conf)?;
match cli.command {
Commands::Instrument(contract_path) => {
let z_config: ZiggyConfig = ZiggyConfig::new_with_contract(
config.to_owned(),
contract_path.contract_path.to_owned(),
)
.context("Couldn't generate handle the ZiggyConfig")?;
let engine = Instrumenter::new(z_config.to_owned());
engine
.to_owned()
.instrument()
.context("Couldn't instrument")?;
engine.build().context("Couldn't run the build")?;
println!(
"\n🤞 Contract '{}' has been instrumented and compiled.\n🤞 You can find the instrumented contract in `{}`",
z_config.contract_path()?.display(),
z_config.config().instrumented_contract().display()
);
Ok(())
}
Commands::Fuzz => {
ZiggyConfig::new(config)
.context("Couldn't generate handle the ZiggyConfig")?
.ziggy_fuzz()
}
Commands::Run => {
ZiggyConfig::new(config)
.context("Couldn't generate handle the ZiggyConfig")?
.ziggy_run()
}
Commands::Minimize => {
ZiggyConfig::new(config)
.context("Couldn't generate handle the ZiggyConfig")?
.ziggy_minimize()
}
Commands::Execute { seed } => {
let fuzzer = Fuzzer::new(ZiggyConfig::new(config))
.context("Creating a new fuzzer instance faled")?;
fuzzer.execute_harness(ExecuteOneInput(seed))
}
Commands::HarnessCover => {
ZiggyConfig::new(config)
.context("Couldn't generate handle the ZiggyConfig")?
.ziggy_cover()
}
Commands::Coverage(contract_path) => {
CoverageTracker::generate(
ZiggyConfig::new_with_contract(config, contract_path.contract_path)
.context("Couldn't generate handle the ZiggyConfig")?,
)
}
Commands::GenerateSeed {
contract,
compiled_directory,
} => {
let mut seeder = SeedExtractInjector::new(&contract, compiled_directory)?;
seeder
.extract(&config.fuzz_output.unwrap_or_default())
.context(format!("Couldn't extract the seed from {contract:?}"))?;
Ok(())
}
}
}