diff --git a/package/version b/package/version index a52e041..072d0fa 100644 --- a/package/version +++ b/package/version @@ -1 +1 @@ -0.1.35 +0.1.36 diff --git a/pyproject.toml b/pyproject.toml index b82ee07..53fcb9c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "skribe" -version = "0.1.35" +version = "0.1.36" description = "Property testing for Stylus smart contracts" readme = "README.md" requires-python = "~=3.10" diff --git a/skribe-fuzz-rs/Cargo.lock b/skribe-fuzz-rs/Cargo.lock index fffcc0c..42097c1 100644 --- a/skribe-fuzz-rs/Cargo.lock +++ b/skribe-fuzz-rs/Cargo.lock @@ -21,7 +21,9 @@ dependencies = [ "alloy-primitives", "alloy-sol-type-parser", "alloy-sol-types", + "arbitrary", "itoa", + "proptest", "serde", "serde_json", "winnow 0.7.15", @@ -46,6 +48,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3b431b4e72cd8bd0ec7a50b4be18e73dab74de0dba180eef171055e5d5926e" dependencies = [ "alloy-rlp", + "arbitrary", "bytes", "cfg-if", "const-hex", @@ -58,6 +61,7 @@ dependencies = [ "keccak-asm", "paste", "proptest", + "proptest-derive", "rand 0.9.4", "rapidhash", "ruint", @@ -207,6 +211,9 @@ name = "arbitrary" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] [[package]] name = "ark-ff" @@ -705,6 +712,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "derive_more" version = "2.1.1" @@ -1071,6 +1089,7 @@ version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ + "arbitrary", "equivalent", "hashbrown 0.17.0", "serde", @@ -1152,7 +1171,7 @@ dependencies = [ [[package]] name = "kframework" version = "0.1.0" -source = "git+https://github.com/runtimeverification/kframework-rs.git?rev=1e7ee3a#1e7ee3a2377a91090e3c66b21dc2ab49e6bc2373" +source = "git+https://github.com/runtimeverification/kframework-rs.git?rev=73c4986#73c4986cedefa8f2190524fa3f8d0948007cb694" dependencies = [ "clap", "serde", @@ -1162,7 +1181,7 @@ dependencies = [ [[package]] name = "kframework_ffi" version = "0.1.0" -source = "git+https://github.com/runtimeverification/kframework-rs.git?rev=1e7ee3a#1e7ee3a2377a91090e3c66b21dc2ab49e6bc2373" +source = "git+https://github.com/runtimeverification/kframework-rs.git?rev=73c4986#73c4986cedefa8f2190524fa3f8d0948007cb694" dependencies = [ "bindgen", "kframework", @@ -1457,6 +1476,17 @@ dependencies = [ "unarray", ] +[[package]] +name = "proptest-derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb6dc647500e84a25a85b100e76c85b8ace114c209432dc174f20aac11d4ed6c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -1625,6 +1655,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0298da754d1395046b0afdc2f20ee76d29a8ae310cd30ffa84ed42acba9cb12a" dependencies = [ "alloy-rlp", + "arbitrary", "ark-ff 0.3.0", "ark-ff 0.4.2", "ark-ff 0.5.0", @@ -1848,6 +1879,7 @@ dependencies = [ "alloy-dyn-abi", "alloy-json-abi", "alloy-primitives", + "arbitrary", "hex-literal", "kframework", "kframework_ffi", @@ -1859,6 +1891,7 @@ dependencies = [ name = "skribe-libfuzzer" version = "0.0.0" dependencies = [ + "arbitrary", "libfuzzer-sys", "pico-args", "skribe-fuzz-rs", diff --git a/skribe-fuzz-rs/Cargo.toml b/skribe-fuzz-rs/Cargo.toml index fca6db0..32c9ae4 100644 --- a/skribe-fuzz-rs/Cargo.toml +++ b/skribe-fuzz-rs/Cargo.toml @@ -7,12 +7,13 @@ version = "0.1.0" edition = "2024" [workspace.dependencies] -alloy-dyn-abi = "1.5.7" +alloy-dyn-abi = { version = "1.5.7", features = ["arbitrary"] } alloy-json-abi = "1.5.7" alloy-primitives = "1.5.7" +arbitrary = "1.4.2" hex-literal = "1.1.0" -kframework = { git = "https://github.com/runtimeverification/kframework-rs.git", rev = "1e7ee3a" } -kframework_ffi = { git = "https://github.com/runtimeverification/kframework-rs.git", rev = "1e7ee3a" } +kframework = { git = "https://github.com/runtimeverification/kframework-rs.git", rev = "73c4986" } +kframework_ffi = { git = "https://github.com/runtimeverification/kframework-rs.git", rev = "73c4986" } libfuzzer-sys = "0.4" pico-args = "0.5.0" serde = "1.0.228" @@ -26,6 +27,7 @@ edition.workspace = true [dependencies] alloy-json-abi.workspace = true alloy-dyn-abi.workspace = true +arbitrary.workspace = true serde.workspace = true serde_json.workspace = true kframework.workspace = true diff --git a/skribe-fuzz-rs/fuzz/Cargo.toml b/skribe-fuzz-rs/fuzz/Cargo.toml index 48bffaa..570d5b1 100644 --- a/skribe-fuzz-rs/fuzz/Cargo.toml +++ b/skribe-fuzz-rs/fuzz/Cargo.toml @@ -8,6 +8,7 @@ edition.workspace = true cargo-fuzz = true [dependencies] +arbitrary.workspace = true libfuzzer-sys.workspace = true pico-args = { workspace = true, features = ["eq-separator"] } diff --git a/skribe-fuzz-rs/fuzz/fuzz_targets/fuzz_target_1.rs b/skribe-fuzz-rs/fuzz/fuzz_targets/fuzz_target_1.rs index 9310cf3..32504db 100644 --- a/skribe-fuzz-rs/fuzz/fuzz_targets/fuzz_target_1.rs +++ b/skribe-fuzz-rs/fuzz/fuzz_targets/fuzz_target_1.rs @@ -1,28 +1,122 @@ #![no_main] +use std::cell::Cell; +use arbitrary::Unstructured; use libfuzzer_sys::fuzz_target; use pico_args::Arguments; -use skribe_fuzz_rs::{kllvm, fuzz_specs_from_json, make_dv}; +use skribe_fuzz_rs::{ + FuzzSpec, Signature, SignatureAbi, fuzz_specs_from_json, get_exit_code, + kllvm::{self, MarshalError, Marshaller, VarHandler}, + kore, +}; + +struct FuzzConfig { + template: kore::Pattern, + abi: SignatureAbi, +} + +struct SignatureFuzzer(Vec); + +impl VarHandler for SignatureFuzzer { + fn substitute( + &mut self, + name: &str, + _sort: &kore::Sort, + ) -> Result { + let sort = kore::Sort::App { + id: kore::Id::new("SortBytes".to_string()).unwrap(), + args: vec![], + }; + let value = kore::Str(self.0.iter().map(|&b| b as char).collect()); + match name { + "VarCALLDATA" => Ok(kore::Pattern::Dv { sort, value }), + _ => Err(MarshalError::Unsupported( + "Encountered a variable that isn't CALLDATA", + )), + } + } +} + +// Persistent data across iterations. +// +// FUZZ_CONFIG - The fuzz spec + contract/function names to fuzz. Parsed from the command line +// MARSHALLER - The marshaller for moving terms over to kllvm. Keeps parts of the template +// configuration cached. +thread_local! { + static FUZZ_CONFIG: Cell> = Cell::new(None); + static MARSHALLER: Cell>> = Cell::new(Some(Marshaller::new(None))) +} fuzz_target!( init: { kllvm::init(); - let mut args = Arguments::from_env(); - let fuzz_spec_file: Option = args - // You must pass this option as `--fuzz-spec=` with the - // equals sign, otherwise libfuzzer treats it as a positional argument - .opt_value_from_str("--fuzz-spec") + // Parse arguments + // + // You must pass these options as `--xxx=` with the equals sign, + // otherwise libfuzzer treats `` as a positional argument. + let mut args = Arguments::from_env(); + let fuzz_spec_file: String = args + .value_from_str("--fuzz-spec") + .unwrap(); + let contract_name: String = args + .value_from_str("--contract-name") + .unwrap(); + let function_name: String = args + .value_from_str("--function-name") .unwrap(); - if let Some(file) = fuzz_spec_file { - let contents = std::fs::read_to_string(file).unwrap(); - let specs = fuzz_specs_from_json(&contents).unwrap(); - println!("{:?}", specs); - } + // Parse fuzz spec + let contents = std::fs::read_to_string(fuzz_spec_file).unwrap(); + let specs = fuzz_specs_from_json(&contents).unwrap(); + let (template_str, signature) = extract_template_and_signature(specs, &contract_name, &function_name).unwrap(); + + let mut parser = kore::Parser::new(&template_str).unwrap(); + let template = parser.pattern().unwrap(); + + let abi = SignatureAbi::from_signature(signature).unwrap(); + + FUZZ_CONFIG.replace(Some(FuzzConfig { template, abi })); }, |data: &[u8]| { - let _ = make_dv(); - // fuzzed code goes here + let mut marshaller_cell: Option> = MARSHALLER.take(); + let marshaller = marshaller_cell.as_mut().unwrap(); + let config_cell = FUZZ_CONFIG.take(); + let config = config_cell.as_ref().unwrap(); + + // Marshal over to kllvm with the CALLDATA variable substituted + let mut u = Unstructured::new(data); + let input = config.abi.arbitrary_input(&mut u).unwrap(); + let sig = SignatureFuzzer(input); + marshaller.set_handler(sig); + let template = &config.template; + let kllvm_pattern: kllvm::Pattern = marshaller.marshal(template).unwrap(); + + // Execute the semantics + let mut block: kllvm::Block = kllvm_pattern.into(); + block.take_steps(-1); + + // Check the exit code + let exit_code = get_exit_code(&block); + if exit_code != 0 { + println!("panic!"); + } + + FUZZ_CONFIG.replace(config_cell); + MARSHALLER.replace(marshaller_cell); }); + +pub fn extract_template_and_signature( + specs: Vec, + contract_name: &str, + function_name: &str, +) -> Option<(String, Signature)> { + specs.into_iter().find_map(|spec| { + let template = spec.template; + spec.signatures + .into_iter() + .find(|sig| sig.contract_name == contract_name && sig.name == function_name) + .map(|sig| (template.clone(), sig)) + }) +} diff --git a/skribe-fuzz-rs/src/abi.rs b/skribe-fuzz-rs/src/abi.rs index 2109ecc..2898163 100644 --- a/skribe-fuzz-rs/src/abi.rs +++ b/skribe-fuzz-rs/src/abi.rs @@ -1,6 +1,7 @@ use crate::Signature; use alloy_dyn_abi::{DynSolType, DynSolValue, JsonAbiExt}; use alloy_json_abi::Function; +use arbitrary::Unstructured; use std::fmt::Debug; #[derive(Debug)] @@ -30,11 +31,18 @@ impl SignatureAbi { Ok(SignatureAbi { types, function }) } - pub fn types(&self) -> &[DynSolType] { - &self.types + pub fn arbitrary_input(&self, u: &mut Unstructured<'_>) -> arbitrary::Result> { + let values = self + .types + .iter() + .map(|ty| DynSolValue::arbitrary_from_type(ty, u)) + .collect::>>()?; + + self.encode_input(&values) + .map_err(|_| arbitrary::Error::IncorrectFormat) } - pub fn encode_input(&self, values: &[DynSolValue]) -> Result, String> { + fn encode_input(&self, values: &[DynSolValue]) -> Result, String> { self.function .abi_encode_input(values) .map_err(|e| e.to_string()) @@ -70,7 +78,7 @@ mod tests { ]; // When - let actual = abi.types(); + let actual = abi.types; // Then assert_eq!(actual, expected); @@ -119,6 +127,20 @@ mod tests { assert_eq!(actual, expected); } + #[test] + fn test_arbitrary_input() { + // Given + let abi = signature_abi(); + let raw = vec![0u8; 256]; + let mut u = Unstructured::new(&raw); + + // When + let result = abi.arbitrary_input(&mut u); + + // Then + assert!(result.is_ok()); + } + fn signature_abi() -> SignatureAbi { SignatureAbi::from_signature(signature()).unwrap() } diff --git a/skribe-fuzz-rs/src/lib.rs b/skribe-fuzz-rs/src/lib.rs index e3057ab..3ef23fa 100644 --- a/skribe-fuzz-rs/src/lib.rs +++ b/skribe-fuzz-rs/src/lib.rs @@ -7,27 +7,14 @@ pub use fuzz_spec::{FuzzSpec, Signature, fuzz_specs_from_json}; pub use kframework::kore; pub use kframework_ffi::kllvm; -use kframework::kore::{Id, Pattern, Sort}; -use kframework_ffi::kllvm::{Marshaller, VarHandler}; - -struct DummyHandler; - -impl VarHandler for DummyHandler { - fn substitute(&mut self, _name: &str, _sort: &Sort) -> Result { - Err(kllvm::MarshalError::Unsupported("Not handling variables")) - } -} - -pub fn make_dv() -> kllvm::Pattern { - let dv = Pattern::Dv { - sort: Sort::App { - id: Id::new("SortInt".to_string()).unwrap(), - args: vec![], - }, - value: "1".into(), - }; - - let mut marshal: Marshaller = Marshaller::new(None); - - marshal.marshal(&dv).unwrap() +/// Get the cell value from a configuration +pub fn get_exit_code(block: &kllvm::Block) -> u32 { + let res_str = format!("{}", block); + let pattern = r#"Lbl'-LT-'exit-code'-GT-'{}(\dv{SortInt{}}(""#; + let idx = res_str.find(pattern).unwrap(); + let slice = &res_str[idx + pattern.len()..]; + let idx2 = slice.find(r#"""#).unwrap(); + let num_str = &slice[..idx2]; + + num_str.parse().unwrap() } diff --git a/uv.lock b/uv.lock index 3b5c721..3c53775 100644 --- a/uv.lock +++ b/uv.lock @@ -1815,7 +1815,7 @@ wheels = [ [[package]] name = "skribe" -version = "0.1.35" +version = "0.1.36" source = { editable = "." } dependencies = [ { name = "kontrol" },